From 89c717b52138ab011addc8d97b69c6d0b993b047 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Tue, 22 Apr 2025 17:57:22 +0000 Subject: [PATCH 1/2] chore: loosen static validation when using dynamic parameters --- coderd/apidoc/docs.go | 3 ++ coderd/apidoc/swagger.json | 3 ++ coderd/workspaces.go | 3 ++ coderd/wsbuilder/wsbuilder.go | 41 +++++++++++++------ codersdk/organizations.go | 1 + codersdk/richparameters.go | 20 +++++++++ docs/reference/api/schemas.md | 2 + docs/reference/api/workspaces.md | 2 + site/src/api/typesGenerated.ts | 1 + .../CreateWorkspacePageExperimental.tsx | 1 + 10 files changed, 64 insertions(+), 13 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 268cfd7a894ba..62f91a858247d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11453,6 +11453,9 @@ const docTemplate = `{ "autostart_schedule": { "type": "string" }, + "enable_dynamic_parameters": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index e973f11849547..e9e0470462b39 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10211,6 +10211,9 @@ "autostart_schedule": { "type": "string" }, + "enable_dynamic_parameters": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/coderd/workspaces.go b/coderd/workspaces.go index a654597faeadd..995d45ddcfa56 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -676,6 +676,9 @@ func createWorkspace( if req.TemplateVersionID != uuid.Nil { builder = builder.VersionID(req.TemplateVersionID) } + if req.EnableDynamicParameters { + builder = builder.UsingDynamicParameters() + } workspaceBuild, provisionerJob, provisionerDaemons, err = builder.Build( ctx, diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index fa7c00861202d..5ac0f54639a06 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -51,10 +51,11 @@ type Builder struct { logLevel string deploymentValues *codersdk.DeploymentValues - richParameterValues []codersdk.WorkspaceBuildParameter - initiator uuid.UUID - reason database.BuildReason - templateVersionPresetID uuid.UUID + richParameterValues []codersdk.WorkspaceBuildParameter + dynamicParametersEnabled bool + initiator uuid.UUID + reason database.BuildReason + templateVersionPresetID uuid.UUID // used during build, makes function arguments less verbose ctx context.Context @@ -178,6 +179,11 @@ func (b Builder) MarkPrebuild() Builder { return b } +func (b Builder) UsingDynamicParameters() Builder { + b.dynamicParametersEnabled = true + return b +} + // SetLastWorkspaceBuildInTx prepopulates the Builder's cache with the last workspace build. This allows us // to avoid a repeated database query when the Builder's caller also needs the workspace build, e.g. auto-start & // auto-stop. @@ -578,6 +584,7 @@ func (b *Builder) getParameters() (names, values []string, err error) { if err != nil { return nil, nil, BuildError{http.StatusBadRequest, "Unable to build workspace with unsupported parameters", err} } + resolver := codersdk.ParameterResolver{ Rich: db2sdk.WorkspaceBuildParameters(lastBuildParameters), } @@ -586,16 +593,24 @@ func (b *Builder) getParameters() (names, values []string, err error) { if err != nil { return nil, nil, BuildError{http.StatusInternalServerError, "failed to convert template version parameter", err} } - value, err := resolver.ValidateResolve( - tvp, - b.findNewBuildParameterValue(templateVersionParameter.Name), - ) - if err != nil { - // At this point, we've queried all the data we need from the database, - // so the only errors are problems with the request (missing data, failed - // validation, immutable parameters, etc.) - return nil, nil, BuildError{http.StatusBadRequest, fmt.Sprintf("Unable to validate parameter %q", templateVersionParameter.Name), err} + + var value string + if !b.dynamicParametersEnabled { + var err error + value, err = resolver.ValidateResolve( + tvp, + b.findNewBuildParameterValue(templateVersionParameter.Name), + ) + if err != nil { + // At this point, we've queried all the data we need from the database, + // so the only errors are problems with the request (missing data, failed + // validation, immutable parameters, etc.) + return nil, nil, BuildError{http.StatusBadRequest, fmt.Sprintf("Unable to validate parameter %q", templateVersionParameter.Name), err} + } + } else { + value = resolver.Resolve(tvp, b.findNewBuildParameterValue(templateVersionParameter.Name)) } + names = append(names, templateVersionParameter.Name) values = append(values, value) } diff --git a/codersdk/organizations.go b/codersdk/organizations.go index b880f25e15a2c..dd2eab50cf57e 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -227,6 +227,7 @@ type CreateWorkspaceRequest struct { RichParameterValues []WorkspaceBuildParameter `json:"rich_parameter_values,omitempty"` AutomaticUpdates AutomaticUpdates `json:"automatic_updates,omitempty"` TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"` + EnableDynamicParameters bool `json:"enable_dynamic_parameters,omitempty"` } func (c *Client) OrganizationByName(ctx context.Context, name string) (Organization, error) { diff --git a/codersdk/richparameters.go b/codersdk/richparameters.go index 24609bea0e68c..2ddd5d00f6c41 100644 --- a/codersdk/richparameters.go +++ b/codersdk/richparameters.go @@ -190,6 +190,26 @@ func (r *ParameterResolver) ValidateResolve(p TemplateVersionParameter, v *Works return resolvedValue.Value, nil } +// Resolve returns the value of the parameter. It does not do any validation, +// and is meant for use with the new dynamic parameters code path. +func (r *ParameterResolver) Resolve(p TemplateVersionParameter, v *WorkspaceBuildParameter) string { + prevV := r.findLastValue(p) + // First, the provided value + resolvedValue := v + // Second, previous value if not ephemeral + if resolvedValue == nil && !p.Ephemeral { + resolvedValue = prevV + } + // Last, default value + if resolvedValue == nil { + resolvedValue = &WorkspaceBuildParameter{ + Name: p.Name, + Value: p.DefaultValue, + } + } + return resolvedValue.Value +} + // findLastValue finds the value from the previous build and returns it, or nil if the parameter had no value in the // last build. func (r *ParameterResolver) findLastValue(p TemplateVersionParameter) *WorkspaceBuildParameter { diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 79d7a411bf98c..dd8ffd1971cb8 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1462,6 +1462,7 @@ None { "automatic_updates": "always", "autostart_schedule": "string", + "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { @@ -1484,6 +1485,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o |------------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------| | `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | | `autostart_schedule` | string | false | | | +| `enable_dynamic_parameters` | boolean | false | | | | `name` | string | true | | | | `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. | | `template_id` | string | false | | Template ID specifies which template should be used for creating the workspace. | diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 5e727cee297fe..5d09c46a01d30 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -25,6 +25,7 @@ of the template will be used. { "automatic_updates": "always", "autostart_schedule": "string", + "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { @@ -605,6 +606,7 @@ of the template will be used. { "automatic_updates": "always", "autostart_schedule": "string", + "enable_dynamic_parameters": true, "name": "string", "rich_parameter_values": [ { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d160b7683e92e..025ed9f1933cf 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -467,6 +467,7 @@ export interface CreateWorkspaceRequest { readonly rich_parameter_values?: readonly WorkspaceBuildParameter[]; readonly automatic_updates?: AutomaticUpdates; readonly template_version_preset_id?: string; + readonly enable_dynamic_parameters?: boolean; } // From codersdk/deployment.go diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx index 0c513a1733ec9..03da3bd477745 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx @@ -282,6 +282,7 @@ const CreateWorkspacePageExperimental: FC = () => { const workspace = await createWorkspaceMutation.mutateAsync({ ...workspaceRequest, + enable_dynamic_parameters: true, userId: owner.id, }); onCreateWorkspace(workspace); From c9fc648d87c2ada8154830fdf304b83eaf1176c4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 23 Apr 2025 09:59:01 -0500 Subject: [PATCH 2/2] chore: require the experiment enabled as well --- coderd/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 995d45ddcfa56..c1c8b1745c106 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -676,7 +676,7 @@ func createWorkspace( if req.TemplateVersionID != uuid.Nil { builder = builder.VersionID(req.TemplateVersionID) } - if req.EnableDynamicParameters { + if req.EnableDynamicParameters && api.Experiments.Enabled(codersdk.ExperimentDynamicParameters) { builder = builder.UsingDynamicParameters() }