From 410bc1fc4e63e9fad537859ee111b293bf61e00d Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Fri, 26 Aug 2022 15:10:10 -0700 Subject: [PATCH 1/2] cli prints license warnings Signed-off-by: Spike Curtis --- cli/root.go | 83 +++++++++++++++++++-------------- enterprise/cli/licenses_test.go | 21 +++++++++ 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/cli/root.go b/cli/root.go index 7551e09c03c63..6094ddfe24d3d 100644 --- a/cli/root.go +++ b/cli/root.go @@ -35,18 +35,20 @@ var ( ) const ( - varURL = "url" - varToken = "token" - varAgentToken = "agent-token" - varAgentURL = "agent-url" - varGlobalConfig = "global-config" - varNoOpen = "no-open" - varNoVersionCheck = "no-version-warning" - varForceTty = "force-tty" - varVerbose = "verbose" - notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." - - envNoVersionCheck = "CODER_NO_VERSION_WARNING" + varURL = "url" + varToken = "token" + varAgentToken = "agent-token" + varAgentURL = "agent-url" + varGlobalConfig = "global-config" + varNoOpen = "no-open" + varNoVersionCheck = "no-version-warning" + varNoFeatureWarning = "no-feature-warning" + varForceTty = "force-tty" + varVerbose = "verbose" + notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." + + envNoVersionCheck = "CODER_NO_VERSION_WARNING" + envNoFeatureWarning = "CODER_NO_FEATURE_WARNING" ) var ( @@ -103,29 +105,25 @@ func Root(subcommands []*cobra.Command) *cobra.Command { Long: `Coder — A tool for provisioning self-hosted development environments. `, PersistentPreRun: func(cmd *cobra.Command, args []string) { - err := func() error { - if cliflag.IsSetBool(cmd, varNoVersionCheck) { - return nil - } - - // Login handles checking the versions itself since it - // has a handle to an unauthenticated client. - // Server is skipped for obvious reasons. - if cmd.Name() == "login" || cmd.Name() == "server" || cmd.Name() == "gitssh" { - return nil - } - - client, err := CreateClient(cmd) - // If the client is unauthenticated we can ignore the check. - // The child commands should handle an unauthenticated client. - if xerrors.Is(err, errUnauthenticated) { - return nil - } - if err != nil { - return xerrors.Errorf("create client: %w", err) - } - return checkVersions(cmd, client) - }() + if cliflag.IsSetBool(cmd, varNoVersionCheck) && + cliflag.IsSetBool(cmd, varNoFeatureWarning) { + return + } + + // Login handles checking the versions itself since it + // has a handle to an unauthenticated client. + // Server is skipped for obvious reasons. + if cmd.Name() == "login" || cmd.Name() == "server" || cmd.Name() == "gitssh" { + return + } + + client, err := CreateClient(cmd) + // If we are unable to create a client, presumably the subcommand will fail as well + // so we can bail out here. + if err != nil { + return + } + err = checkVersions(cmd, client) if err != nil { // Just log the error here. We never want to fail a command // due to a pre-run. @@ -133,6 +131,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command { cliui.Styles.Warn.Render("check versions error: %s"), err) _, _ = fmt.Fprintln(cmd.ErrOrStderr()) } + checkWarnings(cmd, client) }, Example: formatExamples( example{ @@ -152,6 +151,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command { cmd.PersistentFlags().String(varURL, "", "Specify the URL to your deployment.") cliflag.Bool(cmd.PersistentFlags(), varNoVersionCheck, "", envNoVersionCheck, false, "Suppress warning when client and server versions do not match.") + cliflag.Bool(cmd.PersistentFlags(), varNoFeatureWarning, "", envNoFeatureWarning, false, "Suppress warnings about unlicensed features.") cliflag.String(cmd.PersistentFlags(), varToken, "", envSessionToken, "", fmt.Sprintf("Specify an authentication token. For security reasons setting %s is preferred.", envSessionToken)) cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.") _ = cmd.PersistentFlags().MarkHidden(varAgentToken) @@ -493,3 +493,16 @@ download the server version with: 'curl -L https://coder.com/install.sh | sh -s return nil } + +func checkWarnings(cmd *cobra.Command, client *codersdk.Client) { + if cliflag.IsSetBool(cmd, varNoFeatureWarning) { + return + } + entitlements, err := client.Entitlements(cmd.Context()) + if err != nil { + return + } + for _, w := range entitlements.Warnings { + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Warn.Render(w)) + } +} diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index 92fa91da4a685..9d737e6bfd99c 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -20,6 +20,7 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/cli" "github.com/coder/coder/enterprise/coderd" @@ -28,6 +29,7 @@ import ( ) const fakeLicenseJWT = "test.jwt.sig" +const testWarning = "This is a test warning" func TestLicensesAddFake(t *testing.T) { t.Parallel() @@ -179,6 +181,8 @@ func TestLicensesListReal(t *testing.T) { "licenses", "list") stdout := new(bytes.Buffer) cmd.SetOut(stdout) + stderr := new(bytes.Buffer) + cmd.SetErr(stderr) clitest.SetupConfig(t, client, root) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -188,6 +192,7 @@ func TestLicensesListReal(t *testing.T) { }() require.NoError(t, <-errC) assert.Equal(t, "[]\n", stdout.String()) + assert.Contains(t, testWarning, stderr.String()) }) } @@ -260,6 +265,7 @@ func newFakeLicenseAPI(t *testing.T) http.Handler { r.Get("/api/v2/licenses", a.licenses) r.Get("/api/v2/buildinfo", a.noop) r.Delete("/api/v2/licenses/{id}", a.deleteLicense) + r.Get("/api/v2/entitlements", a.entitlements) return r } @@ -330,3 +336,18 @@ func (s *fakeLicenseAPI) deleteLicense(rw http.ResponseWriter, r *http.Request) assert.Equal(s.t, "55", chi.URLParam(r, "id")) rw.WriteHeader(200) } + +func (s *fakeLicenseAPI) entitlements(rw http.ResponseWriter, _ *http.Request) { + features := make(map[string]codersdk.Feature) + for _, f := range codersdk.FeatureNames { + features[f] = codersdk.Feature{ + Entitlement: codersdk.EntitlementEntitled, + Enabled: true, + } + } + httpapi.Write(rw, http.StatusOK, codersdk.Entitlements{ + Features: features, + Warnings: []string{testWarning}, + HasLicense: true, + }) +} From 8bfb916fea489a5b098c241e754ddcde38f79984 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Fri, 26 Aug 2022 16:36:06 -0700 Subject: [PATCH 2/2] Satisfy the linter Signed-off-by: Spike Curtis --- enterprise/cli/licenses_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index 9d737e6bfd99c..8a7f2076d56e6 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -337,7 +337,7 @@ func (s *fakeLicenseAPI) deleteLicense(rw http.ResponseWriter, r *http.Request) rw.WriteHeader(200) } -func (s *fakeLicenseAPI) entitlements(rw http.ResponseWriter, _ *http.Request) { +func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, _ *http.Request) { features := make(map[string]codersdk.Feature) for _, f := range codersdk.FeatureNames { features[f] = codersdk.Feature{