diff --git a/coder-sdk/env.go b/coder-sdk/env.go index 2eced5f1..a95ed4bf 100644 --- a/coder-sdk/env.go +++ b/coder-sdk/env.go @@ -39,10 +39,6 @@ type RebuildMessage struct { Text string `json:"text"` Required bool `json:"required"` AutoOffThreshold xjson.MSDuration `json:"auto_off_threshold" tab:"-"` - RebuildMessages []struct { - Text string `json:"text"` - Required bool `json:"required"` - } `json:"rebuild_messages" tab:"-"` } // EnvironmentStat represents the state of an environment diff --git a/docs/coder_sh.md b/docs/coder_sh.md index 8bd80ac6..72270e8f 100644 --- a/docs/coder_sh.md +++ b/docs/coder_sh.md @@ -14,6 +14,7 @@ coder sh [environment_name] [] [flags] ``` coder sh backend-env +coder sh front-end-dev cat ~/config.json ``` ### Options diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index d00796cd..c629beb3 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -21,24 +21,24 @@ func Make() *cobra.Command { } app.AddCommand( - makeLoginCmd(), - makeLogoutCmd(), - makeShellCmd(), - makeUsersCmd(), - makeConfigSSHCmd(), - makeSecretsCmd(), - envsCommand(), - makeSyncCmd(), - makeURLCmd(), + loginCmd(), + logoutCmd(), + shCmd(), + usersCmd(), + configSSHCmd(), + secretsCmd(), + envsCmd(), + syncCmd(), + urlCmd(), resourceCmd(), - completionCmd, - genDocs(app), + completionCmd(), + genDocsCmd(app), ) app.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show verbose output") return app } -func genDocs(rootCmd *cobra.Command) *cobra.Command { +func genDocsCmd(rootCmd *cobra.Command) *cobra.Command { return &cobra.Command{ Use: "gen-docs [dir_path]", Short: "Generate a markdown documentation tree for the root command.", @@ -52,17 +52,18 @@ func genDocs(rootCmd *cobra.Command) *cobra.Command { } // reference: https://github.com/spf13/cobra/blob/master/shell_completions.md -var completionCmd = &cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", - Example: `coder completion fish > ~/.config/fish/completions/coder.fish +func completionCmd() *cobra.Command { + return &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Example: `coder completion fish > ~/.config/fish/completions/coder.fish coder completion zsh > "${fpath[1]}/_coder" Linux: $ coder completion bash > /etc/bash_completion.d/coder MacOS: $ coder completion bash > /usr/local/etc/bash_completion.d/coder`, - Long: `To load completions: + Long: `To load completions: Bash: @@ -93,19 +94,20 @@ $ coder completion fish | source To load completions for each session, execute once: $ coder completion fish > ~/.config/fish/completions/coder.fish `, - DisableFlagsInUseLine: true, - ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { - switch args[0] { - case "bash": - _ = cmd.Root().GenBashCompletion(os.Stdout) // Best effort. - case "zsh": - _ = cmd.Root().GenZshCompletion(os.Stdout) // Best effort. - case "fish": - _ = cmd.Root().GenFishCompletion(os.Stdout, true) // Best effort. - case "powershell": - _ = cmd.Root().GenPowerShellCompletion(os.Stdout) // Best effort. - } - }, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + _ = cmd.Root().GenBashCompletion(os.Stdout) // Best effort. + case "zsh": + _ = cmd.Root().GenZshCompletion(os.Stdout) // Best effort. + case "fish": + _ = cmd.Root().GenFishCompletion(os.Stdout, true) // Best effort. + case "powershell": + _ = cmd.Root().GenPowerShellCompletion(os.Stdout) // Best effort. + } + }, + } } diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index db76b71e..38d20fdf 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -18,7 +18,7 @@ import ( "golang.org/x/xerrors" ) -func makeConfigSSHCmd() *cobra.Command { +func configSSHCmd() *cobra.Command { var ( configpath string remove = false diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 67c6b50e..1ddf9309 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -17,7 +17,7 @@ import ( const defaultImgTag = "latest" -func envsCommand() *cobra.Command { +func envsCmd() *cobra.Command { var user string cmd := &cobra.Command{ Use: "envs", @@ -28,12 +28,12 @@ func envsCommand() *cobra.Command { cmd.AddCommand( lsEnvsCommand(&user), - stopEnvsCommand(&user), - rmEnvsCommand(&user), + stopEnvsCmd(&user), + rmEnvsCmd(&user), watchBuildLogCommand(&user), rebuildEnvCommand(&user), - createEnvCommand(&user), - editEnvCommand(&user), + createEnvCmd(&user), + editEnvCmd(&user), ) return cmd } @@ -84,7 +84,7 @@ func lsEnvsCommand(user *string) *cobra.Command { return cmd } -func stopEnvsCommand(user *string) *cobra.Command { +func stopEnvsCmd(user *string) *cobra.Command { return &cobra.Command{ Use: "stop [...environment_names]", Short: "stop Coder environments by name", @@ -131,7 +131,7 @@ coder envs --user charlie@coder.com ls -o json \ } } -func createEnvCommand(user *string) *cobra.Command { +func createEnvCmd(user *string) *cobra.Command { var ( org string img string @@ -239,7 +239,7 @@ coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955 return cmd } -func editEnvCommand(user *string) *cobra.Command { +func editEnvCmd(user *string) *cobra.Command { var ( org string img string @@ -336,7 +336,7 @@ coder envs edit back-end-env --disk 20`, return cmd } -func rmEnvsCommand(user *string) *cobra.Command { +func rmEnvsCmd(user *string) *cobra.Command { var force bool cmd := &cobra.Command{ Use: "rm [...environment_names]", diff --git a/internal/cmd/login.go b/internal/cmd/login.go index 2984822b..d6a8db11 100644 --- a/internal/cmd/login.go +++ b/internal/cmd/login.go @@ -18,7 +18,7 @@ import ( "golang.org/x/xerrors" ) -func makeLoginCmd() *cobra.Command { +func loginCmd() *cobra.Command { return &cobra.Command{ Use: "login [Coder Enterprise URL eg. https://my.coder.domain/]", Short: "Authenticate this client for future operations", diff --git a/internal/cmd/logout.go b/internal/cmd/logout.go index b653d68f..dde3d758 100644 --- a/internal/cmd/logout.go +++ b/internal/cmd/logout.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" ) -func makeLogoutCmd() *cobra.Command { +func logoutCmd() *cobra.Command { return &cobra.Command{ Use: "logout", Short: "Remove local authentication credentials if any exist", diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go index 879d6bde..baa8ad25 100644 --- a/internal/cmd/secrets.go +++ b/internal/cmd/secrets.go @@ -14,7 +14,7 @@ import ( "cdr.dev/coder-cli/internal/x/xtabwriter" ) -func makeSecretsCmd() *cobra.Command { +func secretsCmd() *cobra.Command { var user string cmd := &cobra.Command{ Use: "secrets", @@ -26,28 +26,28 @@ func makeSecretsCmd() *cobra.Command { &cobra.Command{ Use: "ls", Short: "List all secrets owned by the active user", - RunE: listSecrets(&user), + RunE: listSecretsCmd(&user), }, - makeCreateSecret(&user), + createSecretCmd(&user), &cobra.Command{ Use: "rm [...secret_name]", Short: "Remove one or more secrets by name", Args: cobra.MinimumNArgs(1), - RunE: makeRemoveSecrets(&user), + RunE: removeSecretsCmd(&user), Example: "coder secrets rm mysql-password mysql-user", }, &cobra.Command{ Use: "view [secret_name]", Short: "View a secret by name", Args: cobra.ExactArgs(1), - RunE: makeViewSecret(&user), + RunE: viewSecretCmd(&user), Example: "coder secrets view mysql-password", }, ) return cmd } -func makeCreateSecret(userEmail *string) *cobra.Command { +func createSecretCmd(userEmail *string) *cobra.Command { var ( fromFile string fromLiteral string @@ -136,7 +136,7 @@ coder secrets create aws-credentials --from-file ./credentials.json`, return cmd } -func listSecrets(userEmail *string) func(cmd *cobra.Command, _ []string) error { +func listSecretsCmd(userEmail *string) func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error { client, err := newClient() if err != nil { @@ -169,7 +169,7 @@ func listSecrets(userEmail *string) func(cmd *cobra.Command, _ []string) error { } } -func makeViewSecret(userEmail *string) func(cmd *cobra.Command, args []string) error { +func viewSecretCmd(userEmail *string) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { var ( name = args[0] @@ -196,7 +196,7 @@ func makeViewSecret(userEmail *string) func(cmd *cobra.Command, args []string) e } } -func makeRemoveSecrets(userEmail *string) func(c *cobra.Command, args []string) error { +func removeSecretsCmd(userEmail *string) func(c *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { client, err := newClient() if err != nil { diff --git a/internal/cmd/shell.go b/internal/cmd/shell.go index 18197570..9d4b9b10 100644 --- a/internal/cmd/shell.go +++ b/internal/cmd/shell.go @@ -23,11 +23,12 @@ import ( func getEnvsForCompletion(user string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ctx := cmd.Context() client, err := newClient() if err != nil { return nil, cobra.ShellCompDirectiveDefault } - envs, err := getEnvs(context.TODO(), client, user) + envs, err := getEnvs(ctx, client, user) if err != nil { return nil, cobra.ShellCompDirectiveDefault } @@ -40,7 +41,7 @@ func getEnvsForCompletion(user string) func(cmd *cobra.Command, args []string, t } } -func makeShellCmd() *cobra.Command { +func shCmd() *cobra.Command { return &cobra.Command{ Use: "sh [environment_name] []", Short: "Open a shell and execute commands in a Coder environment", @@ -49,13 +50,13 @@ func makeShellCmd() *cobra.Command { DisableFlagParsing: true, ValidArgsFunction: getEnvsForCompletion(coder.Me), RunE: shell, - Example: "coder sh backend-env", + Example: `coder sh backend-env +coder sh front-end-dev cat ~/config.json`, } } -func shell(_ *cobra.Command, cmdArgs []string) error { - ctx := context.Background() - +func shell(cmd *cobra.Command, cmdArgs []string) error { + ctx := cmd.Context() command := "sh" args := []string{"-c"} if len(cmdArgs) > 1 { @@ -106,6 +107,18 @@ func runCommand(ctx context.Context, envName, command string, args []string) err return xerrors.Errorf("find environment: %w", err) } + // check if a rebuild is required before attempting to open a shell + for _, r := range env.RebuildMessages { + // use the first rebuild message that is required + if r.Required { + return clog.Error( + fmt.Sprintf(`environment "%s" requires a rebuild`, env.Name), + clog.Causef(r.Text), clog.BlankLine, + clog.Tipf(`run "coder envs rebuild %s" to rebuild`, env.Name), + ) + } + } + termFD := os.Stdout.Fd() isInteractive := terminal.IsTerminal(int(termFD)) diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index 4ff3f4ec..7247d883 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -15,7 +15,7 @@ import ( "golang.org/x/xerrors" ) -func makeSyncCmd() *cobra.Command { +func syncCmd() *cobra.Command { var init bool cmd := &cobra.Command{ Use: "sync [local directory] [:]", diff --git a/internal/cmd/urls.go b/internal/cmd/urls.go index c91427da..1faa1328 100644 --- a/internal/cmd/urls.go +++ b/internal/cmd/urls.go @@ -18,7 +18,7 @@ import ( "cdr.dev/coder-cli/internal/x/xtabwriter" ) -func makeURLCmd() *cobra.Command { +func urlCmd() *cobra.Command { var outputFmt string cmd := &cobra.Command{ Use: "urls", @@ -29,7 +29,7 @@ func makeURLCmd() *cobra.Command { Short: "List all DevURLs for an environment", Args: cobra.ExactArgs(1), ValidArgsFunction: getEnvsForCompletion(coder.Me), - RunE: makeListDevURLs(&outputFmt), + RunE: listDevURLsCmd(&outputFmt), } lsCmd.Flags().StringVarP(&outputFmt, "output", "o", "human", "human|json") @@ -43,7 +43,7 @@ func makeURLCmd() *cobra.Command { cmd.AddCommand( lsCmd, rmCmd, - makeCreateDevURL(), + createDevURLCmd(), ) return cmd @@ -89,7 +89,7 @@ func accessLevelIsValid(level string) bool { // Run gets the list of active devURLs from the cemanager for the // specified environment and outputs info to stdout. -func makeListDevURLs(outputFmt *string) func(cmd *cobra.Command, args []string) error { +func listDevURLsCmd(outputFmt *string) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { envName := args[0] devURLs, err := urlList(cmd.Context(), envName) @@ -120,7 +120,7 @@ func makeListDevURLs(outputFmt *string) func(cmd *cobra.Command, args []string) } } -func makeCreateDevURL() *cobra.Command { +func createDevURLCmd() *cobra.Command { var ( access string urlname string diff --git a/internal/cmd/users.go b/internal/cmd/users.go index b74b1758..15dfbf78 100644 --- a/internal/cmd/users.go +++ b/internal/cmd/users.go @@ -10,7 +10,7 @@ import ( "cdr.dev/coder-cli/internal/x/xtabwriter" ) -func makeUsersCmd() *cobra.Command { +func usersCmd() *cobra.Command { cmd := &cobra.Command{ Use: "users", Short: "Interact with Coder user accounts",