Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion .github/workflows/test-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,36 @@ jobs:
done
}

# Mirror of latestReleaseVersion() in integration/util.go: VERSION names the version
# being prepared, which on a release branch is not published yet, so a pre-release
# resolves to the release preceding it. Keep the two implementations in sync.
latest_release_image() {
if [ -n "${CORTEX_LATEST_RELEASE_IMAGE:-}" ]; then
echo "$CORTEX_LATEST_RELEASE_IMAGE"
return 0
fi

local version major minor patch
version=$(cat testdata/VERSION)
case "$version" in
*-*)
IFS='.' read -r major minor patch <<< "${version%%-*}"
if [ "$patch" -gt 0 ]; then
patch=$((patch - 1))
elif [ "$minor" -gt 0 ]; then
minor=$((minor - 1))
patch=0
else
echo "ERROR: cannot resolve the release preceding major pre-release version ${version};" \
"set CORTEX_LATEST_RELEASE_IMAGE to the latest published release image." >&2
return 1
fi
version="${major}.${minor}.${patch}"
;;
esac
echo "quay.io/cortexproject/cortex:v${version}"
}

retry docker pull minio/minio:RELEASE.2024-05-28T17-19-04Z
retry docker pull consul:1.8.4
retry docker pull quay.io/coreos/etcd:v3.5.29
Expand All @@ -329,7 +359,7 @@ jobs:
retry docker pull quay.io/cortexproject/cortex:v1.21.0
retry docker pull quay.io/cortexproject/cortex:v1.21.1
elif [ "$TEST_TAGS" = "integration_query_fuzz" ]; then
retry docker pull quay.io/cortexproject/cortex:v$(cat testdata/VERSION)
retry docker pull "$(latest_release_image)"
retry docker pull quay.io/prometheus/prometheus:v3.9.1
elif [ "$TEST_TAGS" = "integration_configs_db" ]; then
retry docker pull postgres:9.6.16
Expand Down
83 changes: 79 additions & 4 deletions integration/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -36,20 +37,94 @@ func getCortexProjectDir() string {
return os.Getenv("GOPATH") + "/src/github.com/cortexproject/cortex"
}

// getLatestReleaseImage returns the Cortex image reference for the latest release,
// derived from the VERSION file at the project root.
// getLatestReleaseImage returns the Cortex image reference for the latest published
// release, derived from the VERSION file at the project root.
//
// Set CORTEX_LATEST_RELEASE_IMAGE to override the resolution entirely.
//
// If you change how this resolves, remember to update the preloading done by GitHub
// Actions too (see .github/workflows/test-build-deploy.yml).
func getLatestReleaseImage() (string, error) {
if image := os.Getenv("CORTEX_LATEST_RELEASE_IMAGE"); image != "" {
return image, nil
}

content, err := os.ReadFile(filepath.Join(getCortexProjectDir(), "VERSION"))
if err != nil {
return "", errors.Wrap(err, "unable to read VERSION file")
}

version := strings.TrimSpace(string(content))
version, err := latestReleaseVersion(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}

return fmt.Sprintf("quay.io/cortexproject/cortex:v%s", version), nil
}

// latestReleaseVersion maps the contents of the VERSION file to a version that has
// actually been published to the container registries.
//
// VERSION does not always name a published release. On a release branch it is bumped to
// the version being prepared (e.g. "1.22.0-rc.0") long before the deploy job publishes
// that tag, and the integration job runs before deploy. So a pre-release version resolves
// to the release preceding it, which is always already published by then:
//
// 1.21.1 -> 1.21.1 (VERSION on master is the last GA, whose image exists)
// 1.22.0-rc.0 -> 1.21.0 (the previous minor always shipped a .0)
// 1.22.2-rc.1 -> 1.22.1 (the preceding patch of the same minor)
func latestReleaseVersion(version string) (string, error) {
if version == "" {
return "", errors.New("VERSION file is empty")
}

return fmt.Sprintf("quay.io/cortexproject/cortex:v%s", version), nil
// Anything after the first "-" is a pre-release identifier (e.g. "-rc.0").
base, preRelease, isPreRelease := strings.Cut(version, "-")
if !isPreRelease {
return version, nil
}

major, minor, patch, err := parseVersion(base)
if err != nil {
return "", errors.Wrapf(err, "unable to resolve the release preceding pre-release version %q", version)
}

switch {
case patch > 0:
// A patch pre-release: the preceding patch of the same minor is published.
patch--
case minor > 0:
// A minor pre-release: the previous minor's initial release is published. Using
// .0 rather than its latest patch keeps this derivable from VERSION alone.
minor--
patch = 0
default:
// A major pre-release (e.g. "2.0.0-rc.0"). The last release of the previous major
// is not derivable from VERSION, so the maintainer has to say which one it is.
return "", errors.Errorf("cannot resolve the release preceding major pre-release version %q (base %q, pre-release %q):"+
" set CORTEX_LATEST_RELEASE_IMAGE to the latest published release image", version, base, preRelease)
}

return fmt.Sprintf("%d.%d.%d", major, minor, patch), nil
}

func parseVersion(version string) (major, minor, patch int, err error) {
parts := strings.Split(version, ".")
if len(parts) != 3 {
return 0, 0, 0, errors.Errorf("expected a major.minor.patch version, got %q", version)
}

out := make([]int, len(parts))
for i, part := range parts {
if out[i], err = strconv.Atoi(part); err != nil {
return 0, 0, 0, errors.Wrapf(err, "invalid version %q", version)
}
if out[i] < 0 {
return 0, 0, 0, errors.Errorf("invalid version %q", version)
}
}

return out[0], out[1], out[2], nil
}

func writeFileToSharedDir(s *e2e.Scenario, dst string, content []byte) error {
Expand Down
94 changes: 94 additions & 0 deletions integration/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//go:build integration

package integration

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLatestReleaseVersion(t *testing.T) {
tests := map[string]struct {
version string
expected string
expectedErr bool
}{
"a GA version is already published": {
version: "1.21.1",
expected: "1.21.1",
},
"a GA version with a zero patch is already published": {
version: "1.21.0",
expected: "1.21.0",
},
"a minor release candidate falls back to the previous minor": {
version: "1.22.0-rc.0",
expected: "1.21.0",
},
"a later minor release candidate falls back to the same previous minor": {
version: "1.22.0-rc.3",
expected: "1.21.0",
},
"a patch release candidate falls back to the preceding patch": {
version: "1.22.1-rc.0",
expected: "1.22.0",
},
"a later patch release candidate falls back to the preceding patch": {
version: "1.22.3-rc.1",
expected: "1.22.2",
},
"a major release candidate cannot be resolved": {
version: "2.0.0-rc.0",
expectedErr: true,
},
"an empty VERSION is rejected": {
version: "",
expectedErr: true,
},
"a malformed pre-release base is rejected": {
version: "1.22-rc.0",
expectedErr: true,
},
"a non-numeric pre-release base is rejected": {
version: "1.x.0-rc.0",
expectedErr: true,
},
}

for name, testData := range tests {
t.Run(name, func(t *testing.T) {
actual, err := latestReleaseVersion(testData.version)
if testData.expectedErr {
require.Error(t, err)
return
}

require.NoError(t, err)
assert.Equal(t, testData.expected, actual)
})
}
}

func TestGetLatestReleaseImage(t *testing.T) {
// Point getCortexProjectDir() at a scratch checkout so we can exercise the VERSION file
// contents a release branch would actually have.
dir := t.TempDir()
t.Setenv("CORTEX_CHECKOUT_DIR", dir)
require.NoError(t, os.WriteFile(filepath.Join(dir, "VERSION"), []byte("1.22.0-rc.0\n"), 0o600))

image, err := getLatestReleaseImage()
require.NoError(t, err)
assert.Equal(t, "quay.io/cortexproject/cortex:v1.21.0", image)
}

func TestGetLatestReleaseImage_HonorsOverride(t *testing.T) {
t.Setenv("CORTEX_LATEST_RELEASE_IMAGE", "quay.io/cortexproject/cortex:v1.20.1")

image, err := getLatestReleaseImage()
require.NoError(t, err)
assert.Equal(t, "quay.io/cortexproject/cortex:v1.20.1", image)
}