From e94fe3c21154c7a11e026a5e7aaaca108387f801 Mon Sep 17 00:00:00 2001 From: Harsha P Dixit Date: Wed, 15 Apr 2026 18:38:42 +0530 Subject: [PATCH] Revert "feat: Add github release update check (#1988)" This reverts commit 6528e6e4939c8ed101d1bca1b9cea47632d415cc. --- cmd/root.go | 13 +- cmd/update_check.go | 138 ----------------- cmd/update_check_test.go | 327 --------------------------------------- 3 files changed, 1 insertion(+), 477 deletions(-) delete mode 100644 cmd/update_check.go delete mode 100644 cmd/update_check_test.go diff --git a/cmd/root.go b/cmd/root.go index 87b9d5816..a3c1962d0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,8 +53,7 @@ configuration file. It can be used to export, import, or sync entities to Kong.`, SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - maybePrintUpdateNotice(cmd) + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { if _, err := url.ParseRequestURI(rootConfig.Address); err != nil { return fmt.Errorf("invalid URL: %w", err) } @@ -84,11 +83,6 @@ It can be used to export, import, or sync entities to Kong.`, viper.BindPFlag("analytics", rootCmd.PersistentFlags().Lookup("analytics")) - rootCmd.PersistentFlags().Bool("suppress-update-check", false, - "Disable checking GitHub for newer decK releases.\n"+ - "This value can also be set using DECK_SUPPRESS_UPDATE_CHECK "+ - "environment variable.") - // TODO: everything below are online flags to be moved to the "gateway" subcommand // moving them now would break to top-level commands (sync, diff, etc) we still // need for backward compatibility. @@ -272,11 +266,6 @@ It can be used to export, import, or sync entities to Kong.`, fileCmd.AddCommand(newKong2KicCmd()) fileCmd.AddCommand(newKong2TfCmd()) } - defaultHelpFunc := rootCmd.HelpFunc() - rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - maybePrintUpdateNotice(cmd) - defaultHelpFunc(cmd, args) - }) return rootCmd } diff --git a/cmd/update_check.go b/cmd/update_check.go deleted file mode 100644 index c49b7b553..000000000 --- a/cmd/update_check.go +++ /dev/null @@ -1,138 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "net/http" - "os" - "strconv" - "sync" - "time" - - "github.com/blang/semver/v4" - "github.com/fatih/color" - "github.com/spf13/cobra" -) - -const ( - updateCheckURL = "https://api.github.com/repos/kong/deck/releases/latest" - updateReleasePageURL = "https://github.com/kong/deck/releases" - updateCheckTimeout = 2 * time.Second -) - -type githubRelease struct { - TagName string `json:"tag_name"` -} - -var ( - updateNoticeOnce sync.Once - updateHTTPClient = &http.Client{Timeout: updateCheckTimeout} -) - -func maybePrintUpdateNotice(cmd *cobra.Command) { - if cmd == nil || suppressUpdateCheckEnabled(cmd) { - return - } - - updateNoticeOnce.Do(func() { - notice := buildUpdateNotice(VERSION) - if notice == "" { - return - } - fmt.Fprintln(cmd.ErrOrStderr(), notice) - }) -} - -func suppressUpdateCheckEnabled(cmd *cobra.Command) bool { - if cmd != nil { - if flag := cmd.Flags().Lookup("suppress-update-check"); flag != nil && flag.Changed { - value, err := cmd.Flags().GetBool("suppress-update-check") - if err == nil { - return value - } - } - - if flag := cmd.Flags().Lookup("json-output"); flag != nil { - value, err := cmd.Flags().GetBool("json-output") - if err == nil && value { - return true - } - } - } - - if value, ok := os.LookupEnv("DECK_SUPPRESS_UPDATE_CHECK"); ok { - enabled, err := strconv.ParseBool(value) - if err == nil && enabled { - return true - } - } - return false -} - -func buildUpdateNotice(localVersion string) string { - currentVersion, err := parseReleaseVersion(localVersion) - if err != nil { - return "" - } - - latestVersion, err := fetchLatestReleaseVersion(localVersion) - if err != nil { - return "" - } - - if !latestVersion.GT(currentVersion) { - return "" - } - - return formatUpdateNotice(currentVersion, latestVersion) -} - -func parseReleaseVersion(version string) (semver.Version, error) { - return semver.ParseTolerant(version) -} - -func fetchLatestReleaseVersion(localVersion string) (semver.Version, error) { - req, err := http.NewRequest(http.MethodGet, updateCheckURL, nil) - if err != nil { - return semver.Version{}, err - } - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", fmt.Sprintf("decK/%s", localVersion)) - - resp, err := updateHTTPClient.Do(req) - if err != nil { - return semver.Version{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return semver.Version{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } - - var release githubRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return semver.Version{}, err - } - - return parseReleaseVersion(release.TagName) -} - -func releaseNotesURL(version semver.Version) string { - return fmt.Sprintf("%s/tag/%s", updateReleasePageURL, displayVersion(version)) -} - -func displayVersion(version semver.Version) string { - return "v" + version.String() -} - -func formatUpdateNotice(currentVersion, latestVersion semver.Version) string { - header := color.New(color.FgYellow, color.Bold).Sprint("== Update available") - - return fmt.Sprintf( - "%s %s -> %s\nDownload & Release Notes: %s\n", - header, - displayVersion(currentVersion), - displayVersion(latestVersion), - releaseNotesURL(latestVersion), - ) -} diff --git a/cmd/update_check_test.go b/cmd/update_check_test.go deleted file mode 100644 index cb99b489d..000000000 --- a/cmd/update_check_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package cmd - -import ( - "bytes" - "io" - "net/http" - "strings" - "sync" - "testing" - - "github.com/blang/semver/v4" - "github.com/fatih/color" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type roundTripFunc func(*http.Request) (*http.Response, error) - -func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req) -} - -func resetUpdateCheckState() { - updateNoticeOnce = sync.Once{} - updateHTTPClient = &http.Client{Timeout: updateCheckTimeout} -} - -func setVersionForTest(t *testing.T) { - t.Helper() - previousVersion := VERSION - VERSION = "v1.2.3" - t.Cleanup(func() { - VERSION = previousVersion - }) -} - -func disableColorForTest(t *testing.T) { - t.Helper() - previousNoColor := color.NoColor - color.NoColor = true - t.Cleanup(func() { - color.NoColor = previousNoColor - }) -} - -func setRootAddressForTest(t *testing.T) { - t.Helper() - previousAddress := rootConfig.Address - rootConfig.Address = defaultKongURL - t.Cleanup(func() { - rootConfig.Address = previousAddress - }) -} - -func TestBuildUpdateNotice(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { - require.Equal(t, updateCheckURL, req.URL.String()) - require.Equal(t, "application/vnd.github+json", req.Header.Get("Accept")) - require.Equal(t, "decK/"+VERSION, req.Header.Get("User-Agent")) - - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v1.2.4"}`)), - Header: make(http.Header), - }, nil - }), - } - - notice := buildUpdateNotice("v1.2.3") - assert.Contains(t, notice, "== Update available v1.2.3 -> v1.2.4") - assert.Contains(t, notice, "Download & Release Notes: "+releaseNotesURL(mustParseVersion(t, "v1.2.4"))) - assert.True(t, strings.HasSuffix(notice, "\n")) -} - -func TestBuildUpdateNoticeSkipsInvalidOrNonNewerVersions(t *testing.T) { - tests := []struct { - name string - localVersion string - responseBody string - statusCode int - wantNotice string - }{ - { - name: "local version is not a release", - localVersion: "dev", - responseBody: `{"tag_name":"v1.2.4"}`, - statusCode: http.StatusOK, - }, - { - name: "same version", - localVersion: "v1.2.4", - responseBody: `{"tag_name":"v1.2.4"}`, - statusCode: http.StatusOK, - }, - { - name: "newer local version", - localVersion: "v1.2.5", - responseBody: `{"tag_name":"v1.2.4"}`, - statusCode: http.StatusOK, - }, - { - name: "remote tag missing", - localVersion: "v1.2.3", - responseBody: `{}`, - statusCode: http.StatusOK, - }, - { - name: "remote tag invalid", - localVersion: "v1.2.3", - responseBody: `{"tag_name":"latest"}`, - statusCode: http.StatusOK, - }, - { - name: "non-200 response", - localVersion: "v1.2.3", - responseBody: `{"tag_name":"v1.2.4"}`, - statusCode: http.StatusForbidden, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: tt.statusCode, - Body: io.NopCloser(strings.NewReader(tt.responseBody)), - Header: make(http.Header), - }, nil - }), - } - - notice := buildUpdateNotice(tt.localVersion) - assert.Equal(t, tt.wantNotice, notice) - }) - } -} - -func TestMaybePrintUpdateNoticePrintsOnceToStderr(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v1.2.4"}`)), - Header: make(http.Header), - }, nil - }), - } - - var stderr bytes.Buffer - cmd := NewRootCmd() - cmd.SetErr(&stderr) - - maybePrintUpdateNotice(cmd) - maybePrintUpdateNotice(cmd) - - output := stderr.String() - assert.Contains(t, output, "== Update available") - assert.Equal(t, 1, strings.Count(output, "== Update available")) - assert.Contains(t, output, "/tag/v1.2.4") - assert.Contains(t, output, "\n\n") -} - -func TestRootCommandSuppressesUpdateCheckWithFlag(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - disableColorForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v999.0.0"}`)), - Header: make(http.Header), - }, nil - }), - } - - cmd := NewRootCmd() - var stderr bytes.Buffer - cmd.SetErr(&stderr) - cmd.SetArgs([]string{"version", "--suppress-update-check"}) - - err := cmd.Execute() - require.NoError(t, err) - assert.NotContains(t, stderr.String(), "== Update available") -} - -func TestRootCommandSuppressesUpdateCheckWithEnvVar(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - t.Setenv("DECK_SUPPRESS_UPDATE_CHECK", "true") - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v999.0.0"}`)), - Header: make(http.Header), - }, nil - }), - } - - cmd := NewRootCmd() - var stderr bytes.Buffer - cmd.SetErr(&stderr) - cmd.SetArgs([]string{"version"}) - - err := cmd.Execute() - require.NoError(t, err) - assert.NotContains(t, stderr.String(), "== Update available") -} - -func TestRootCommandFlagFalseOverridesSuppressEnvVar(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - t.Setenv("DECK_SUPPRESS_UPDATE_CHECK", "true") - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v999.0.0"}`)), - Header: make(http.Header), - }, nil - }), - } - - cmd := NewRootCmd() - var stderr bytes.Buffer - cmd.SetErr(&stderr) - cmd.SetArgs([]string{"version", "--suppress-update-check=false"}) - - err := cmd.Execute() - require.NoError(t, err) - assert.Contains(t, stderr.String(), "== Update available") -} - -func TestRootCommandSuppressesUpdateCheckWithJSONOutput(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v999.0.0"}`)), - Header: make(http.Header), - }, nil - }), - } - - cmd := NewRootCmd() - var stderr bytes.Buffer - cmd.SetErr(&stderr) - cmd.SetArgs([]string{"gateway", "diff", "--json-output"}) - - err := cmd.Execute() - require.Error(t, err) - assert.NotContains(t, stderr.String(), "== Update available") -} - -func TestSuppressUpdateCheckEnabledFromEnvWithoutViperInitialization(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - t.Setenv("DECK_SUPPRESS_UPDATE_CHECK", "true") - - cmd := NewRootCmd() - - assert.True(t, suppressUpdateCheckEnabled(cmd)) -} - -func TestRootHelpShowsUpdateNoticeOnStderr(t *testing.T) { - t.Cleanup(resetUpdateCheckState) - setVersionForTest(t) - disableColorForTest(t) - setRootAddressForTest(t) - - updateHTTPClient = &http.Client{ - Transport: roundTripFunc(func(_ *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(`{"tag_name":"v999.0.0"}`)), - Header: make(http.Header), - }, nil - }), - } - - cmd := NewRootCmd() - var stdout bytes.Buffer - var stderr bytes.Buffer - cmd.SetOut(&stdout) - cmd.SetErr(&stderr) - cmd.SetArgs([]string{"--help"}) - - err := cmd.Execute() - require.NoError(t, err) - assert.Contains(t, stdout.String(), "The deck tool helps you manage Kong clusters") - assert.Contains(t, stderr.String(), "== Update available") - assert.Contains(t, stderr.String(), "/tag/v999.0.0") - assert.True(t, strings.HasSuffix(stderr.String(), "\n\n")) -} - -func mustParseVersion(t *testing.T, version string) semver.Version { - t.Helper() - parsed, err := parseReleaseVersion(version) - require.NoError(t, err) - return parsed -}