From c3bdf6912e67c69af0832225169e2c48b8536f1f Mon Sep 17 00:00:00 2001 From: adil ghaffar Date: Wed, 7 Jun 2023 15:13:56 +0300 Subject: [PATCH] Resolve release markers Signed-off-by: muhammad adil ghaffar --- .../repository/repository_github_test.go | 82 ++++++---------- .../repository/repository_gitlab_test.go | 3 +- internal/goproxy/goproxy_test.go | 39 ++------ internal/goproxy/test/test_utils.go | 52 +++++++++++ test/e2e/clusterctl_upgrade.go | 1 + test/e2e/clusterctl_upgrade_test.go | 64 +++++++++---- test/e2e/common.go | 9 ++ test/e2e/config/docker.yaml | 48 +++++----- test/framework/clusterctl/e2e_config.go | 93 ++++++++++++++++++- test/framework/clusterctl/e2e_config_test.go | 90 ++++++++++++++++++ 10 files changed, 354 insertions(+), 127 deletions(-) create mode 100644 internal/goproxy/test/test_utils.go create mode 100644 test/framework/clusterctl/e2e_config_test.go diff --git a/cmd/clusterctl/client/repository/repository_github_test.go b/cmd/clusterctl/client/repository/repository_github_test.go index 6f9210dfde6f..36baebcb078f 100644 --- a/cmd/clusterctl/client/repository/repository_github_test.go +++ b/cmd/clusterctl/client/repository/repository_github_test.go @@ -20,8 +20,6 @@ import ( "context" "fmt" "net/http" - "net/http/httptest" - "net/url" "testing" "time" @@ -33,6 +31,7 @@ import ( "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" "sigs.k8s.io/cluster-api/internal/goproxy" + goproxytest "sigs.k8s.io/cluster-api/internal/goproxy/test" ) func Test_gitHubRepository_GetVersions(t *testing.T) { @@ -44,7 +43,7 @@ func Test_gitHubRepository_GetVersions(t *testing.T) { // Setup an handler for returning 5 fake releases. mux.HandleFunc("/repos/o/r1/releases", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, `[`) fmt.Fprint(w, `{"id":1, "tag_name": "v0.4.0"},`) fmt.Fprint(w, `{"id":2, "tag_name": "v0.4.1"},`) @@ -53,12 +52,13 @@ func Test_gitHubRepository_GetVersions(t *testing.T) { fmt.Fprint(w, `]`) }) - clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := goproxy.NewClient(scheme, host) defer teardownGoproxy() // Setup a handler for returning 4 fake releases. muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.5.0\n") fmt.Fprint(w, "v0.4.0\n") fmt.Fprint(w, "v0.3.2\n") @@ -67,16 +67,16 @@ func Test_gitHubRepository_GetVersions(t *testing.T) { // Setup a handler for returning 3 different major fake releases. muxGoproxy.HandleFunc("/github.com/o/r3/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v1.0.0\n") fmt.Fprint(w, "v0.1.0\n") }) muxGoproxy.HandleFunc("/github.com/o/r3/v2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v2.0.0\n") }) muxGoproxy.HandleFunc("/github.com/o/r3/v3/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v3.0.0\n") }) @@ -271,13 +271,13 @@ func Test_githubRepository_getFile(t *testing.T) { // Setup a handler for returning a fake release. mux.HandleFunc("/repos/o/r/releases/tags/v0.4.1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, `{"id":13, "tag_name": "v0.4.1", "assets": [{"id": 1, "name": "file.yaml"}] }`) }) // Setup a handler for returning a fake release asset. mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=file.yaml") fmt.Fprint(w, "content") @@ -345,7 +345,7 @@ func Test_gitHubRepository_getVersions(t *testing.T) { // Each response contains a link to the next page (if available) which // is parsed by the handler to navigate through all pages mux.HandleFunc("/repos/o/r1/releases", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") page := r.URL.Query().Get("page") switch page { case "", "1": @@ -424,24 +424,26 @@ func Test_gitHubRepository_getLatestContractRelease(t *testing.T) { // Setup a handler for returning a fake release. mux.HandleFunc("/repos/o/r1/releases/tags/v0.5.0", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, `{"id":13, "tag_name": "v0.5.0", "assets": [{"id": 1, "name": "metadata.yaml"}] }`) }) // Setup a handler for returning a fake release metadata file. mux.HandleFunc("/repos/o/r1/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=metadata.yaml") fmt.Fprint(w, "apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3\nreleaseSeries:\n - major: 0\n minor: 4\n contract: v1alpha4\n - major: 0\n minor: 5\n contract: v1alpha4\n - major: 0\n minor: 3\n contract: v1alpha3\n") }) - clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := goproxy.NewClient(scheme, host) + defer teardownGoproxy() // Setup a handler for returning 4 fake releases. muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.5.0\n") fmt.Fprint(w, "v0.4.0\n") fmt.Fprint(w, "v0.3.2\n") @@ -450,7 +452,7 @@ func Test_gitHubRepository_getLatestContractRelease(t *testing.T) { // setup an handler for returning 4 fake releases but no actual tagged release muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.5.0\n") fmt.Fprint(w, "v0.4.0\n") fmt.Fprint(w, "v0.3.2\n") @@ -528,12 +530,13 @@ func Test_gitHubRepository_getLatestContractRelease(t *testing.T) { func Test_gitHubRepository_getLatestRelease(t *testing.T) { retryableOperationInterval = 200 * time.Millisecond retryableOperationTimeout = 1 * time.Second - clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := goproxy.NewClient(scheme, host) defer teardownGoproxy() // Setup a handler for returning 4 fake releases. muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.4.1\n") fmt.Fprint(w, "v0.4.2\n") fmt.Fprint(w, "v0.4.3-alpha\n") // prerelease @@ -541,19 +544,19 @@ func Test_gitHubRepository_getLatestRelease(t *testing.T) { }) // And also expose a release for them muxGoproxy.HandleFunc("/api.github.com/repos/o/r1/releases/tags/v0.4.2", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "{}\n") }) // Setup a handler for returning no releases. muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") // no releases }) // Setup a handler for returning fake prereleases only. muxGoproxy.HandleFunc("/github.com/o/r3/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.1.0-alpha.0\n") fmt.Fprint(w, "v0.1.0-alpha.1\n") fmt.Fprint(w, "v0.1.0-alpha.2\n") @@ -621,12 +624,13 @@ func Test_gitHubRepository_getLatestRelease(t *testing.T) { func Test_gitHubRepository_getLatestPatchRelease(t *testing.T) { retryableOperationInterval = 200 * time.Millisecond retryableOperationTimeout = 1 * time.Second - clientGoproxy, muxGoproxy, teardownGoproxy := newFakeGoproxy() + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := goproxy.NewClient(scheme, host) defer teardownGoproxy() // Setup a handler for returning 4 fake releases. muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v0.4.0\n") fmt.Fprint(w, "v0.3.2\n") fmt.Fprint(w, "v1.3.2\n") @@ -712,7 +716,7 @@ func Test_gitHubRepository_getReleaseByTag(t *testing.T) { // Setup a handler for returning a fake release. mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, `{"id":13, "tag_name": "v0.4.1"}`) }) @@ -783,14 +787,14 @@ func Test_gitHubRepository_downloadFilesFromRelease(t *testing.T) { // Setup a handler for returning a fake release asset. mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=file.yaml") fmt.Fprint(w, "content") }) // Setup a handler which redirects to a different location. mux.HandleFunc("/repos/o/r-with-redirect/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") http.Redirect(w, r, "/api-v3/repos/o/r/releases/assets/1", http.StatusFound) }) @@ -903,35 +907,9 @@ func Test_gitHubRepository_downloadFilesFromRelease(t *testing.T) { } } -func testMethod(t *testing.T, r *http.Request, want string) { - t.Helper() - - if got := r.Method; got != want { - t.Errorf("Request method: %v, want %v", got, want) - } -} - // resetCaches is called repeatedly throughout tests to help avoid cross-test pollution. func resetCaches() { cacheVersions = map[string][]string{} cacheReleases = map[string]*github.RepositoryRelease{} cacheFiles = map[string][]byte{} } - -// newFakeGoproxy sets up a test HTTP server along with a github.Client that is -// configured to talk to that test server. Tests should register handlers on -// mux which provide mock responses for the API method being tested. -func newFakeGoproxy() (client *goproxy.Client, mux *http.ServeMux, teardown func()) { - // mux is the HTTP request multiplexer used with the test server. - mux = http.NewServeMux() - - apiHandler := http.NewServeMux() - apiHandler.Handle("/", mux) - - // server is a test HTTP server used to provide mock API responses. - server := httptest.NewServer(apiHandler) - - // client is the GitHub client being tested and is configured to use test server. - url, _ := url.Parse(server.URL + "/") - return goproxy.NewClient(url.Scheme, url.Host), mux, server.Close -} diff --git a/cmd/clusterctl/client/repository/repository_gitlab_test.go b/cmd/clusterctl/client/repository/repository_gitlab_test.go index 59b0a46b9d81..8844084dae5c 100644 --- a/cmd/clusterctl/client/repository/repository_gitlab_test.go +++ b/cmd/clusterctl/client/repository/repository_gitlab_test.go @@ -28,6 +28,7 @@ import ( clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" + goproxytest "sigs.k8s.io/cluster-api/internal/goproxy/test" ) func Test_gitLabRepository_newGitLabRepository(t *testing.T) { @@ -152,7 +153,7 @@ func Test_gitLabRepository_getFile(t *testing.T) { providerConfig := config.NewProvider("test", providerURL, clusterctlv1.CoreProviderType) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") if r.URL.RawPath == "/api/v4/projects/group%2Fproject/packages/generic/my-package/v0.4.1/file.yaml" { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=file.yaml") diff --git a/internal/goproxy/goproxy_test.go b/internal/goproxy/goproxy_test.go index f25f69b83f68..408684681690 100644 --- a/internal/goproxy/goproxy_test.go +++ b/internal/goproxy/goproxy_test.go @@ -20,38 +20,39 @@ import ( "context" "fmt" "net/http" - "net/http/httptest" - "net/url" "testing" "time" "github.com/blang/semver/v4" . "github.com/onsi/gomega" + + goproxytest "sigs.k8s.io/cluster-api/internal/goproxy/test" ) func TestClient_GetVersions(t *testing.T) { retryableOperationInterval = 200 * time.Millisecond retryableOperationTimeout = 1 * time.Second - clientGoproxy, muxGoproxy, teardownGoproxy := NewFakeGoproxy() + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := NewClient(scheme, host) defer teardownGoproxy() // setup an handler for returning 2 fake releases muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v1.1.0\n") fmt.Fprint(w, "v0.2.0\n") }) // setup an handler for returning 2 fake releases for v1 muxGoproxy.HandleFunc("/github.com/o/r2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v1.1.0\n") fmt.Fprint(w, "v0.2.0\n") }) // setup an handler for returning 2 fake releases for v2 muxGoproxy.HandleFunc("/github.com/o/r2/v2/@v/list", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") + goproxytest.HTTPTestMethod(t, r, "GET") fmt.Fprint(w, "v2.0.1\n") fmt.Fprint(w, "v2.0.0\n") }) @@ -182,29 +183,3 @@ func Test_GetGoproxyHost(t *testing.T) { }) } } - -// NewFakeGoproxy sets up a test HTTP server along with a github.Client that is -// configured to talk to that test server. Tests should register handlers on -// mux which provide mock responses for the API method being tested. -func NewFakeGoproxy() (client *Client, mux *http.ServeMux, teardown func()) { - // mux is the HTTP request multiplexer used with the test server. - mux = http.NewServeMux() - - apiHandler := http.NewServeMux() - apiHandler.Handle("/", mux) - - // server is a test HTTP server used to provide mock API responses. - server := httptest.NewServer(apiHandler) - - // client is the GitHub client being tested and is configured to use test server. - url, _ := url.Parse(server.URL + "/") - return NewClient(url.Scheme, url.Host), mux, server.Close -} - -func testMethod(t *testing.T, r *http.Request, want string) { - t.Helper() - - if got := r.Method; got != want { - t.Errorf("Request method: %v, want %v", got, want) - } -} diff --git a/internal/goproxy/test/test_utils.go b/internal/goproxy/test/test_utils.go new file mode 100644 index 000000000000..2002b80761d9 --- /dev/null +++ b/internal/goproxy/test/test_utils.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package test contains test util functions for goproxy. +package test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +// NewFakeGoproxy sets up a test HTTP server along with a github.Client that is +// configured to talk to that test server. Tests should register handlers on +// mux which provide mock responses for the API method being tested. +func NewFakeGoproxy() (scheme string, host string, mux *http.ServeMux, teardown func()) { + // mux is the HTTP request multiplexer used with the test server. + mux = http.NewServeMux() + + apiHandler := http.NewServeMux() + apiHandler.Handle("/", mux) + + // server is a test HTTP server used to provide mock API responses. + server := httptest.NewServer(apiHandler) + + // client is the GitHub client being tested and is configured to use test server. + url, _ := url.Parse(server.URL + "/") + return url.Scheme, url.Host, mux, server.Close +} + +// HTTPTestMethod reports error http.Request does not return want string. +func HTTPTestMethod(t *testing.T, r *http.Request, want string) { + t.Helper() + + if got := r.Method; got != want { + t.Errorf("Request method: %v, want %v", got, want) + } +} diff --git a/test/e2e/clusterctl_upgrade.go b/test/e2e/clusterctl_upgrade.go index e801906dddf9..a2e1c103b08b 100644 --- a/test/e2e/clusterctl_upgrade.go +++ b/test/e2e/clusterctl_upgrade.go @@ -187,6 +187,7 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg clusterctlBinaryURLTemplate := input.InitWithBinary if clusterctlBinaryURLTemplate == "" { + Expect(input.E2EConfig.ResolveReleases(ctx)).To(Succeed(), "Failed to resolve release markers in e2e test config file") Expect(input.E2EConfig.Variables).To(HaveKey(initWithBinaryVariableName), "Invalid argument. %s variable must be defined when calling %s spec", initWithBinaryVariableName, specName) Expect(input.E2EConfig.Variables[initWithBinaryVariableName]).ToNot(BeEmpty(), "Invalid argument. %s variable can't be empty when calling %s spec", initWithBinaryVariableName, specName) clusterctlBinaryURLTemplate = input.E2EConfig.GetVariable(initWithBinaryVariableName) diff --git a/test/e2e/clusterctl_upgrade_test.go b/test/e2e/clusterctl_upgrade_test.go index 726f0a724d4c..4a51774b1bf6 100644 --- a/test/e2e/clusterctl_upgrade_test.go +++ b/test/e2e/clusterctl_upgrade_test.go @@ -20,11 +20,25 @@ limitations under the License. package e2e import ( + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "k8s.io/utils/ptr" ) +var ( + clusterctlDownloadURL = "https://github.com/kubernetes-sigs/cluster-api/releases/download/v%s/clusterctl-{OS}-{ARCH}" + providerCAPIPrefix = "cluster-api:v%s" + providerKubeadmPrefix = "kubeadm:v%s" + providerDockerPrefix = "docker:v%s" +) + var _ = Describe("When testing clusterctl upgrades (v1.0=>current)", func() { + // Get v1.0 latest stable release + version := "1.0" + stableRelease, err := GetStableReleaseOfMinor(ctx, version) + Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", version) ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput { return ClusterctlUpgradeSpecInput{ E2EConfig: e2eConfig, @@ -33,13 +47,13 @@ var _ = Describe("When testing clusterctl upgrades (v1.0=>current)", func() { ArtifactFolder: artifactFolder, SkipCleanup: skipCleanup, InfrastructureProvider: ptr.To("docker"), - InitWithBinary: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.5/clusterctl-{OS}-{ARCH}", + InitWithBinary: fmt.Sprintf(clusterctlDownloadURL, stableRelease), // We have to pin the providers because with `InitWithProvidersContract` the test would // use the latest version for the contract (which is v1.3.X for v1beta1). - InitWithCoreProvider: "cluster-api:v1.0.5", - InitWithBootstrapProviders: []string{"kubeadm:v1.0.5"}, - InitWithControlPlaneProviders: []string{"kubeadm:v1.0.5"}, - InitWithInfrastructureProviders: []string{"docker:v1.0.5"}, + InitWithCoreProvider: fmt.Sprintf(providerCAPIPrefix, stableRelease), + InitWithBootstrapProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithControlPlaneProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithInfrastructureProviders: []string{fmt.Sprintf(providerDockerPrefix, stableRelease)}, // We have to set this to an empty array as clusterctl v1.0 doesn't support // runtime extension providers. If we don't do this the test will automatically // try to deploy the latest version of our test-extension from docker.yaml. @@ -54,6 +68,10 @@ var _ = Describe("When testing clusterctl upgrades (v1.0=>current)", func() { }) var _ = Describe("When testing clusterctl upgrades (v1.4=>current)", func() { + // Get v1.4 latest stable release + version := "1.4" + stableRelease, err := GetStableReleaseOfMinor(ctx, version) + Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", version) ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput { return ClusterctlUpgradeSpecInput{ E2EConfig: e2eConfig, @@ -62,13 +80,13 @@ var _ = Describe("When testing clusterctl upgrades (v1.4=>current)", func() { ArtifactFolder: artifactFolder, SkipCleanup: skipCleanup, InfrastructureProvider: ptr.To("docker"), - InitWithBinary: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/clusterctl-{OS}-{ARCH}", + InitWithBinary: fmt.Sprintf(clusterctlDownloadURL, stableRelease), // We have to pin the providers because with `InitWithProvidersContract` the test would // use the latest version for the contract (which is v1.5.X for v1beta1). - InitWithCoreProvider: "cluster-api:v1.4.5", - InitWithBootstrapProviders: []string{"kubeadm:v1.4.5"}, - InitWithControlPlaneProviders: []string{"kubeadm:v1.4.5"}, - InitWithInfrastructureProviders: []string{"docker:v1.4.5"}, + InitWithCoreProvider: fmt.Sprintf(providerCAPIPrefix, stableRelease), + InitWithBootstrapProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithControlPlaneProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithInfrastructureProviders: []string{fmt.Sprintf(providerDockerPrefix, stableRelease)}, InitWithProvidersContract: "v1beta1", // NOTE: If this version is changed here the image and SHA must also be updated in all DockerMachineTemplates in `test/e2e/data/infrastructure-docker/v1.4/bases. InitWithKubernetesVersion: "v1.27.3", @@ -80,6 +98,10 @@ var _ = Describe("When testing clusterctl upgrades (v1.4=>current)", func() { }) var _ = Describe("When testing clusterctl upgrades using ClusterClass (v1.4=>current) [ClusterClass]", func() { + // Get v1.4 latest stable release + version := "1.4" + stableRelease, err := GetStableReleaseOfMinor(ctx, version) + Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", version) ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput { return ClusterctlUpgradeSpecInput{ E2EConfig: e2eConfig, @@ -88,13 +110,13 @@ var _ = Describe("When testing clusterctl upgrades using ClusterClass (v1.4=>cur ArtifactFolder: artifactFolder, SkipCleanup: skipCleanup, InfrastructureProvider: ptr.To("docker"), - InitWithBinary: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/clusterctl-{OS}-{ARCH}", + InitWithBinary: fmt.Sprintf(clusterctlDownloadURL, stableRelease), // We have to pin the providers because with `InitWithProvidersContract` the test would // use the latest version for the contract (which is v1.5.X for v1beta1). - InitWithCoreProvider: "cluster-api:v1.4.5", - InitWithBootstrapProviders: []string{"kubeadm:v1.4.5"}, - InitWithControlPlaneProviders: []string{"kubeadm:v1.4.5"}, - InitWithInfrastructureProviders: []string{"docker:v1.4.5"}, + InitWithCoreProvider: fmt.Sprintf(providerCAPIPrefix, stableRelease), + InitWithBootstrapProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithControlPlaneProviders: []string{fmt.Sprintf(providerKubeadmPrefix, stableRelease)}, + InitWithInfrastructureProviders: []string{fmt.Sprintf(providerDockerPrefix, stableRelease)}, InitWithProvidersContract: "v1beta1", // NOTE: If this version is changed here the image and SHA must also be updated in all DockerMachineTemplates in `test/e2e/data/infrastructure-docker/v1.4/bases. InitWithKubernetesVersion: "v1.27.3", @@ -107,6 +129,10 @@ var _ = Describe("When testing clusterctl upgrades using ClusterClass (v1.4=>cur var _ = Describe("When testing clusterctl upgrades (v1.5=>current)", func() { ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput { + // Get v1.5 latest stable release + version := "1.5" + stableRelease, err := GetStableReleaseOfMinor(ctx, version) + Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", version) return ClusterctlUpgradeSpecInput{ E2EConfig: e2eConfig, ClusterctlConfigPath: clusterctlConfigPath, @@ -114,7 +140,7 @@ var _ = Describe("When testing clusterctl upgrades (v1.5=>current)", func() { ArtifactFolder: artifactFolder, SkipCleanup: skipCleanup, InfrastructureProvider: ptr.To("docker"), - InitWithBinary: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/clusterctl-{OS}-{ARCH}", + InitWithBinary: fmt.Sprintf(clusterctlDownloadURL, stableRelease), InitWithProvidersContract: "v1beta1", // NOTE: If this version is changed here the image and SHA must also be updated in all DockerMachineTemplates in `test/e2e/data/infrastructure-docker/v1.5/bases. InitWithKubernetesVersion: "v1.28.0", @@ -126,6 +152,10 @@ var _ = Describe("When testing clusterctl upgrades (v1.5=>current)", func() { }) var _ = Describe("When testing clusterctl upgrades using ClusterClass (v1.5=>current) [ClusterClass]", func() { + // Get v1.5 latest stable release + version := "1.5" + stableRelease, err := GetStableReleaseOfMinor(ctx, version) + Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", version) ClusterctlUpgradeSpec(ctx, func() ClusterctlUpgradeSpecInput { return ClusterctlUpgradeSpecInput{ E2EConfig: e2eConfig, @@ -134,7 +164,7 @@ var _ = Describe("When testing clusterctl upgrades using ClusterClass (v1.5=>cur ArtifactFolder: artifactFolder, SkipCleanup: skipCleanup, InfrastructureProvider: ptr.To("docker"), - InitWithBinary: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/clusterctl-{OS}-{ARCH}", + InitWithBinary: fmt.Sprintf(clusterctlDownloadURL, stableRelease), InitWithProvidersContract: "v1beta1", // NOTE: If this version is changed here the image and SHA must also be updated in all DockerMachineTemplates in `test/e2e/data/infrastructure-docker/v1.5/bases. InitWithKubernetesVersion: "v1.28.0", diff --git a/test/e2e/common.go b/test/e2e/common.go index d52d81b4c502..2904700a1f79 100644 --- a/test/e2e/common.go +++ b/test/e2e/common.go @@ -31,6 +31,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/test/framework" + "sigs.k8s.io/cluster-api/test/framework/clusterctl" "sigs.k8s.io/cluster-api/util" ) @@ -49,6 +50,8 @@ const ( IPFamily = "IP_FAMILY" ) +var releaseMarkerPrefix = "go://sigs.k8s.io/cluster-api@v%s" + func Byf(format string, a ...interface{}) { By(fmt.Sprintf(format, a...)) } @@ -151,3 +154,9 @@ func (m *validVersionMatcher) FailureMessage(_ interface{}) (message string) { func (m *validVersionMatcher) NegatedFailureMessage(_ interface{}) (message string) { return fmt.Sprintf("Expected\n%s\n%s", m.version, " not to be a valid version ") } + +// GetStableReleaseOfMinor returns latest stable version of minorRelease. +func GetStableReleaseOfMinor(ctx context.Context, minorRelease string) (string, error) { + releaseMarker := fmt.Sprintf(releaseMarkerPrefix, minorRelease) + return clusterctl.ResolveRelease(ctx, releaseMarker) +} diff --git a/test/e2e/config/docker.yaml b/test/e2e/config/docker.yaml index c56efd998348..f900b1eb42e8 100644 --- a/test/e2e/config/docker.yaml +++ b/test/e2e/config/docker.yaml @@ -35,8 +35,8 @@ providers: - name: cluster-api type: CoreProvider versions: - - name: v1.0.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.5/core-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.0}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.0}/core-components.yaml" type: "url" contract: v1beta1 replacements: @@ -44,8 +44,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.0/metadata.yaml" - - name: v1.4.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/core-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.4}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.4}/core-components.yaml" type: "url" contract: v1beta1 replacements: @@ -53,8 +53,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.4/metadata.yaml" - - name: v1.5.0 # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/core-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.5}" # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.5}/core-components.yaml" type: "url" contract: v1beta1 replacements: @@ -73,8 +73,8 @@ providers: - name: kubeadm type: BootstrapProvider versions: - - name: v1.0.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.5/bootstrap-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.0}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.0}/bootstrap-components.yaml" type: "url" contract: v1beta1 replacements: @@ -82,8 +82,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.0/metadata.yaml" - - name: v1.4.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/bootstrap-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.4}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.4}/bootstrap-components.yaml" type: "url" contract: v1beta1 replacements: @@ -91,8 +91,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.4/metadata.yaml" - - name: v1.5.0 # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/bootstrap-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.5}" # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.5}/bootstrap-components.yaml" type: "url" contract: v1beta1 replacements: @@ -111,8 +111,8 @@ providers: - name: kubeadm type: ControlPlaneProvider versions: - - name: v1.0.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.5/control-plane-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.0}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.0}/control-plane-components.yaml" type: "url" contract: v1beta1 replacements: @@ -120,8 +120,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.0/metadata.yaml" - - name: v1.4.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/control-plane-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.4}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.4}/control-plane-components.yaml" type: "url" contract: v1beta1 replacements: @@ -129,8 +129,8 @@ providers: new: --metrics-addr=:8080 files: - sourcePath: "../data/shared/v1.4/metadata.yaml" - - name: v1.5.0 # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/control-plane-components.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.5}" # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.5}/control-plane-components.yaml" type: "url" contract: v1beta1 replacements: @@ -149,8 +149,8 @@ providers: - name: docker type: InfrastructureProvider versions: - - name: v1.0.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.5/infrastructure-components-development.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.0}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.0}/infrastructure-components-development.yaml" type: "url" contract: v1beta1 replacements: @@ -159,8 +159,8 @@ providers: files: - sourcePath: "../data/shared/v1.0/metadata.yaml" - sourcePath: "../data/infrastructure-docker/v1.0/cluster-template.yaml" - - name: v1.4.5 # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.4.5/infrastructure-components-development.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.4}" # supported release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.4}/infrastructure-components-development.yaml" type: "url" contract: v1beta1 replacements: @@ -171,8 +171,8 @@ providers: - sourcePath: "../data/infrastructure-docker/v1.4/cluster-template.yaml" - sourcePath: "../data/infrastructure-docker/v1.4/cluster-template-topology.yaml" - sourcePath: "../data/infrastructure-docker/v1.4/clusterclass-quick-start.yaml" - - name: v1.5.0 # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. - value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.5.0/infrastructure-components-development.yaml" + - name: "{go://sigs.k8s.io/cluster-api@v1.5}" # latest published release in the v1beta1 series; this is used for v1beta1 --> main clusterctl upgrades test only. + value: "https://github.com/kubernetes-sigs/cluster-api/releases/download/{go://sigs.k8s.io/cluster-api@v1.5}/infrastructure-components-development.yaml" type: "url" contract: v1beta1 replacements: diff --git a/test/framework/clusterctl/e2e_config.go b/test/framework/clusterctl/e2e_config.go index da7464b4da0b..ffd1b16f122e 100644 --- a/test/framework/clusterctl/e2e_config.go +++ b/test/framework/clusterctl/e2e_config.go @@ -29,6 +29,7 @@ import ( "strings" "time" + "github.com/blang/semver/v4" . "github.com/onsi/gomega" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/util/version" @@ -37,6 +38,7 @@ import ( clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" clusterctlconfig "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" + "sigs.k8s.io/cluster-api/internal/goproxy" "sigs.k8s.io/cluster-api/util" ) @@ -49,7 +51,7 @@ type LoadE2EConfigInput struct { } // LoadE2EConfig loads the configuration for the e2e test environment. -func LoadE2EConfig(_ context.Context, input LoadE2EConfigInput) *E2EConfig { +func LoadE2EConfig(ctx context.Context, input LoadE2EConfigInput) *E2EConfig { configData, err := os.ReadFile(input.ConfigPath) Expect(err).ToNot(HaveOccurred(), "Failed to read the e2e test config file") Expect(configData).ToNot(BeEmpty(), "The e2e test config file should not be empty") @@ -57,6 +59,7 @@ func LoadE2EConfig(_ context.Context, input LoadE2EConfigInput) *E2EConfig { config := &E2EConfig{} Expect(yaml.Unmarshal(configData, config)).To(Succeed(), "Failed to convert the e2e test config file to yaml") + Expect(config.ResolveReleases(ctx)).To(Succeed(), "Failed to resolve release markers in e2e test config file") config.Defaults() config.AbsPaths(filepath.Dir(input.ConfigPath)) @@ -245,6 +248,94 @@ type Files struct { TargetName string `json:"targetName,omitempty"` } +// ResolveReleases converts release markers to release version. +func (c *E2EConfig) ResolveReleases(ctx context.Context) error { + for i := range c.Providers { + provider := &c.Providers[i] + for j := range provider.Versions { + version := &provider.Versions[j] + if version.Type != URLSource { + continue + } + // Skipping versions that are not a resolvable marker. Resolvable markers are surrounded by `{}` + if !strings.HasPrefix(version.Name, "{") || !strings.HasSuffix(version.Name, "}") { + continue + } + releaseMarker := strings.TrimLeft(strings.TrimRight(version.Name, "}"), "{") + ver, err := ResolveRelease(ctx, releaseMarker) + if err != nil { + return errors.Wrapf(err, "failed resolving release url %q", version.Name) + } + ver = "v" + ver + version.Value = strings.Replace(version.Value, version.Name, ver, 1) + version.Name = ver + } + } + return nil +} + +func ResolveRelease(ctx context.Context, releaseMarker string) (string, error) { + scheme, host, err := goproxy.GetSchemeAndHost(os.Getenv("GOPROXY")) + if err != nil { + return "", err + } + if scheme == "" || host == "" { + return "", errors.Errorf("releasemarker does not support disabling the go proxy: GOPROXY=%q", os.Getenv("GOPROXY")) + } + goproxyClient := goproxy.NewClient(scheme, host) + return resolveReleaseMarker(ctx, releaseMarker, goproxyClient) +} + +// resolveReleaseMarker resolves releaseMarker string to verion string e.g. +// - Resolves "go://sigs.k8s.io/cluster-api@v1.0" to the latest stable patch release of v1.0. +// - Resolves "go://sigs.k8s.io/cluster-api@latest-v1.0" to the latest patch release of v.1.0 including rc and pre releases. +func resolveReleaseMarker(ctx context.Context, releaseMarker string, goproxyClient *goproxy.Client) (string, error) { + if !strings.HasPrefix(releaseMarker, "go://") { + return "", errors.Errorf("unknown release marker scheme") + } + + releaseMarker = strings.TrimPrefix(releaseMarker, "go://") + if releaseMarker == "" { + return "", errors.New("empty release url") + } + + gomoduleParts := strings.Split(releaseMarker, "@") + if len(gomoduleParts) < 2 { + return "", errors.Errorf("go module or version missing") + } + gomodule := gomoduleParts[0] + + includePrereleases := false + if strings.HasPrefix(gomoduleParts[1], "latest-") { + includePrereleases = true + } + version := strings.TrimPrefix(gomoduleParts[1], "latest-") + ".0" + version = strings.TrimPrefix(version, "v") + semVersion, err := semver.Parse(version) + if err != nil { + return "", errors.Wrapf(err, "parsing semver for %s", version) + } + + parsedTags, err := goproxyClient.GetVersions(ctx, gomodule) + if err != nil { + return "", err + } + + var picked semver.Version + for i, tag := range parsedTags { + if !includePrereleases && len(tag.Pre) > 0 { + continue + } + if tag.Major == semVersion.Major && tag.Minor == semVersion.Minor { + picked = parsedTags[i] + } + } + if picked.Major == 0 && picked.Minor == 0 && picked.Patch == 0 { + return "", errors.Errorf("no suitable release available for release marker %s", releaseMarker) + } + return picked.String(), nil +} + // Defaults assigns default values to the object. More specifically: // - ManagementClusterName gets a default name if empty. // - Providers version gets type KustomizeSource if not otherwise specified. diff --git a/test/framework/clusterctl/e2e_config_test.go b/test/framework/clusterctl/e2e_config_test.go new file mode 100644 index 000000000000..5b2586552ac9 --- /dev/null +++ b/test/framework/clusterctl/e2e_config_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clusterctl + +import ( + "context" + "fmt" + "net/http" + "testing" + + . "github.com/onsi/gomega" + + "sigs.k8s.io/cluster-api/internal/goproxy" + goproxytest "sigs.k8s.io/cluster-api/internal/goproxy/test" +) + +func Test_resolveReleaseMarker(t *testing.T) { + scheme, host, muxGoproxy, teardownGoproxy := goproxytest.NewFakeGoproxy() + clientGoproxy := goproxy.NewClient(scheme, host) + defer teardownGoproxy() + + // setup an handler with fake releases + muxGoproxy.HandleFunc("/github.com/o/r1/@v/list", func(w http.ResponseWriter, r *http.Request) { + goproxytest.HTTPTestMethod(t, r, "GET") + fmt.Fprint(w, "v1.2.0\n") + fmt.Fprint(w, "v1.2.1-rc.0\n") + fmt.Fprint(w, "v1.3.0-rc.0\n") + fmt.Fprint(w, "v1.3.0-rc.1\n") + }) + tests := []struct { + name string + releaseMarker string + want string + wantErr bool + }{ + { + name: "Invalid url", + releaseMarker: "github.com/o/doesntexist", + want: "", + wantErr: true, + }, + { + name: "Get stable release", + releaseMarker: "go://github.com/o/r1@v1.2", + want: "1.2.0", + wantErr: false, + }, + { + name: "Get latest release", + releaseMarker: "go://github.com/o/r1@latest-v1.2", + want: "1.2.1-rc.0", + wantErr: false, + }, + { + name: "Get stable release when there is no stable release in given minor", + releaseMarker: "go://github.com/o/r1@v1.3", + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + got, err := resolveReleaseMarker(context.Background(), tt.releaseMarker, clientGoproxy) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + return + } + g.Expect(err).ToNot(HaveOccurred()) + + g.Expect(got).To(BeEquivalentTo(tt.want)) + }) + } +}