From 221f17cd2293fa0d6e450b16fd2be13ac0f0ec09 Mon Sep 17 00:00:00 2001 From: Ricardo Maraschini Date: Wed, 6 Nov 2024 11:00:22 +0100 Subject: [PATCH] feat: return error if ec version is a pre-release if the version present in the embedded cluster config is a pre-release, the linter should return an error. --- pkg/ec/lint.go | 81 +++++++++++++++++++++++++++++++-------------- pkg/ec/lint_test.go | 44 +++++++++++++++++++++++- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/pkg/ec/lint.go b/pkg/ec/lint.go index ca3a783..33770ef 100644 --- a/pkg/ec/lint.go +++ b/pkg/ec/lint.go @@ -1,6 +1,7 @@ package ec import ( + "encoding/json" "fmt" "net/http" "os" @@ -11,11 +12,16 @@ import ( "gopkg.in/yaml.v2" ) -var ecVersions map[string]bool +var ecVersions map[string]EmbeddedClusterVersion var rwMutex sync.RWMutex +var githubAPIURL = "http://api.github.com" + +type EmbeddedClusterVersion struct { + PreRelease bool `json:"prerelease"` +} func init() { - ecVersions = make(map[string]bool) + ecVersions = make(map[string]EmbeddedClusterVersion) } func LintEmbeddedClusterVersion(specFiles domain.SpecFiles) ([]domain.LintExpression, error) { @@ -48,7 +54,7 @@ func LintEmbeddedClusterVersion(specFiles domain.SpecFiles) ([]domain.LintExpres lintExpressions = append(lintExpressions, ecVersionlintExpression) } else { // version is defined, check if it is valid. - exists, err := checkIfECVersionExists(version) + ecVersion, exists, err := checkIfECVersionExists(version) if err != nil { return nil, errors.Wrap(err, "failed to check if ec version exists") } @@ -60,6 +66,14 @@ func LintEmbeddedClusterVersion(specFiles domain.SpecFiles) ([]domain.LintExpres Message: "Embedded Cluster version not found", } lintExpressions = append(lintExpressions, ecVersionlintExpression) + } else if ecVersion.PreRelease { + ecVersionlintExpression := domain.LintExpression{ + Rule: "non-existent-ec-version", + Type: "error", + Path: spec.Path, + Message: "Embedded Cluster version is a pre-release", + } + lintExpressions = append(lintExpressions, ecVersionlintExpression) } } } @@ -68,33 +82,52 @@ func LintEmbeddedClusterVersion(specFiles domain.SpecFiles) ([]domain.LintExpres return lintExpressions, nil } -func checkIfECVersionExists(version string) (bool, error) { - url := "http://api.github.com/repos/replicatedhq/embedded-cluster/releases/tags/%s" +func checkIfECVersionExists(version string) (*EmbeddedClusterVersion, bool, error) { + url := githubAPIURL + "/repos/replicatedhq/embedded-cluster/releases/tags/%s" token := os.Getenv("GITHUB_API_TOKEN") var bearer = "Bearer " + token rwMutex.RLock() - verIsCached := ecVersions[version] + ecVersion, found := ecVersions[version] rwMutex.RUnlock() - if !verIsCached { - req, err := http.NewRequest("GET", fmt.Sprintf(url, version), nil) - if err != nil { - return false, errors.Wrap(err, "failed to create new request") - } - req.Header.Set("Authorization", bearer) - client := &http.Client{} - resp, _ := client.Do(req) - if resp.StatusCode == 404 { - return false, nil - } else if resp.StatusCode == 200 { - rwMutex.Lock() - ecVersions[version] = true - rwMutex.Unlock() - } else { - return false, errors.New(fmt.Sprintf("received non 200 status code (%d) from GitHub API request", resp.StatusCode)) - } + if found { + return &ecVersion, true, nil + } + + req, err := http.NewRequest("GET", fmt.Sprintf(url, version), nil) + if err != nil { + return nil, false, errors.Wrap(err, "failed to create new request") } + req.Header.Set("Authorization", bearer) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, false, errors.Wrap(err, "failed to make http request") + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, false, nil + } + + if resp.StatusCode != http.StatusOK { + return nil, false, errors.New(fmt.Sprintf("received non 200 status code (%d) from GitHub API request", resp.StatusCode)) + } + + var newVersion EmbeddedClusterVersion + if err := json.NewDecoder(resp.Body).Decode(&newVersion); err != nil { + return nil, false, errors.Wrap(err, "failed to decode embedded cluster version json") + } + + if newVersion.PreRelease { + return &newVersion, true, nil + } + + rwMutex.Lock() + ecVersions[version] = newVersion + rwMutex.Unlock() - return true, nil + return &newVersion, true, nil } diff --git a/pkg/ec/lint_test.go b/pkg/ec/lint_test.go index 07dbf5b..89227df 100644 --- a/pkg/ec/lint_test.go +++ b/pkg/ec/lint_test.go @@ -1,6 +1,8 @@ package ec import ( + "net/http" + "net/http/httptest" "testing" "github.com/replicatedhq/kots-lint/pkg/domain" @@ -14,6 +16,7 @@ func Test_LintEmbeddedClusterVersion(t *testing.T) { name string specFiles domain.SpecFiles expect []domain.LintExpression + apiResult []byte }{ { name: "valid version", @@ -26,7 +29,8 @@ spec: version: "v1.2.2+k8s-1.29"`, }, }, - expect: []domain.LintExpression{}, + expect: []domain.LintExpression{}, + apiResult: []byte(`{}`), }, { name: "invalid version", @@ -47,10 +51,48 @@ spec: }, }, }, + { + name: "pre-release version", + specFiles: domain.SpecFiles{ + { + Path: "", + Content: `apiVersion: embeddedcluster.replicated.com/v1beta1 +kind: Config +spec: + version: "pre-release-version"`, + }, + }, + expect: []domain.LintExpression{ + { + Rule: "non-existent-ec-version", + Type: "error", + Message: "Embedded Cluster version is a pre-release", + }, + }, + apiResult: []byte(`{"prerelease": true}`), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + oldURL := githubAPIURL + defer func() { githubAPIURL = oldURL }() + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if test.apiResult == nil { + w.WriteHeader(http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(test.apiResult) + }, + ), + ) + defer server.Close() + + githubAPIURL = server.URL actual, err := LintEmbeddedClusterVersion(test.specFiles) require.NoError(t, err) assert.ElementsMatch(t, actual, test.expect)