Skip to content

Commit 083fc89

Browse files
authored
feat: accept immutable parameters when used first time (coder#7000)
* Backend fixes * CLI: adjust update flow
1 parent e84061e commit 083fc89

File tree

4 files changed

+220
-7
lines changed

4 files changed

+220
-7
lines changed

cli/create.go

+27-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"time"
88

9+
"github.com/google/uuid"
910
"golang.org/x/exp/slices"
1011
"golang.org/x/xerrors"
1112

@@ -213,6 +214,7 @@ type prepWorkspaceBuildArgs struct {
213214
NewWorkspaceName string
214215

215216
UpdateWorkspace bool
217+
WorkspaceID uuid.UUID
216218
}
217219

218220
type buildParameters struct {
@@ -340,8 +342,17 @@ PromptRichParamLoop:
340342
}
341343

342344
if args.UpdateWorkspace && !templateVersionParameter.Mutable {
343-
_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Warn.Render(fmt.Sprintf(`Parameter %q is not mutable, so can't be customized after workspace creation.`, templateVersionParameter.Name)))
344-
continue
345+
// Check if the immutable parameter was used in the previous build. If so, then it isn't a fresh one
346+
// and the user should be warned.
347+
exists, err := workspaceBuildParameterExists(ctx, client, args.WorkspaceID, templateVersionParameter)
348+
if err != nil {
349+
return nil, err
350+
}
351+
352+
if exists {
353+
_, _ = fmt.Fprintln(inv.Stdout, cliui.Styles.Warn.Render(fmt.Sprintf(`Parameter %q is not mutable, so can't be customized after workspace creation.`, templateVersionParameter.Name)))
354+
continue
355+
}
345356
}
346357

347358
parameterValue, err := getWorkspaceBuildParameterValueFromMapOrInput(inv, parameterMapFromFile, templateVersionParameter)
@@ -414,3 +425,17 @@ PromptRichParamLoop:
414425
richParameters: richParameters,
415426
}, nil
416427
}
428+
429+
func workspaceBuildParameterExists(ctx context.Context, client *codersdk.Client, workspaceID uuid.UUID, templateVersionParameter codersdk.TemplateVersionParameter) (bool, error) {
430+
lastBuildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceID)
431+
if err != nil {
432+
return false, xerrors.Errorf("can't fetch last workspace build parameters: %w", err)
433+
}
434+
435+
for _, p := range lastBuildParameters {
436+
if p.Name == templateVersionParameter.Name {
437+
return true, nil
438+
}
439+
}
440+
return false, nil
441+
}

cli/update.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ func (r *RootCmd) update() *clibase.Cmd {
5959
ExistingRichParams: existingRichParams,
6060
RichParameterFile: richParameterFile,
6161
NewWorkspaceName: workspace.Name,
62-
UpdateWorkspace: true,
62+
63+
UpdateWorkspace: true,
64+
WorkspaceID: workspace.LatestBuild.ID,
6365
})
6466
if err != nil {
6567
return nil

coderd/workspacebuilds.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -530,10 +530,12 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
530530
// Check if parameter value is in request
531531
if buildParameter, found := findWorkspaceBuildParameter(createBuild.RichParameterValues, templateVersionParameter.Name); found {
532532
if !templateVersionParameter.Mutable {
533-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
534-
Message: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", templateVersionParameter.Name),
535-
})
536-
return
533+
if _, found := findWorkspaceBuildParameter(apiLastBuildParameters, templateVersionParameter.Name); found {
534+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
535+
Message: fmt.Sprintf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", templateVersionParameter.Name),
536+
})
537+
return
538+
}
537539
}
538540
parameters = append(parameters, *buildParameter)
539541
continue

coderd/workspacebuilds_test.go

+184
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,190 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
781781
})
782782
require.Error(t, err)
783783
})
784+
785+
t.Run("NewImmutableRequiredParameterAdded", func(t *testing.T) {
786+
t.Parallel()
787+
788+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
789+
user := coderdtest.CreateFirstUser(t, client)
790+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
791+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
792+
793+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
794+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
795+
cwr.RichParameterValues = initialBuildParameters
796+
})
797+
798+
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
799+
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
800+
801+
// Push new template revision
802+
const newImmutableParameterName = "new_immutable_parameter"
803+
const newImmutableParameterDescription = "This is also an immutable parameter"
804+
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
805+
Parse: echo.ParseComplete,
806+
ProvisionPlan: []*proto.Provision_Response{
807+
{
808+
Type: &proto.Provision_Response_Complete{
809+
Complete: &proto.Provision_Complete{
810+
Parameters: []*proto.RichParameter{
811+
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
812+
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
813+
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
814+
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, Required: true},
815+
},
816+
},
817+
},
818+
},
819+
},
820+
ProvisionApply: []*proto.Provision_Response{{
821+
Type: &proto.Provision_Response_Complete{
822+
Complete: &proto.Provision_Complete{},
823+
},
824+
}},
825+
}, template.ID)
826+
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
827+
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
828+
ID: version2.ID,
829+
})
830+
require.NoError(t, err)
831+
832+
// Update build parameters
833+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
834+
defer cancel()
835+
836+
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
837+
{Name: newImmutableParameterName, Value: "good"},
838+
}
839+
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
840+
TemplateVersionID: version2.ID,
841+
Transition: codersdk.WorkspaceTransitionStart,
842+
RichParameterValues: nextBuildParameters,
843+
})
844+
require.NoError(t, err)
845+
})
846+
847+
t.Run("NewImmutableOptionalParameterAdded", func(t *testing.T) {
848+
t.Parallel()
849+
850+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
851+
user := coderdtest.CreateFirstUser(t, client)
852+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
853+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
854+
855+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
856+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
857+
cwr.RichParameterValues = initialBuildParameters
858+
})
859+
860+
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
861+
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
862+
863+
// Push new template revision
864+
const newImmutableParameterName = "new_immutable_parameter"
865+
const newImmutableParameterDescription = "This is also an immutable parameter"
866+
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
867+
Parse: echo.ParseComplete,
868+
ProvisionPlan: []*proto.Provision_Response{
869+
{
870+
Type: &proto.Provision_Response_Complete{
871+
Complete: &proto.Provision_Complete{
872+
Parameters: []*proto.RichParameter{
873+
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
874+
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
875+
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
876+
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345"},
877+
},
878+
},
879+
},
880+
},
881+
},
882+
ProvisionApply: []*proto.Provision_Response{{
883+
Type: &proto.Provision_Response_Complete{
884+
Complete: &proto.Provision_Complete{},
885+
},
886+
}},
887+
}, template.ID)
888+
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
889+
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
890+
ID: version2.ID,
891+
})
892+
require.NoError(t, err)
893+
894+
// Update build parameters
895+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
896+
defer cancel()
897+
898+
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
899+
{Name: newImmutableParameterName, Value: "good"},
900+
}
901+
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
902+
TemplateVersionID: version2.ID,
903+
Transition: codersdk.WorkspaceTransitionStart,
904+
RichParameterValues: nextBuildParameters,
905+
})
906+
require.NoError(t, err)
907+
})
908+
909+
t.Run("NewImmutableOptionalParameterUsesDefault", func(t *testing.T) {
910+
t.Parallel()
911+
912+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
913+
user := coderdtest.CreateFirstUser(t, client)
914+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, echoResponses)
915+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
916+
917+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
918+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
919+
cwr.RichParameterValues = initialBuildParameters
920+
})
921+
922+
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
923+
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
924+
925+
// Push new template revision
926+
const newImmutableParameterName = "new_immutable_parameter"
927+
const newImmutableParameterDescription = "This is also an immutable parameter"
928+
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
929+
Parse: echo.ParseComplete,
930+
ProvisionPlan: []*proto.Provision_Response{
931+
{
932+
Type: &proto.Provision_Response_Complete{
933+
Complete: &proto.Provision_Complete{
934+
Parameters: []*proto.RichParameter{
935+
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
936+
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
937+
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
938+
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345"},
939+
},
940+
},
941+
},
942+
},
943+
},
944+
ProvisionApply: []*proto.Provision_Response{{
945+
Type: &proto.Provision_Response_Complete{
946+
Complete: &proto.Provision_Complete{},
947+
},
948+
}},
949+
}, template.ID)
950+
coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
951+
err := client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{
952+
ID: version2.ID,
953+
})
954+
require.NoError(t, err)
955+
956+
// Update build parameters
957+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
958+
defer cancel()
959+
960+
var nextBuildParameters []codersdk.WorkspaceBuildParameter
961+
_, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
962+
TemplateVersionID: version2.ID,
963+
Transition: codersdk.WorkspaceTransitionStart,
964+
RichParameterValues: nextBuildParameters,
965+
})
966+
require.NoError(t, err)
967+
})
784968
}
785969

786970
func TestWorkspaceBuildValidateRichParameters(t *testing.T) {

0 commit comments

Comments
 (0)