From 8a9d2c6d39000c4eb70baffe63bbed741749f07c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 4 May 2021 11:14:41 -0500 Subject: [PATCH 1/6] Add edit-from-config command to switch templates --- coder-sdk/env.go | 13 ++-- internal/cmd/envs.go | 174 ++++++++++++++++++++++++++++--------------- 2 files changed, 120 insertions(+), 67 deletions(-) diff --git a/coder-sdk/env.go b/coder-sdk/env.go index e807b219..6d7d624f 100644 --- a/coder-sdk/env.go +++ b/coder-sdk/env.go @@ -205,12 +205,13 @@ func (c *DefaultClient) StopEnvironment(ctx context.Context, envID string) error // UpdateEnvironmentReq defines the update operation, only setting // nil-fields. type UpdateEnvironmentReq struct { - ImageID *string `json:"image_id"` - ImageTag *string `json:"image_tag"` - CPUCores *float32 `json:"cpu_cores"` - MemoryGB *float32 `json:"memory_gb"` - DiskGB *int `json:"disk_gb"` - GPUs *int `json:"gpus"` + ImageID *string `json:"image_id"` + ImageTag *string `json:"image_tag"` + CPUCores *float32 `json:"cpu_cores"` + MemoryGB *float32 `json:"memory_gb"` + DiskGB *int `json:"disk_gb"` + GPUs *int `json:"gpus"` + TemplateID *string `json:"template_id"` } // RebuildEnvironment requests that the given envID is rebuilt with no changes to its specification. diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index c33db37d..533b082f 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -35,7 +35,8 @@ func envsCmd() *cobra.Command { watchBuildLogCommand(), rebuildEnvCommand(), createEnvCmd(), - createEnvFromConfigCmd(), + environmentFromConfigCmd(true), + environmentFromConfigCmd(false), editEnvCmd(), ) return cmd @@ -287,7 +288,10 @@ coder envs create my-new-powerful-env --cpu 12 --disk 100 --memory 16 --image ub return cmd } -func createEnvFromConfigCmd() *cobra.Command { +// environmentFromConfigCmd will return a create or an update workspace for a template'd workspace. +// The code for create/update is nearly identical. +// If `update` is true, the update command is returned. If false, the create command. +func environmentFromConfigCmd(update bool) *cobra.Command { var ( ref string repo string @@ -298,38 +302,50 @@ func createEnvFromConfigCmd() *cobra.Command { envName string ) - cmd := &cobra.Command{ - Use: "create-from-config", - Short: "create a new environment from a template", - Long: "Create a new Coder environment using a Workspaces As Code template.", - Example: `# create a new environment from git repository -coder envs create-from-config --name="dev-env" --repo-url https://github.com/cdr/m --ref my-branch -coder envs create-from-config --name="dev-env" -f coder.yaml`, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() + run := func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + // Update requires the env name, and the name should be the first argument. + if update { + envName = args[0] + } else { // Create takes the name as a flag + // TODO: @emyrk Should we take the name as the first argument always? if envName == "" { return clog.Error("Must provide a environment name.", clog.BlankLine, clog.Tipf("Use --name= to name your environment"), ) } + } - client, err := newClient(ctx) - if err != nil { - return err - } + client, err := newClient(ctx) + if err != nil { + return err + } - orgs, err := getUserOrgs(ctx, client, coder.Me) - if err != nil { - return err - } + orgs, err := getUserOrgs(ctx, client, coder.Me) + if err != nil { + return err + } - multiOrgMember := len(orgs) > 1 - if multiOrgMember && org == "" { - return xerrors.New("org is required for multi-org members") - } + multiOrgMember := len(orgs) > 1 + if multiOrgMember && org == "" { + return xerrors.New("org is required for multi-org members") + } + // This is the env to be updated/created + var env *coder.Environment + + // OrgID is the org where the template and env should be created. + // If we are updating an env, use the orgID from the environment. + var orgID string + if update { + env, err = findEnv(ctx, client, envName, coder.Me) + if err != nil { + return handleAPIError(err) + } + orgID = env.OrganizationID + } else { var userOrg *coder.Organization for i := range orgs { // Look for org by name @@ -351,35 +367,46 @@ coder envs create-from-config --name="dev-env" -f coder.yaml`, return xerrors.Errorf("Unable to locate a default organization for the user") } - var rd io.Reader - if filepath != "" { - b, err := ioutil.ReadFile(filepath) - if err != nil { - return xerrors.Errorf("read local file: %w", err) - } - rd = bytes.NewReader(b) - } - - req := coder.ParseTemplateRequest{ - RepoURL: repo, - Ref: ref, - Local: rd, - OrgID: userOrg.ID, - Filepath: ".coder/coder.yaml", - } + orgID = userOrg.ID + } - version, err := client.ParseTemplate(ctx, req) + var rd io.Reader + if filepath != "" { + b, err := ioutil.ReadFile(filepath) if err != nil { - return handleAPIError(err) + return xerrors.Errorf("read local file: %w", err) } + rd = bytes.NewReader(b) + } + + req := coder.ParseTemplateRequest{ + RepoURL: repo, + Ref: ref, + Local: rd, + OrgID: orgID, + Filepath: ".coder/coder.yaml", + } - provider, err := coderutil.DefaultWorkspaceProvider(ctx, client) + version, err := client.ParseTemplate(ctx, req) + if err != nil { + return handleAPIError(err) + } + + provider, err := coderutil.DefaultWorkspaceProvider(ctx, client) + if err != nil { + return xerrors.Errorf("default workspace provider: %w", err) + } + + if update { + err = client.EditEnvironment(ctx, env.ID, coder.UpdateEnvironmentReq{ + TemplateID: &version.TemplateID, + }) if err != nil { - return xerrors.Errorf("default workspace provider: %w", err) + return handleAPIError(err) } - - env, err := client.CreateEnvironment(ctx, coder.CreateEnvironmentRequest{ - OrgID: userOrg.ID, + } else { + env, err = client.CreateEnvironment(ctx, coder.CreateEnvironmentRequest{ + OrgID: orgID, TemplateID: version.TemplateID, ResourcePoolID: provider.ID, Namespace: provider.DefaultNamespace, @@ -388,29 +415,54 @@ coder envs create-from-config --name="dev-env" -f coder.yaml`, if err != nil { return handleAPIError(err) } + } - if follow { - clog.LogSuccess("creating environment...") - if err := trailBuildLogs(ctx, client, env.ID); err != nil { - return err - } - return nil + if follow { + clog.LogSuccess("creating environment...") + if err := trailBuildLogs(ctx, client, env.ID); err != nil { + return err } - - clog.LogSuccess("creating environment...", - clog.BlankLine, - clog.Tipf(`run "coder envs watch-build %s" to trail the build logs`, env.Name), - ) return nil - }, + } + + clog.LogSuccess("creating environment...", + clog.BlankLine, + clog.Tipf(`run "coder envs watch-build %s" to trail the build logs`, env.Name), + ) + return nil } - cmd.Flags().StringVarP(&org, "org", "o", "", "name of the organization the environment should be created under.") + + var cmd *cobra.Command + if update { + cmd = &cobra.Command{ + Use: "edit-from-config", + Short: "change the template an environment is tracking", + Long: "Edit an existing Coder environment using a Workspaces As Code template.", + Args: cobra.ExactArgs(1), + Example: `# edit a new environment from git repository +coder envs edit-from-config dev-env --repo-url https://github.com/cdr/m --ref my-branch +coder envs edit-from-config dev-env -f coder.yaml`, + RunE: run, + } + } else { + cmd = &cobra.Command{ + Use: "create-from-config", + Short: "create a new environment from a template", + Long: "Create a new Coder environment using a Workspaces As Code template.", + Example: `# create a new environment from git repository +coder envs create-from-config --name="dev-env" --repo-url https://github.com/cdr/m --ref my-branch +coder envs create-from-config --name="dev-env" -f coder.yaml`, + RunE: run, + } + cmd.Flags().StringVar(&providerName, "provider", "", "name of Workspace Provider with which to create the environment") + cmd.Flags().StringVar(&envName, "name", "", "name of the environment to be created") + cmd.Flags().StringVarP(&org, "org", "o", "", "name of the organization the environment should be created under.") + } + cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "path to local template file.") cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.") cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.") cmd.Flags().BoolVar(&follow, "follow", false, "follow buildlog after initiating rebuild") - cmd.Flags().StringVar(&providerName, "provider", "", "name of Workspace Provider with which to create the environment") - cmd.Flags().StringVar(&envName, "name", "", "name of the environment to be created") return cmd } From 47b8b7c4d14ab6634557fb5c299a28175ddf06dd Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 4 May 2021 11:35:27 -0500 Subject: [PATCH 2/6] Handle cyclo limit --- internal/cmd/envs.go | 71 ++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 533b082f..51c84fb2 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -288,6 +288,32 @@ coder envs create my-new-powerful-env --cpu 12 --disk 100 --memory 16 --image ub return cmd } +// selectOrg finds the organization in the list or returns the default organization +// if the needle isn't found. +func selectOrg(needle string, haystack []coder.Organization) (*coder.Organization, error) { + var userOrg *coder.Organization + for i := range haystack { + // Look for org by name + if haystack[i].Name == needle { + userOrg = &haystack[i] + break + } + // Or use default if the provided is blank + if needle == "" && haystack[i].Default { + userOrg = &haystack[i] + break + } + } + + if userOrg == nil { + if needle != "" { + return nil, xerrors.Errorf("Unable to locate org '%s'", needle) + } + return nil, xerrors.Errorf("Unable to locate a default organization for the user") + } + return userOrg, nil +} + // environmentFromConfigCmd will return a create or an update workspace for a template'd workspace. // The code for create/update is nearly identical. // If `update` is true, the update command is returned. If false, the create command. @@ -308,14 +334,12 @@ func environmentFromConfigCmd(update bool) *cobra.Command { // Update requires the env name, and the name should be the first argument. if update { envName = args[0] - } else { // Create takes the name as a flag - // TODO: @emyrk Should we take the name as the first argument always? - if envName == "" { - return clog.Error("Must provide a environment name.", - clog.BlankLine, - clog.Tipf("Use --name= to name your environment"), - ) - } + } else if envName == "" { + // Create takes the name as a flag, and it must be set + return clog.Error("Must provide a environment name.", + clog.BlankLine, + clog.Tipf("Use --name= to name your environment"), + ) } client, err := newClient(ctx) @@ -347,24 +371,10 @@ func environmentFromConfigCmd(update bool) *cobra.Command { orgID = env.OrganizationID } else { var userOrg *coder.Organization - for i := range orgs { - // Look for org by name - if orgs[i].Name == org { - userOrg = &orgs[i] - break - } - // Or use default if the provided is blank - if org == "" && orgs[i].Default { - userOrg = &orgs[i] - break - } - } - - if userOrg == nil { - if org != "" { - return xerrors.Errorf("Unable to locate org '%s'", org) - } - return xerrors.Errorf("Unable to locate a default organization for the user") + // Select org in list or use default + userOrg, err := selectOrg(org, orgs) + if err != nil { + return err } orgID = userOrg.ID @@ -401,9 +411,6 @@ func environmentFromConfigCmd(update bool) *cobra.Command { err = client.EditEnvironment(ctx, env.ID, coder.UpdateEnvironmentReq{ TemplateID: &version.TemplateID, }) - if err != nil { - return handleAPIError(err) - } } else { env, err = client.CreateEnvironment(ctx, coder.CreateEnvironmentRequest{ OrgID: orgID, @@ -412,9 +419,9 @@ func environmentFromConfigCmd(update bool) *cobra.Command { Namespace: provider.DefaultNamespace, Name: envName, }) - if err != nil { - return handleAPIError(err) - } + } + if err != nil { + return handleAPIError(err) } if follow { From 16cca44f29513079ca747febd88ff7131dd02cb8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 4 May 2021 16:57:53 +0000 Subject: [PATCH 3/6] Gen docs --- docs/coder_envs.md | 1 + docs/coder_envs_edit-from-config.md | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 docs/coder_envs_edit-from-config.md diff --git a/docs/coder_envs.md b/docs/coder_envs.md index 623c5e83..29553d2d 100644 --- a/docs/coder_envs.md +++ b/docs/coder_envs.md @@ -24,6 +24,7 @@ Perform operations on the Coder environments owned by the active user. * [coder envs create](coder_envs_create.md) - create a new environment. * [coder envs create-from-config](coder_envs_create-from-config.md) - create a new environment from a template * [coder envs edit](coder_envs_edit.md) - edit an existing environment and initiate a rebuild. +* [coder envs edit-from-config](coder_envs_edit-from-config.md) - change the template an environment is tracking * [coder envs ls](coder_envs_ls.md) - list all environments owned by the active user * [coder envs rebuild](coder_envs_rebuild.md) - rebuild a Coder environment * [coder envs rm](coder_envs_rm.md) - remove Coder environments by name diff --git a/docs/coder_envs_edit-from-config.md b/docs/coder_envs_edit-from-config.md new file mode 100644 index 00000000..c0c98a05 --- /dev/null +++ b/docs/coder_envs_edit-from-config.md @@ -0,0 +1,40 @@ +## coder envs edit-from-config + +change the template an environment is tracking + +### Synopsis + +Edit an existing Coder environment using a Workspaces As Code template. + +``` +coder envs edit-from-config [flags] +``` + +### Examples + +``` +# edit a new environment from git repository +coder envs edit-from-config dev-env --repo-url https://github.com/cdr/m --ref my-branch +coder envs edit-from-config dev-env -f coder.yaml +``` + +### Options + +``` + -f, --filepath string path to local template file. + --follow follow buildlog after initiating rebuild + -h, --help help for edit-from-config + --ref string git reference to pull template from. May be a branch, tag, or commit hash. (default "master") + -r, --repo-url string URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'. +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder envs](coder_envs.md) - Interact with Coder environments + From efae3f86da13329ba94494b85edd95a0c1b14a7f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 5 May 2021 15:02:50 +0000 Subject: [PATCH 4/6] Add an early error message if no template is specified --- internal/cmd/envs.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 51c84fb2..e2c3ddb3 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -380,6 +380,12 @@ func environmentFromConfigCmd(update bool) *cobra.Command { orgID = userOrg.ID } + if filepath == "" && ref == "" && repo == "" { + return clog.Error("Must specify a configuration source", + "A template source is either sourced from a local file (-f) or from a git repository (--repo-url and --ref)", + ) + } + var rd io.Reader if filepath != "" { b, err := ioutil.ReadFile(filepath) @@ -464,11 +470,12 @@ coder envs create-from-config --name="dev-env" -f coder.yaml`, cmd.Flags().StringVar(&providerName, "provider", "", "name of Workspace Provider with which to create the environment") cmd.Flags().StringVar(&envName, "name", "", "name of the environment to be created") cmd.Flags().StringVarP(&org, "org", "o", "", "name of the organization the environment should be created under.") + // Ref and repo-url can only be used for create + cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.") + cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.") } cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "path to local template file.") - cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.") - cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.") cmd.Flags().BoolVar(&follow, "follow", false, "follow buildlog after initiating rebuild") return cmd } From 33a5cb5a9a8692029ff2cb2225a24e2b82b39939 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 5 May 2021 15:05:51 +0000 Subject: [PATCH 5/6] Gendocs --- docs/coder_envs_edit-from-config.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/coder_envs_edit-from-config.md b/docs/coder_envs_edit-from-config.md index c0c98a05..ec5d6c03 100644 --- a/docs/coder_envs_edit-from-config.md +++ b/docs/coder_envs_edit-from-config.md @@ -24,8 +24,6 @@ coder envs edit-from-config dev-env -f coder.yaml -f, --filepath string path to local template file. --follow follow buildlog after initiating rebuild -h, --help help for edit-from-config - --ref string git reference to pull template from. May be a branch, tag, or commit hash. (default "master") - -r, --repo-url string URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'. ``` ### Options inherited from parent commands From 37dc37548c0cafd589a1b28221a17043ee24357b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 18 May 2021 12:34:46 -0500 Subject: [PATCH 6/6] Gen docs --- docs/coder_workspaces.md | 1 + docs/coder_workspaces_create-from-config.md | 4 ++-- ...onfig.md => coder_workspaces_edit-from-config.md} | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) rename docs/{coder_envs_edit-from-config.md => coder_workspaces_edit-from-config.md} (61%) diff --git a/docs/coder_workspaces.md b/docs/coder_workspaces.md index bb29bcf5..4402d92c 100644 --- a/docs/coder_workspaces.md +++ b/docs/coder_workspaces.md @@ -24,6 +24,7 @@ Perform operations on the Coder workspaces owned by the active user. * [coder workspaces create](coder_workspaces_create.md) - create a new workspace. * [coder workspaces create-from-config](coder_workspaces_create-from-config.md) - create a new workspace from a template * [coder workspaces edit](coder_workspaces_edit.md) - edit an existing workspace and initiate a rebuild. +* [coder workspaces edit-from-config](coder_workspaces_edit-from-config.md) - change the template a workspace is tracking * [coder workspaces ls](coder_workspaces_ls.md) - list all workspaces owned by the active user * [coder workspaces rebuild](coder_workspaces_rebuild.md) - rebuild a Coder workspace * [coder workspaces rm](coder_workspaces_rm.md) - remove Coder workspaces by name diff --git a/docs/coder_workspaces_create-from-config.md b/docs/coder_workspaces_create-from-config.md index 3ad6c2ae..221175b7 100644 --- a/docs/coder_workspaces_create-from-config.md +++ b/docs/coder_workspaces_create-from-config.md @@ -14,8 +14,8 @@ coder workspaces create-from-config [flags] ``` # create a new workspace from git repository -coder workspaces create-from-config --name="dev-workspace" --repo-url https://github.com/cdr/m --ref my-branch -coder workspaces create-from-config --name="dev-workspace" -f coder.yaml +coder envs create-from-config --name="dev-env" --repo-url https://github.com/cdr/m --ref my-branch +coder envs create-from-config --name="dev-env" -f coder.yaml ``` ### Options diff --git a/docs/coder_envs_edit-from-config.md b/docs/coder_workspaces_edit-from-config.md similarity index 61% rename from docs/coder_envs_edit-from-config.md rename to docs/coder_workspaces_edit-from-config.md index ec5d6c03..085e1470 100644 --- a/docs/coder_envs_edit-from-config.md +++ b/docs/coder_workspaces_edit-from-config.md @@ -1,19 +1,19 @@ -## coder envs edit-from-config +## coder workspaces edit-from-config -change the template an environment is tracking +change the template a workspace is tracking ### Synopsis -Edit an existing Coder environment using a Workspaces As Code template. +Edit an existing Coder workspace using a Workspaces As Code template. ``` -coder envs edit-from-config [flags] +coder workspaces edit-from-config [flags] ``` ### Examples ``` -# edit a new environment from git repository +# edit a new workspace from git repository coder envs edit-from-config dev-env --repo-url https://github.com/cdr/m --ref my-branch coder envs edit-from-config dev-env -f coder.yaml ``` @@ -34,5 +34,5 @@ coder envs edit-from-config dev-env -f coder.yaml ### SEE ALSO -* [coder envs](coder_envs.md) - Interact with Coder environments +* [coder workspaces](coder_workspaces.md) - Interact with Coder workspaces