From 3979b41999749917e4c5b186c6d8a7efd3a1a563 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 11:02:06 +0100 Subject: [PATCH 01/10] CopyParameters -first version --- cli/create.go | 40 ++++++++++++++++++++++--- cli/testdata/coder_create_--help.golden | 3 ++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/cli/create.go b/cli/create.go index 79f569d4a02d6..7b0b3c1fccf3f 100644 --- a/cli/create.go +++ b/cli/create.go @@ -28,6 +28,7 @@ func (r *RootCmd) create() *clibase.Cmd { parameterFlags workspaceParameterFlags autoUpdates string + copyParameters string ) client := new(codersdk.Client) cmd := &clibase.Cmd{ @@ -76,6 +77,22 @@ func (r *RootCmd) create() *clibase.Cmd { return xerrors.Errorf("A workspace already exists named %q!", workspaceName) } + var sourceWorkspace codersdk.Workspace + if copyParameters != "" { + sourceWorkspaceOwner, sourceWorkspaceName, err := splitNamedWorkspace(copyParameters) + if err != nil { + return err + } + + sourceWorkspace, err = client.WorkspaceByOwnerAndName(inv.Context(), sourceWorkspaceOwner, sourceWorkspaceName, codersdk.WorkspaceOptions{}) + if err != nil { + return err + } + + _, _ = fmt.Fprintf(inv.Stdout, "Coder will use the same template %q as the source workspace.\n", sourceWorkspace.TemplateName) + templateName = sourceWorkspace.TemplateName + } + var template codersdk.Template if templateName == "" { _, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select a template below to preview the provisioned infrastructure:")) @@ -134,9 +151,17 @@ func (r *RootCmd) create() *clibase.Cmd { schedSpec = ptr.Ref(sched.String()) } - cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters) - if err != nil { - return xerrors.Errorf("can't parse given parameter values: %w", err) + var buildParameters []codersdk.WorkspaceBuildParameter + if copyParameters != "" { + buildParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.ID) + if err != nil { + return err + } + } else { + buildParameters, err = asWorkspaceBuildParameters(parameterFlags.richParameters) + if err != nil { + return xerrors.Errorf("can't parse given parameter values: %w", err) + } } richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{ @@ -145,7 +170,7 @@ func (r *RootCmd) create() *clibase.Cmd { NewWorkspaceName: workspaceName, RichParameterFile: parameterFlags.richParameterFile, - RichParameters: cliRichParameters, + RichParameters: buildParameters, }) if err != nil { return xerrors.Errorf("prepare build: %w", err) @@ -217,6 +242,13 @@ func (r *RootCmd) create() *clibase.Cmd { Default: string(codersdk.AutomaticUpdatesNever), Value: clibase.StringOf(&autoUpdates), }, + clibase.Option{ + Flag: "copy-parameters", + Env: "CODER_WORKSPACE_COPY_PARAMETERS", + Description: "Specify the source workspace name to copy parameters from.", + Default: string(codersdk.AutomaticUpdatesNever), + Value: clibase.StringOf(©Parameters), + }, cliui.SkipPromptOption(), ) cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...) diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index 2d4031999c3d6..d649268e1d6ca 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -14,6 +14,9 @@ OPTIONS: Specify automatic updates setting for the workspace (accepts 'always' or 'never'). + --copy-parameters string, $CODER_WORKSPACE_COPY_PARAMETERS (default: never) + Specify the source workspace name to copy parameters from. + --parameter string-array, $CODER_RICH_PARAMETER Rich parameter value in the format "name=value". From 487424d64d17b0b01410d4cb6746bbe21b6910e2 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 11:31:58 +0100 Subject: [PATCH 02/10] make gen --- docs/cli/create.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/cli/create.md b/docs/cli/create.md index f7036ac84d9a3..60ad134527ebb 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -30,6 +30,16 @@ coder create [flags] [name] Specify automatic updates setting for the workspace (accepts 'always' or 'never'). +### --copy-parameters + +| | | +| ----------- | --------------------------------------------- | +| Type | string | +| Environment | $CODER_WORKSPACE_COPY_PARAMETERS | +| Default | never | + +Specify the source workspace name to copy parameters from. + ### --parameter | | | From 925c812f9e46bc243a5532680c9278ede789d724 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 11:43:03 +0100 Subject: [PATCH 03/10] Use resolver --- cli/create.go | 22 +++++++++++++--------- cli/parameterresolver.go | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/cli/create.go b/cli/create.go index 7b0b3c1fccf3f..0d09c9cb7a8eb 100644 --- a/cli/create.go +++ b/cli/create.go @@ -151,17 +151,17 @@ func (r *RootCmd) create() *clibase.Cmd { schedSpec = ptr.Ref(sched.String()) } - var buildParameters []codersdk.WorkspaceBuildParameter + cliBuildParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters) + if err != nil { + return xerrors.Errorf("can't parse given parameter values: %w", err) + } + + var sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter if copyParameters != "" { - buildParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.ID) + sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.ID) if err != nil { return err } - } else { - buildParameters, err = asWorkspaceBuildParameters(parameterFlags.richParameters) - if err != nil { - return xerrors.Errorf("can't parse given parameter values: %w", err) - } } richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{ @@ -170,7 +170,9 @@ func (r *RootCmd) create() *clibase.Cmd { NewWorkspaceName: workspaceName, RichParameterFile: parameterFlags.richParameterFile, - RichParameters: buildParameters, + RichParameters: cliBuildParameters, + + SourceWorkspaceParameters: sourceWorkspaceParameters, }) if err != nil { return xerrors.Errorf("prepare build: %w", err) @@ -260,7 +262,8 @@ type prepWorkspaceBuildArgs struct { TemplateVersionID uuid.UUID NewWorkspaceName string - LastBuildParameters []codersdk.WorkspaceBuildParameter + LastBuildParameters []codersdk.WorkspaceBuildParameter + SourceWorkspaceParameters []codersdk.WorkspaceBuildParameter PromptBuildOptions bool BuildOptions []codersdk.WorkspaceBuildParameter @@ -295,6 +298,7 @@ func prepWorkspaceBuild(inv *clibase.Invocation, client *codersdk.Client, args p resolver := new(ParameterResolver). WithLastBuildParameters(args.LastBuildParameters). + WithSourceWorkspaceParameters(args.SourceWorkspaceParameters). WithPromptBuildOptions(args.PromptBuildOptions). WithBuildOptions(args.BuildOptions). WithPromptRichParameters(args.PromptRichParameters). diff --git a/cli/parameterresolver.go b/cli/parameterresolver.go index 21a31825bd0cf..c97d9a3e1bfd2 100644 --- a/cli/parameterresolver.go +++ b/cli/parameterresolver.go @@ -23,7 +23,8 @@ const ( ) type ParameterResolver struct { - lastBuildParameters []codersdk.WorkspaceBuildParameter + lastBuildParameters []codersdk.WorkspaceBuildParameter + sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter richParameters []codersdk.WorkspaceBuildParameter richParametersFile map[string]string @@ -38,6 +39,11 @@ func (pr *ParameterResolver) WithLastBuildParameters(params []codersdk.Workspace return pr } +func (pr *ParameterResolver) WithSourceWorkspaceParameters(params []codersdk.WorkspaceBuildParameter) *ParameterResolver { + pr.sourceWorkspaceParameters = params + return pr +} + func (pr *ParameterResolver) WithRichParameters(params []codersdk.WorkspaceBuildParameter) *ParameterResolver { pr.richParameters = params return pr @@ -69,6 +75,7 @@ func (pr *ParameterResolver) Resolve(inv *clibase.Invocation, action WorkspaceCL staged = pr.resolveWithParametersMapFile(staged) staged = pr.resolveWithCommandLineOrEnv(staged) + staged = pr.resolveWithSourceBuildParameters(staged, templateVersionParameters) staged = pr.resolveWithLastBuildParameters(staged, templateVersionParameters) if err = pr.verifyConstraints(staged, action, templateVersionParameters); err != nil { return nil, err @@ -160,6 +167,30 @@ next: return resolved } +func (pr *ParameterResolver) resolveWithSourceBuildParameters(resolved []codersdk.WorkspaceBuildParameter, templateVersionParameters []codersdk.TemplateVersionParameter) []codersdk.WorkspaceBuildParameter { +next: + for _, buildParameter := range pr.sourceWorkspaceParameters { + tvp := findTemplateVersionParameter(buildParameter, templateVersionParameters) + if tvp == nil { + continue // it looks like this parameter is not present anymore + } + + if tvp.Ephemeral { + continue // ephemeral parameters should not be passed to consecutive builds + } + + for i, r := range resolved { + if r.Name == buildParameter.Name { + resolved[i].Value = buildParameter.Value + continue next + } + } + + resolved = append(resolved, buildParameter) + } + return resolved +} + func (pr *ParameterResolver) verifyConstraints(resolved []codersdk.WorkspaceBuildParameter, action WorkspaceCLIAction, templateVersionParameters []codersdk.TemplateVersionParameter) error { for _, r := range resolved { tvp := findTemplateVersionParameter(r, templateVersionParameters) From f0bb47ecdb1e8f9d0df66620ea435a21a4ea9100 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 12:55:08 +0100 Subject: [PATCH 04/10] Improve --- cli/create.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/create.go b/cli/create.go index 0d09c9cb7a8eb..31eafdf2f2b38 100644 --- a/cli/create.go +++ b/cli/create.go @@ -86,7 +86,7 @@ func (r *RootCmd) create() *clibase.Cmd { sourceWorkspace, err = client.WorkspaceByOwnerAndName(inv.Context(), sourceWorkspaceOwner, sourceWorkspaceName, codersdk.WorkspaceOptions{}) if err != nil { - return err + return xerrors.Errorf("get source workspace: %w", err) } _, _ = fmt.Fprintf(inv.Stdout, "Coder will use the same template %q as the source workspace.\n", sourceWorkspace.TemplateName) @@ -158,9 +158,9 @@ func (r *RootCmd) create() *clibase.Cmd { var sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter if copyParameters != "" { - sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.ID) + sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.LatestBuild.ID) if err != nil { - return err + return xerrors.Errorf("get source workspace build parameters: %w", err) } } From 4c9c77e6e91dd0b275f229a595887c27f221be87 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 14:01:48 +0100 Subject: [PATCH 05/10] Fix default --- cli/create.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/create.go b/cli/create.go index 31eafdf2f2b38..6e06bfaaffc6a 100644 --- a/cli/create.go +++ b/cli/create.go @@ -248,7 +248,6 @@ func (r *RootCmd) create() *clibase.Cmd { Flag: "copy-parameters", Env: "CODER_WORKSPACE_COPY_PARAMETERS", Description: "Specify the source workspace name to copy parameters from.", - Default: string(codersdk.AutomaticUpdatesNever), Value: clibase.StringOf(©Parameters), }, cliui.SkipPromptOption(), From c5f8ee9d76196f3ba840cad3dcd4d23d6b8198f3 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 14:19:36 +0100 Subject: [PATCH 06/10] make gen --- cli/testdata/coder_create_--help.golden | 2 +- docs/cli/create.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index d649268e1d6ca..ee07e7dabb0da 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -14,7 +14,7 @@ OPTIONS: Specify automatic updates setting for the workspace (accepts 'always' or 'never'). - --copy-parameters string, $CODER_WORKSPACE_COPY_PARAMETERS (default: never) + --copy-parameters string, $CODER_WORKSPACE_COPY_PARAMETERS Specify the source workspace name to copy parameters from. --parameter string-array, $CODER_RICH_PARAMETER diff --git a/docs/cli/create.md b/docs/cli/create.md index 60ad134527ebb..e56d60d841b8c 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -36,7 +36,6 @@ Specify automatic updates setting for the workspace (accepts 'always' or 'never' | ----------- | --------------------------------------------- | | Type | string | | Environment | $CODER_WORKSPACE_COPY_PARAMETERS | -| Default | never | Specify the source workspace name to copy parameters from. From 4deaad0e6b1e4c013a60a3d4b484be183a3ce438 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 14:54:59 +0100 Subject: [PATCH 07/10] WIP --- cli/create_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cli/create_test.go b/cli/create_test.go index 0f3f06eb1db1d..91f260b5026c9 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -416,6 +416,39 @@ func TestCreateWithRichParameters(t *testing.T) { assert.ErrorContains(t, err, "parameter \""+wrongFirstParameterName+"\" is not present in the template") assert.ErrorContains(t, err, "Did you mean: "+firstParameterName) }) + + t.Run("CopyParameters", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + + // Firstly, create a regular workspace using template with parameters. + inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name, "-y", + "--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstParameterValue), + "--parameter", fmt.Sprintf("%s=%s", secondParameterName, secondParameterValue), + "--parameter", fmt.Sprintf("%s=%s", immutableParameterName, immutableParameterValue)) + clitest.SetupConfig(t, member, root) + pty := ptytest.New(t).Attach(inv) + inv.Stdout = pty.Output() + inv.Stderr = pty.Output() + err := inv.Run() + require.NoError(t, err, "can't create first workspace") + + // Secondly, create a new workspace using parameters from the previous workspace. + inv, root = clitest.New(t, "create", "--copy-parameters", "my-workspace", "other-workspace", "-y") + clitest.SetupConfig(t, member, root) + pty = ptytest.New(t).Attach(inv) + inv.Stdout = pty.Output() + inv.Stderr = pty.Output() + err = inv.Run() + require.NoError(t, err, "can't create a workspace based on the source workspace") + }) } func TestCreateValidateRichParameters(t *testing.T) { From f9662946eb14ceac2197141a7d8a8903f8945e0d Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 17:03:08 +0100 Subject: [PATCH 08/10] CLI test --- cli/create_test.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cli/create_test.go b/cli/create_test.go index 91f260b5026c9..3a4748b646f77 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -441,13 +441,34 @@ func TestCreateWithRichParameters(t *testing.T) { require.NoError(t, err, "can't create first workspace") // Secondly, create a new workspace using parameters from the previous workspace. - inv, root = clitest.New(t, "create", "--copy-parameters", "my-workspace", "other-workspace", "-y") + const otherWorkspace = "other-workspace" + + inv, root = clitest.New(t, "create", "--copy-parameters", "my-workspace", otherWorkspace, "-y") clitest.SetupConfig(t, member, root) pty = ptytest.New(t).Attach(inv) inv.Stdout = pty.Output() inv.Stderr = pty.Output() err = inv.Run() require.NoError(t, err, "can't create a workspace based on the source workspace") + + // Verify if the new workspace uses expected parameters. + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + defer cancel() + + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + Name: otherWorkspace, + }) + require.NoError(t, err, "can't list available workspaces") + require.Len(t, workspaces.Workspaces, 1) + + otherWorkspaceLatestBuild := workspaces.Workspaces[0].LatestBuild + + buildParameters, err := client.WorkspaceBuildParameters(ctx, otherWorkspaceLatestBuild.ID) + require.NoError(t, err) + require.Len(t, buildParameters, 3) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: secondParameterName, Value: secondParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: immutableParameterName, Value: immutableParameterValue}) }) } From 5861631d3b9fb36206b1193aa551b72c5ae2c294 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 17:46:41 +0100 Subject: [PATCH 09/10] copy-parameters-from --- cli/create.go | 18 +++++++++--------- cli/create_test.go | 2 +- cli/testdata/coder_create_--help.golden | 2 +- docs/cli/create.md | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cli/create.go b/cli/create.go index 6e06bfaaffc6a..379da1f3fef1b 100644 --- a/cli/create.go +++ b/cli/create.go @@ -26,9 +26,9 @@ func (r *RootCmd) create() *clibase.Cmd { stopAfter time.Duration workspaceName string - parameterFlags workspaceParameterFlags - autoUpdates string - copyParameters string + parameterFlags workspaceParameterFlags + autoUpdates string + copyParametersFrom string ) client := new(codersdk.Client) cmd := &clibase.Cmd{ @@ -78,8 +78,8 @@ func (r *RootCmd) create() *clibase.Cmd { } var sourceWorkspace codersdk.Workspace - if copyParameters != "" { - sourceWorkspaceOwner, sourceWorkspaceName, err := splitNamedWorkspace(copyParameters) + if copyParametersFrom != "" { + sourceWorkspaceOwner, sourceWorkspaceName, err := splitNamedWorkspace(copyParametersFrom) if err != nil { return err } @@ -157,7 +157,7 @@ func (r *RootCmd) create() *clibase.Cmd { } var sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter - if copyParameters != "" { + if copyParametersFrom != "" { sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.LatestBuild.ID) if err != nil { return xerrors.Errorf("get source workspace build parameters: %w", err) @@ -245,10 +245,10 @@ func (r *RootCmd) create() *clibase.Cmd { Value: clibase.StringOf(&autoUpdates), }, clibase.Option{ - Flag: "copy-parameters", - Env: "CODER_WORKSPACE_COPY_PARAMETERS", + Flag: "copy-parameters-from", + Env: "CODER_WORKSPACE_COPY_PARAMETERS_FROM", Description: "Specify the source workspace name to copy parameters from.", - Value: clibase.StringOf(©Parameters), + Value: clibase.StringOf(©ParametersFrom), }, cliui.SkipPromptOption(), ) diff --git a/cli/create_test.go b/cli/create_test.go index 3a4748b646f77..be63e572ffbf9 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -443,7 +443,7 @@ func TestCreateWithRichParameters(t *testing.T) { // Secondly, create a new workspace using parameters from the previous workspace. const otherWorkspace = "other-workspace" - inv, root = clitest.New(t, "create", "--copy-parameters", "my-workspace", otherWorkspace, "-y") + inv, root = clitest.New(t, "create", "--copy-parameters-from", "my-workspace", otherWorkspace, "-y") clitest.SetupConfig(t, member, root) pty = ptytest.New(t).Attach(inv) inv.Stdout = pty.Output() diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index ee07e7dabb0da..7662ecd7ed7ee 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -14,7 +14,7 @@ OPTIONS: Specify automatic updates setting for the workspace (accepts 'always' or 'never'). - --copy-parameters string, $CODER_WORKSPACE_COPY_PARAMETERS + --copy-parameters-from string, $CODER_WORKSPACE_COPY_PARAMETERS_FROM Specify the source workspace name to copy parameters from. --parameter string-array, $CODER_RICH_PARAMETER diff --git a/docs/cli/create.md b/docs/cli/create.md index e56d60d841b8c..0b3f63b32b3fd 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -30,12 +30,12 @@ coder create [flags] [name] Specify automatic updates setting for the workspace (accepts 'always' or 'never'). -### --copy-parameters +### --copy-parameters-from -| | | -| ----------- | --------------------------------------------- | -| Type | string | -| Environment | $CODER_WORKSPACE_COPY_PARAMETERS | +| | | +| ----------- | -------------------------------------------------- | +| Type | string | +| Environment | $CODER_WORKSPACE_COPY_PARAMETERS_FROM | Specify the source workspace name to copy parameters from. From 01627957b81d3e802674af003db45803edf18b49 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 9 Nov 2023 18:23:28 +0100 Subject: [PATCH 10/10] Fix: use right template version --- cli/create.go | 13 ++++++++-- cli/create_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/cli/create.go b/cli/create.go index 379da1f3fef1b..1a2492374a186 100644 --- a/cli/create.go +++ b/cli/create.go @@ -94,6 +94,7 @@ func (r *RootCmd) create() *clibase.Cmd { } var template codersdk.Template + var templateVersionID uuid.UUID if templateName == "" { _, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select a template below to preview the provisioned infrastructure:")) @@ -135,11 +136,19 @@ func (r *RootCmd) create() *clibase.Cmd { } template = templateByName[option] + templateVersionID = template.ActiveVersionID + } else if sourceWorkspace.LatestBuild.TemplateVersionID != uuid.Nil { + template, err = client.Template(inv.Context(), sourceWorkspace.TemplateID) + if err != nil { + return xerrors.Errorf("get template by name: %w", err) + } + templateVersionID = sourceWorkspace.LatestBuild.TemplateVersionID } else { template, err = client.TemplateByName(inv.Context(), organization.ID, templateName) if err != nil { return xerrors.Errorf("get template by name: %w", err) } + templateVersionID = template.ActiveVersionID } var schedSpec *string @@ -166,7 +175,7 @@ func (r *RootCmd) create() *clibase.Cmd { richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{ Action: WorkspaceCreate, - TemplateVersionID: template.ActiveVersionID, + TemplateVersionID: templateVersionID, NewWorkspaceName: workspaceName, RichParameterFile: parameterFlags.richParameterFile, @@ -192,7 +201,7 @@ func (r *RootCmd) create() *clibase.Cmd { } workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{ - TemplateID: template.ID, + TemplateVersionID: templateVersionID, Name: workspaceName, AutostartSchedule: schedSpec, TTLMillis: ttlMillis, diff --git a/cli/create_test.go b/cli/create_test.go index be63e572ffbf9..42b526d404cfc 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -470,6 +470,70 @@ func TestCreateWithRichParameters(t *testing.T) { require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: secondParameterName, Value: secondParameterValue}) require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: immutableParameterName, Value: immutableParameterValue}) }) + + t.Run("CopyParametersFromNotUpdatedWorkspace", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + + // Firstly, create a regular workspace using template with parameters. + inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name, "-y", + "--parameter", fmt.Sprintf("%s=%s", firstParameterName, firstParameterValue), + "--parameter", fmt.Sprintf("%s=%s", secondParameterName, secondParameterValue), + "--parameter", fmt.Sprintf("%s=%s", immutableParameterName, immutableParameterValue)) + clitest.SetupConfig(t, member, root) + pty := ptytest.New(t).Attach(inv) + inv.Stdout = pty.Output() + inv.Stderr = pty.Output() + err := inv.Run() + require.NoError(t, err, "can't create first workspace") + + // Secondly, update the template to the newer version. + version2 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses([]*proto.RichParameter{ + {Name: "third_parameter", Type: "string", DefaultValue: "not-relevant"}, + }), func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID) + coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, version2.ID) + + // Thirdly, create a new workspace using parameters from the previous workspace. + const otherWorkspace = "other-workspace" + + inv, root = clitest.New(t, "create", "--copy-parameters-from", "my-workspace", otherWorkspace, "-y") + clitest.SetupConfig(t, member, root) + pty = ptytest.New(t).Attach(inv) + inv.Stdout = pty.Output() + inv.Stderr = pty.Output() + err = inv.Run() + require.NoError(t, err, "can't create a workspace based on the source workspace") + + // Verify if the new workspace uses expected parameters. + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + defer cancel() + + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + Name: otherWorkspace, + }) + require.NoError(t, err, "can't list available workspaces") + require.Len(t, workspaces.Workspaces, 1) + + otherWorkspaceLatestBuild := workspaces.Workspaces[0].LatestBuild + require.Equal(t, version.ID, otherWorkspaceLatestBuild.TemplateVersionID) + + buildParameters, err := client.WorkspaceBuildParameters(ctx, otherWorkspaceLatestBuild.ID) + require.NoError(t, err) + require.Len(t, buildParameters, 3) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: secondParameterName, Value: secondParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: immutableParameterName, Value: immutableParameterValue}) + }) } func TestCreateValidateRichParameters(t *testing.T) {