diff --git a/ci/README.md b/ci/README.md index ae2370a1..1daee639 100644 --- a/ci/README.md +++ b/ci/README.md @@ -1,5 +1,13 @@ # ci +## checks + +- `steps/build.sh` builds release assets with the appropriate tag injected. Required to pass for merging. +- `steps/fmt.sh` formats all Go source files. +- `steps/gendocs.sh` generates CLI documentation into `/docs` from the command specifications. +- `steps/lint.sh` lints all Go source files based on the rules set fourth in `/.golangci.yml`. + + ## integration tests ### `tcli` @@ -8,7 +16,7 @@ Package `tcli` provides a framework for writing end-to-end CLI tests. Each test group can have its own container for executing commands in a consistent and isolated filesystem. -### prerequisites +### running Assign the following environment variables to run the integration tests against an existing Enterprise deployment instance. @@ -22,5 +30,5 @@ export CODER_PASSWORD=... Then, simply run the test command from the project root ```sh -go test -v ./ci/integration +./ci/steps/integration.sh ``` diff --git a/ci/integration/integration_test.go b/ci/integration/integration_test.go index 52527839..8475de4f 100644 --- a/ci/integration/integration_test.go +++ b/ci/integration/integration_test.go @@ -14,7 +14,7 @@ func run(t *testing.T, container string, execute func(t *testing.T, ctx context. t.Run(container, func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index f514ba89..1860298d 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -19,8 +19,7 @@ var errNeedLogin = clog.Fatal( clog.Hintf(`did you run "coder login [https://coder.domain.com]"?`), ) -func newClient() (*coder.Client, error) { - ctx := context.Background() +func newClient(ctx context.Context) (*coder.Client, error) { sessionToken, err := config.Session.Read() if err != nil { return nil, errNeedLogin diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 38d20fdf..afa6b6b4 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -49,9 +49,7 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st endToken := "# ------------END-CODER-ENTERPRISE------------" return func(cmd *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - + ctx := cmd.Context() usr, err := user.Current() if err != nil { return xerrors.Errorf("get user home directory: %w", err) @@ -88,22 +86,21 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st return nil } - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } - sshAvailable := isSSHAvailable(ctx) - if !sshAvailable { + if !isSSHAvailable(ctx) { return xerrors.New("SSH is disabled or not available for your Coder Enterprise deployment.") } - user, err := client.Me(cmd.Context()) + user, err := client.Me(ctx) if err != nil { return xerrors.Errorf("fetch username: %w", err) } - envs, err := getEnvs(cmd.Context(), client, coder.Me) + envs, err := getEnvs(ctx, client, coder.Me) if err != nil { return err } diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 1ddf9309..8805e208 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -46,11 +46,12 @@ func lsEnvsCommand(user *string) *cobra.Command { Short: "list all environments owned by the active user", Long: "List all Coder environments owned by the active user.", RunE: func(cmd *cobra.Command, args []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return err } - envs, err := getEnvs(cmd.Context(), client, *user) + envs, err := getEnvs(ctx, client, *user) if err != nil { return err } @@ -101,7 +102,8 @@ coder envs --user charlie@coder.com ls -o json \ | xargs coder envs --user charlie@coder.com stop`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return xerrors.Errorf("new client: %w", err) } @@ -110,12 +112,12 @@ coder envs --user charlie@coder.com ls -o json \ for _, envName := range args { envName := envName egroup.Go(func() error { - env, err := findEnv(cmd.Context(), client, envName, *user) + env, err := findEnv(ctx, client, envName, *user) if err != nil { return err } - if err = client.StopEnvironment(cmd.Context(), env.ID); err != nil { + if err = client.StopEnvironment(ctx, env.ID); err != nil { return clog.Error(fmt.Sprintf("stop environment %q", env.Name), clog.Causef(err.Error()), clog.BlankLine, clog.Hintf("current environment status is %q", env.LatestStat.ContainerStatus), @@ -152,16 +154,17 @@ coder envs create --image 5f443b16-30652892427b955601330fa5 my-env-name # create a new environment using custom resource amounts coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955601330fa5 my-env-name`, RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() if img == "" { return xerrors.New("image unset") } - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } - multiOrgMember, err := isMultiOrgMember(cmd.Context(), client, *user) + multiOrgMember, err := isMultiOrgMember(ctx, client, *user) if err != nil { return err } @@ -170,7 +173,7 @@ coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955 return xerrors.New("org is required for multi-org members") } - importedImg, err := findImg(cmd.Context(), + importedImg, err := findImg(ctx, findImgConf{ client: client, email: *user, @@ -207,14 +210,14 @@ coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955 createReq.DiskGB = importedImg.DefaultDiskGB } - env, err := client.CreateEnvironment(cmd.Context(), importedImg.OrganizationID, *createReq) + env, err := client.CreateEnvironment(ctx, importedImg.OrganizationID, *createReq) if err != nil { return xerrors.Errorf("create environment: %w", err) } if follow { clog.LogSuccess("creating environment...") - if err := trailBuildLogs(cmd.Context(), client, env.ID); err != nil { + if err := trailBuildLogs(ctx, client, env.ID); err != nil { return err } return nil @@ -261,19 +264,20 @@ func editEnvCmd(user *string) *cobra.Command { coder envs edit back-end-env --disk 20`, RunE: func(cmd *cobra.Command, args []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return err } envName := args[0] - env, err := findEnv(cmd.Context(), client, envName, *user) + env, err := findEnv(ctx, client, envName, *user) if err != nil { return err } - multiOrgMember, err := isMultiOrgMember(cmd.Context(), client, *user) + multiOrgMember, err := isMultiOrgMember(ctx, client, *user) if err != nil { return err } @@ -288,7 +292,7 @@ coder envs edit back-end-env --disk 20`, diskGB, _ = cmd.Flags().GetInt("disk") gpus, _ = cmd.Flags().GetInt("gpus") - req, err := buildUpdateReq(cmd.Context(), + req, err := buildUpdateReq(ctx, updateConf{ cpu: cpuCores, memGB: memGB, @@ -306,13 +310,13 @@ coder envs edit back-end-env --disk 20`, return err } - if err := client.EditEnvironment(cmd.Context(), env.ID, *req); err != nil { + if err := client.EditEnvironment(ctx, env.ID, *req); err != nil { return xerrors.Errorf("failed to apply changes to environment %q: %w", envName, err) } if follow { clog.LogSuccess("applied changes to the environment, rebuilding...") - if err := trailBuildLogs(cmd.Context(), client, env.ID); err != nil { + if err := trailBuildLogs(ctx, client, env.ID); err != nil { return err } return nil @@ -344,7 +348,7 @@ func rmEnvsCmd(user *string) *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } @@ -369,7 +373,7 @@ func rmEnvsCmd(user *string) *cobra.Command { if err != nil { return err } - if err = client.DeleteEnvironment(cmd.Context(), env.ID); err != nil { + if err = client.DeleteEnvironment(ctx, env.ID); err != nil { return clog.Error( fmt.Sprintf(`failed to delete environment "%s"`, env.Name), clog.Causef(err.Error()), diff --git a/internal/cmd/rebuild.go b/internal/cmd/rebuild.go index 958af18f..687e132c 100644 --- a/internal/cmd/rebuild.go +++ b/internal/cmd/rebuild.go @@ -28,7 +28,7 @@ func rebuildEnvCommand(user *string) *cobra.Command { coder envs rebuild backend-env --force`, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } @@ -144,7 +144,7 @@ func watchBuildLogCommand(user *string) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } diff --git a/internal/cmd/resourcemanager.go b/internal/cmd/resourcemanager.go index 515318fc..1fbc210d 100644 --- a/internal/cmd/resourcemanager.go +++ b/internal/cmd/resourcemanager.go @@ -55,7 +55,7 @@ coder resources top --sort-by memory --show-empty`, func runResourceTop(options *resourceTopOptions) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go index baa8ad25..7c8dfdf6 100644 --- a/internal/cmd/secrets.go +++ b/internal/cmd/secrets.go @@ -82,8 +82,9 @@ coder secrets create aws-credentials --from-file ./credentials.json`, name = args[0] value string err error + ctx = cmd.Context() ) - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } @@ -112,11 +113,11 @@ coder secrets create aws-credentials --from-file ./credentials.json`, } } - user, err := client.UserByEmail(cmd.Context(), *userEmail) + user, err := client.UserByEmail(ctx, *userEmail) if err != nil { return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } - err = client.InsertSecret(cmd.Context(), user, coder.InsertSecretReq{ + err = client.InsertSecret(ctx, user, coder.InsertSecretReq{ Name: name, Value: value, Description: description, @@ -138,16 +139,17 @@ coder secrets create aws-credentials --from-file ./credentials.json`, func listSecretsCmd(userEmail *string) func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return err } - user, err := client.UserByEmail(cmd.Context(), *userEmail) + user, err := client.UserByEmail(ctx, *userEmail) if err != nil { return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } - secrets, err := client.Secrets(cmd.Context(), user.ID) + secrets, err := client.Secrets(ctx, user.ID) if err != nil { return xerrors.Errorf("get secrets: %w", err) } @@ -173,17 +175,18 @@ func viewSecretCmd(userEmail *string) func(cmd *cobra.Command, args []string) er return func(cmd *cobra.Command, args []string) error { var ( name = args[0] + ctx = cmd.Context() ) - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } - user, err := client.UserByEmail(cmd.Context(), *userEmail) + user, err := client.UserByEmail(ctx, *userEmail) if err != nil { return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } - secret, err := client.SecretWithValueByName(cmd.Context(), name, user.ID) + secret, err := client.SecretWithValueByName(ctx, name, user.ID) if err != nil { return xerrors.Errorf("get secret by name: %w", err) } @@ -198,18 +201,19 @@ func viewSecretCmd(userEmail *string) func(cmd *cobra.Command, args []string) er func removeSecretsCmd(userEmail *string) func(c *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return err } - user, err := client.UserByEmail(cmd.Context(), *userEmail) + user, err := client.UserByEmail(ctx, *userEmail) if err != nil { return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } errorSeen := false for _, n := range args { - err := client.DeleteSecretByName(cmd.Context(), n, user.ID) + err := client.DeleteSecretByName(ctx, n, user.ID) if err != nil { clog.Log(clog.Error( fmt.Sprintf("failed to delete secret %q: %v", n, err), diff --git a/internal/cmd/shell.go b/internal/cmd/shell.go index 9d4b9b10..7a79a2b7 100644 --- a/internal/cmd/shell.go +++ b/internal/cmd/shell.go @@ -24,7 +24,7 @@ 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() + client, err := newClient(ctx) if err != nil { return nil, cobra.ShellCompDirectiveDefault } @@ -98,7 +98,7 @@ func sendResizeEvents(ctx context.Context, termFD uintptr, process wsep.Process) } func runCommand(ctx context.Context, envName, command string, args []string) error { - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index 7247d883..9f162b1b 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -52,7 +52,7 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error { remote = args[1] ) - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } @@ -66,7 +66,7 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error { remoteDir = remoteTokens[1] ) - env, err := findEnv(cmd.Context(), client, envName, coder.Me) + env, err := findEnv(ctx, client, envName, coder.Me) if err != nil { return err } diff --git a/internal/cmd/urls.go b/internal/cmd/urls.go index 1faa1328..fa4ebd0f 100644 --- a/internal/cmd/urls.go +++ b/internal/cmd/urls.go @@ -91,8 +91,9 @@ func accessLevelIsValid(level string) bool { // specified environment and outputs info to stdout. func listDevURLsCmd(outputFmt *string) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() envName := args[0] - devURLs, err := urlList(cmd.Context(), envName) + devURLs, err := urlList(ctx, envName) if err != nil { return err } @@ -135,6 +136,7 @@ func createDevURLCmd() *cobra.Command { var ( envName = args[0] port = args[1] + ctx = cmd.Context() ) portNum, err := validatePort(port) @@ -150,17 +152,17 @@ func createDevURLCmd() *cobra.Command { if urlname != "" && !devURLNameValidRx.MatchString(urlname) { return xerrors.New("update devurl: name must be < 64 chars in length, begin with a letter and only contain letters or digits.") } - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } - env, err := findEnv(cmd.Context(), client, envName, coder.Me) + env, err := findEnv(ctx, client, envName, coder.Me) if err != nil { return err } - urls, err := urlList(cmd.Context(), envName) + urls, err := urlList(ctx, envName) if err != nil { return err } @@ -168,13 +170,13 @@ func createDevURLCmd() *cobra.Command { urlID, found := devURLID(portNum, urls) if found { clog.LogInfo(fmt.Sprintf("updating devurl for port %v", port)) - err := client.UpdateDevURL(cmd.Context(), env.ID, urlID, portNum, urlname, access) + err := client.UpdateDevURL(ctx, env.ID, urlID, portNum, urlname, access) if err != nil { return xerrors.Errorf("update DevURL: %w", err) } } else { clog.LogInfo(fmt.Sprintf("Adding devurl for port %v", port)) - err := client.InsertDevURL(cmd.Context(), env.ID, portNum, urlname, access) + err := client.InsertDevURL(ctx, env.ID, portNum, urlname, access) if err != nil { return xerrors.Errorf("insert DevURL: %w", err) } @@ -212,6 +214,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { var ( envName = args[0] port = args[1] + ctx = cmd.Context() ) portNum, err := validatePort(port) @@ -219,16 +222,16 @@ func removeDevURL(cmd *cobra.Command, args []string) error { return xerrors.Errorf("validate port: %w", err) } - client, err := newClient() + client, err := newClient(ctx) if err != nil { return err } - env, err := findEnv(cmd.Context(), client, envName, coder.Me) + env, err := findEnv(ctx, client, envName, coder.Me) if err != nil { return err } - urls, err := urlList(cmd.Context(), envName) + urls, err := urlList(ctx, envName) if err != nil { return err } @@ -240,7 +243,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { return xerrors.Errorf("No devurl found for port %v", port) } - if err := client.DelDevURL(cmd.Context(), env.ID, urlID); err != nil { + if err := client.DelDevURL(ctx, env.ID, urlID); err != nil { return xerrors.Errorf("delete DevURL: %w", err) } return nil @@ -248,7 +251,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { // urlList returns the list of active devURLs from the cemanager. func urlList(ctx context.Context, envName string) ([]DevURL, error) { - client, err := newClient() + client, err := newClient(ctx) if err != nil { return nil, err } diff --git a/internal/cmd/users.go b/internal/cmd/users.go index 15dfbf78..87f40fa4 100644 --- a/internal/cmd/users.go +++ b/internal/cmd/users.go @@ -32,12 +32,13 @@ coder users ls -o json | jq .[] | jq -r .email`, func listUsers(outputFmt *string) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - client, err := newClient() + ctx := cmd.Context() + client, err := newClient(ctx) if err != nil { return err } - users, err := client.Users(cmd.Context()) + users, err := client.Users(ctx) if err != nil { return xerrors.Errorf("get users: %w", err) }