feat!: add a hidden flag to disable dynamic parameter closure evaluation (#29356)

**What changed**
Dynamic parameter rendering now evaluates only the coder parameter blocks and what they reference. Other root-module resources are skipped. Parameter, preset, and tag values are unchanged; optimizing rendering on large templates.

**How to revert**
Set `CODER_DYNAMIC_PARAMETERS_FULL_EVALUATION=true` (or `--dynamic-parameters-full-evaluation`) on `coder server` to restore full evaluation. The flag is hidden and needs no restart of workspaces. If you need it, please [open an issue](https://github.com/coder/coder/issues/new) with the template so we can fix the root cause.

**Troubleshooting**
Symptoms would be a parameter default, option list, preset value, or workspace tag that shows as unknown or empty after upgrading, when it was populated before. Confirm by setting the flag above; if the value comes back, report it. Templates written in `.tf.json` are not affected (they always use full evaluation).

**Why**
Rendering re-ran the entire template graph on every parameter change in the workspace form, which dominated latency on large templates. Resources a workspace would create cannot feed a parameter, so evaluating them is wasted work. The optimization lives in coder/preview#226 (coder/trivy#74); this PR adds the deployment-level escape hatch.

---

- `CODER_DYNAMIC_PARAMETERS_FULL_EVALUATION` / `--dynamic-parameters-full-evaluation` (hidden, default `false`) on `DeploymentValues`.
- `dynamicparameters.PreviewOptions(dv)` maps it to `preview.OptionFullEvaluation()`; `dynamicparameters.WithPreviewOptions` threads it through the loader to `preview.Preview`.
- Wired at the three render paths that hold deployment values: the template version tags extractor, the dynamic parameters websocket, and `wsbuilder` builds created from `coderd` (which call `.DeploymentValues(...)`).

Not covered: the autobuild lifecycle executor and the enterprise prebuilds reconciler construct `wsbuilder` without deployment values, so builds started from those paths use preview's default regardless of the flag. Those only re-render for builds, not the keystroke websocket path the optimization targets.

Prepared with Coder Agents assistance.
This commit is contained in:
Steven Masley
2026-09-17 14:33:37 -05:00
committed by GitHub
parent 86a050de43
commit a90ad83af3
12 changed files with 84 additions and 2 deletions
+5
View File
@@ -505,6 +505,11 @@ provisioning:
# templates cannot opt back in.
# (default: false, type: bool)
disableModuleCache: false
# Evaluate every resource in a template when rendering dynamic parameters, instead
# of only the parameter, preset, and tag blocks and what they reference. Slower,
# and only needed if a template renders incorrectly with the default.
# (default: false, type: bool)
dynamicParametersFullEvaluation: false
# Enable one or more experiments. These are not ready for production. Separate
# multiple experiments with commas, or enter '*' to opt-in to all available
# experiments.
+3
View File
@@ -23154,6 +23154,9 @@ const docTemplate = `{
"docs_url": {
"$ref": "#/definitions/serpent.URL"
},
"dynamic_parameters_full_evaluation": {
"type": "boolean"
},
"enable_authz_recording": {
"type": "boolean"
},
+3
View File
@@ -21047,6 +21047,9 @@
"docs_url": {
"$ref": "#/definitions/serpent.URL"
},
"dynamic_parameters_full_evaluation": {
"type": "boolean"
},
"enable_authz_recording": {
"type": "boolean"
},
+29 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/preview"
previewtypes "github.com/coder/preview/types"
)
@@ -45,6 +46,9 @@ type loader struct {
job *database.ProvisionerJob
terraformValues *database.TemplateVersionTerraformValue
templateVariableValues *[]database.TemplateVersionVariable
// previewOptions are passed through to every preview.Preview call.
previewOptions []preview.Option
}
// Prepare is the entrypoint for this package. It loads the necessary objects &
@@ -90,6 +94,30 @@ func WithTerraformValues(values database.TemplateVersionTerraformValue) func(r *
}
}
// WithPreviewOptions passes options through to preview.Preview. See
// PreviewOptions for the options implied by the deployment configuration.
func WithPreviewOptions(opts ...preview.Option) func(r *loader) {
return func(r *loader) {
r.previewOptions = append(r.previewOptions, opts...)
}
}
// PreviewOptions returns the preview options implied by the deployment
// configuration. A nil configuration yields preview's defaults.
func PreviewOptions(dv *codersdk.DeploymentValues) []preview.Option {
if dv == nil {
return nil
}
var opts []preview.Option
if dv.DynamicParametersFullEvaluation.Value() {
// Escape hatch for preview's resource closure optimization: evaluate
// every resource rather than only what parameters, presets, and tags
// reference.
opts = append(opts, preview.OptionFullEvaluation())
}
return opts
}
func (r *loader) loadData(ctx context.Context, db database.Store) error {
if r.templateVersion == nil {
tv, err := db.GetTemplateVersionByID(ctx, r.templateVersionID)
@@ -257,7 +285,7 @@ func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values
Logger: slog.New(slog.DiscardHandler),
}
return preview.Preview(ctx, input, r.templateFS)
return preview.Preview(ctx, input, r.templateFS, r.data.previewOptions...)
}
func (r *dynamicRenderer) getWorkspaceOwnerData(ctx context.Context, ownerID uuid.UUID) error {
+23
View File
@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/dynamicparameters"
"github.com/coder/coder/v2/codersdk"
)
func TestProvisionerVersionSupportsDynamicParameters(t *testing.T) {
@@ -33,3 +34,25 @@ func TestProvisionerVersionSupportsDynamicParameters(t *testing.T) {
})
}
}
func TestPreviewOptions(t *testing.T) {
t.Parallel()
t.Run("NilConfig", func(t *testing.T) {
t.Parallel()
require.Empty(t, dynamicparameters.PreviewOptions(nil))
})
t.Run("Default", func(t *testing.T) {
t.Parallel()
dv := &codersdk.DeploymentValues{}
require.Empty(t, dynamicparameters.PreviewOptions(dv))
})
t.Run("FullEvaluation", func(t *testing.T) {
t.Parallel()
dv := &codersdk.DeploymentValues{}
require.NoError(t, dv.DynamicParametersFullEvaluation.Set("true"))
require.Len(t, dynamicparameters.PreviewOptions(dv), 1)
})
}
+1
View File
@@ -82,6 +82,7 @@ func (api *API) templateVersionDynamicParameters(listen bool, initial codersdk.D
renderer, err := dynamicparameters.Prepare(ctx, api.Database, api.FileCache, templateVersion.ID,
dynamicparameters.WithTemplateVersion(templateVersion),
dynamicparameters.WithPreviewOptions(dynamicparameters.PreviewOptions(api.DeploymentValues)...),
)
if err != nil {
if httpapi.Is404Error(err) {
+1 -1
View File
@@ -1858,7 +1858,7 @@ func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.Response
Logger: stdslog.New(stdslog.DiscardHandler),
TFVars: tfVarValues,
}
output, diags := preview.Preview(ctx, input, files)
output, diags := preview.Preview(ctx, input, files, dynamicparameters.PreviewOptions(api.DeploymentValues)...)
if output != nil {
// ValidatePrebuilds iterates through the presets and validate their values. This
// ensures the prebuild can actually succeed in a workspace build. The failure
+1
View File
@@ -728,6 +728,7 @@ func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, err
dynamicparameters.WithProvisionerJob(*job),
dynamicparameters.WithTerraformValues(*tfVals),
dynamicparameters.WithTemplateVariableValues(variableValues),
dynamicparameters.WithPreviewOptions(dynamicparameters.PreviewOptions(b.deploymentValues)...),
)
if err != nil {
return nil, xerrors.Errorf("get template version renderer: %w", err)
+13
View File
@@ -738,6 +738,7 @@ type DeploymentValues struct {
DisableUserSecretFilePath serpent.Bool `json:"disable_user_secret_file_path,omitempty" typescript:",notnull"`
ProxyHealthStatusInterval serpent.Duration `json:"proxy_health_status_interval,omitempty" typescript:",notnull"`
EnableTerraformDebugMode serpent.Bool `json:"enable_terraform_debug_mode,omitempty" typescript:",notnull"`
DynamicParametersFullEvaluation serpent.Bool `json:"dynamic_parameters_full_evaluation,omitempty" typescript:",notnull"`
UserQuietHoursSchedule UserQuietHoursScheduleConfig `json:"user_quiet_hours_schedule,omitempty" typescript:",notnull"`
WebTerminalRenderer serpent.String `json:"web_terminal_renderer,omitempty" typescript:",notnull"`
// Deprecated: Use the per-template allow_workspace_renames setting instead.
@@ -3344,6 +3345,18 @@ communicating directly.`,
Group: &deploymentGroupIntrospectionLogging,
YAML: "enableTerraformDebugMode",
},
{
Name: "Dynamic Parameters Full Evaluation",
Description: "Evaluate every resource in a template when rendering dynamic parameters, " +
"instead of only the parameter, preset, and tag blocks and what they reference. " +
"Slower, and only needed if a template renders incorrectly with the default.",
Flag: "dynamic-parameters-full-evaluation",
Env: "CODER_DYNAMIC_PARAMETERS_FULL_EVALUATION",
Default: "false",
Value: &c.DynamicParametersFullEvaluation,
Hidden: true,
YAML: "dynamicParametersFullEvaluation",
},
{
Name: "Additional CSP Policy",
Description: "Coder configures a Content Security Policy (CSP) to protect against XSS attacks. " +
+1
View File
@@ -302,6 +302,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"scheme": "string",
"user": {}
},
"dynamic_parameters_full_evaluation": true,
"enable_authz_recording": true,
"enable_terraform_debug_mode": true,
"ephemeral_deployment": true,
+3
View File
@@ -7384,6 +7384,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"scheme": "string",
"user": {}
},
"dynamic_parameters_full_evaluation": true,
"enable_authz_recording": true,
"enable_terraform_debug_mode": true,
"ephemeral_deployment": true,
@@ -7994,6 +7995,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"scheme": "string",
"user": {}
},
"dynamic_parameters_full_evaluation": true,
"enable_authz_recording": true,
"enable_terraform_debug_mode": true,
"ephemeral_deployment": true,
@@ -8385,6 +8387,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `disable_workspace_agent_context_sync` | boolean | false | | |
| `disable_workspace_sharing` | boolean | false | | |
| `docs_url` | [serpent.URL](#serpenturl) | false | | |
| `dynamic_parameters_full_evaluation` | boolean | false | | |
| `enable_authz_recording` | boolean | false | | |
| `enable_terraform_debug_mode` | boolean | false | | |
| `ephemeral_deployment` | boolean | false | | |
+1
View File
@@ -4774,6 +4774,7 @@ export interface DeploymentValues {
readonly disable_user_secret_file_path?: boolean;
readonly proxy_health_status_interval?: number;
readonly enable_terraform_debug_mode?: boolean;
readonly dynamic_parameters_full_evaluation?: boolean;
readonly user_quiet_hours_schedule?: UserQuietHoursScheduleConfig;
readonly web_terminal_renderer?: string;
/**