Skip to content

feat: make dynamic parameters opt-in by default for new templates #19006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
DisplayName: takeFirst(seed.DisplayName, testutil.GetRandomName(t)),
AllowUserCancelWorkspaceJobs: seed.AllowUserCancelWorkspaceJobs,
MaxPortSharingLevel: takeFirst(seed.MaxPortSharingLevel, database.AppSharingLevelOwner),
UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, true),
UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, false),
})
require.NoError(t, err, "insert template")

Expand Down
18 changes: 10 additions & 8 deletions coderd/insights_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,10 +665,11 @@ func TestTemplateInsights_Golden(t *testing.T) {
// where we can control the template ID.
// createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID)
createdTemplate := dbgen.Template(t, db, database.Template{
ID: template.id,
ActiveVersionID: version.ID,
OrganizationID: firstUser.OrganizationID,
CreatedBy: firstUser.UserID,
ID: template.id,
ActiveVersionID: version.ID,
OrganizationID: firstUser.OrganizationID,
CreatedBy: firstUser.UserID,
UseClassicParameterFlow: true,
GroupACL: database.TemplateACL{
firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse),
},
Expand Down Expand Up @@ -1556,10 +1557,11 @@ func TestUserActivityInsights_Golden(t *testing.T) {
// where we can control the template ID.
// createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID)
createdTemplate := dbgen.Template(t, db, database.Template{
ID: template.id,
ActiveVersionID: version.ID,
OrganizationID: firstUser.OrganizationID,
CreatedBy: firstUser.UserID,
ID: template.id,
ActiveVersionID: version.ID,
OrganizationID: firstUser.OrganizationID,
CreatedBy: firstUser.UserID,
UseClassicParameterFlow: true, // Required for parameter usage tracking in this test
GroupACL: database.TemplateACL{
firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse),
},
Expand Down
4 changes: 2 additions & 2 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
return
}

// Default is true until dynamic parameters are promoted to stable.
useClassicParameterFlow := ptr.NilToDefault(createTemplate.UseClassicParameterFlow, true)
// Default is false as dynamic parameters are now the preferred approach.
useClassicParameterFlow := ptr.NilToDefault(createTemplate.UseClassicParameterFlow, false)

Comment on lines +200 to 202
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Breaking-default flip – double-check upstream callers

Switching the default from truefalse silently changes behaviour for every client that omits use_classic_parameter_flow when calling the POST /organizations/:org/templates endpoint.
Please verify that:

  1. All first-party callers (UI, CLI, Terraform provider, integration tests, seed data, etc.) now explicitly set the flag to true when the classic flow is expected.
  2. Public API docs are updated so external consumers are aware of the new default.
  3. No inserts rely on the column’s DB-level default anymore (we pass the value explicitly here, but raw SQL elsewhere might not).

Quick scan helper:


🏁 Script executed:

#!/bin/bash
# Find CreateTemplateRequest literals that do NOT specify UseClassicParameterFlow
rg --type-go -n --pretty $'CreateTemplateRequest\\s*{[^}]*}' | \
  grep -v 'UseClassicParameterFlow'

Length of output: 532


🏁 Script executed:

#!/usr/bin/env bash
# Scan all .go files (excluding vendor/testdata) for CreateTemplateRequest literals missing the UseClassicParameterFlow field
python3 - << 'EOF'
import re, glob, os

for path in glob.glob('**/*.go', recursive=True):
    if any(excl in path for excl in ('vendor/', 'testdata/')):
        continue
    with open(path, 'r', encoding='utf-8') as f:
        src = f.read()
    for m in re.finditer(r'CreateTemplateRequest\s*\{', src):
        # Walk the braces to extract the full literal block
        idx = m.end()
        depth = 1
        while depth > 0 and idx < len(src):
            if src[idx] == '{':
                depth += 1
            elif src[idx] == '}':
                depth -= 1
            idx += 1
        block = src[m.start():idx]
        if 'UseClassicParameterFlow' not in block:
            lineno = src.count('\n', 0, m.start()) + 1
            print(f"{path}:{lineno}")
EOF

Length of output: 542


Explicitly set UseClassicParameterFlow on all CreateTemplateRequest call sites

We flipped the default from truefalse, so any caller that omits UseClassicParameterFlow will now use the new dynamic‐parameter flow. The following literals need to be updated to include UseClassicParameterFlow: ptr.Bool(true) where the classic flow is expected:

• cli/templatecreate.go:152
• cli/templatepush.go:183
• coderd/templates_test.go:99, 116, 132, 150, 183, 219, 242, 262, 312, 362, 393, 419, 435
• coderd/coderdtest/coderdtest.go:969
• codersdk/toolsdk/toolsdk.go:1284
• enterprise/coderd/users_test.go:282

Additionally:

  • Update the public API docs to reflect the new default (false) for use_classic_parameter_flow.
  • Audit any raw‐SQL inserts/migrations to ensure they no longer rely on the old DB default.
🤖 Prompt for AI Agents
In coderd/templates.go around lines 200 to 202, the default for
UseClassicParameterFlow was changed from true to false, affecting all
CreateTemplateRequest call sites. Update all listed call sites in
cli/templatecreate.go, cli/templatepush.go, coderd/templates_test.go,
coderd/coderdtest/coderdtest.go, codersdk/toolsdk/toolsdk.go, and
enterprise/coderd/users_test.go to explicitly set UseClassicParameterFlow:
ptr.Bool(true) where the classic flow is required. Also, update the public API
documentation to show the new default as false and audit any raw-SQL inserts or
migrations to ensure they do not depend on the old default value.

// Make a temporary struct to represent the template. This is used for
// auditing if any of the following checks fail. It will be overwritten when
Expand Down
4 changes: 2 additions & 2 deletions coderd/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestPostTemplateByOrganization(t *testing.T) {
assert.Equal(t, expected.Name, got.Name)
assert.Equal(t, expected.Description, got.Description)
assert.Equal(t, expected.ActivityBumpMillis, got.ActivityBumpMillis)
assert.Equal(t, expected.UseClassicParameterFlow, true) // Current default is true
assert.Equal(t, expected.UseClassicParameterFlow, false) // Current default is false

require.Len(t, auditor.AuditLogs(), 3)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action)
Expand Down Expand Up @@ -1551,7 +1551,7 @@ func TestPatchTemplateMeta(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
require.True(t, template.UseClassicParameterFlow, "default is true")
require.False(t, template.UseClassicParameterFlow, "default is false")

bTrue := true
bFalse := false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { ClassicParameterFlowDeprecationWarning } from "./ClassicParameterFlowDeprecationWarning";

jest.mock("modules/navigation", () => ({
useLinks: () => () => "/mock-link",
linkToTemplate: () => "/mock-template-link",
}));

describe("ClassicParameterFlowDeprecationWarning", () => {
const defaultProps = {
enabled: true,
organizationName: "test-org",
templateName: "test-template",
canUpdateTemplate: true,
};
Comment on lines +10 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix prop naming inconsistency in test setup.

The default props define enabled: true but the test uses isEnabled={true}. This creates confusion about the actual prop interface.

 const defaultProps = {
-  enabled: true,
   organizationName: "test-org",
   templateName: "test-template",
-  canUpdateTemplate: true,
+  isEnabled: true,
 };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx
between lines 10 and 15, the defaultProps object uses the prop name 'enabled'
while the test uses 'isEnabled'. To fix this inconsistency, rename the prop in
defaultProps from 'enabled' to 'isEnabled' so it matches the prop used in the
test, ensuring clarity and consistency in the prop interface.


it("renders warning when enabled and user has template update permissions", () => {
render(
<ClassicParameterFlowDeprecationWarning
{...defaultProps}
isEnabled={true}
/>,
);

expect(screen.getByText("deprecated")).toBeInTheDocument();
expect(screen.getByText("Go to Template Settings")).toBeInTheDocument();
});

it("does not render when enabled is false", () => {
const { container } = render(
<ClassicParameterFlowDeprecationWarning
{...defaultProps}
isEnabled={false}
/>,
);

expect(container.firstChild).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Alert } from "components/Alert/Alert";
import { Link } from "components/Link/Link";
import { linkToTemplate, useLinks } from "modules/navigation";
import type { FC } from "react";
import { docs } from "utils/docs";

interface ClassicParameterFlowDeprecationWarningProps {
organizationName: string;
templateName: string;
isEnabled: boolean;
}

export const ClassicParameterFlowDeprecationWarning: FC<
ClassicParameterFlowDeprecationWarningProps
> = ({ organizationName, templateName, isEnabled }) => {
const getLink = useLinks();

if (!isEnabled) {
return null;
}

const templateSettingsLink = `${getLink(
linkToTemplate(organizationName, templateName),
)}/settings`;

return (
<Alert severity="warning" className="mb-2">
<div>
This template is using the classic parameter flow, which will be{" "}
<strong>deprecated</strong> and removed in a future release. Please
migrate to{" "}
<a
href={docs("/admin/templates/extending-templates/dynamic-parameters")}
className="text-content-link"
>
dynamic parameters
</a>{" "}
on template settings for improved functionality.
</div>

<Link className="text-xs" href={templateSettingsLink}>
Go to Template Settings
</Link>
</Alert>
);
};
23 changes: 22 additions & 1 deletion site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ const CreateWorkspacePage: FC = () => {
}),
enabled: !!templateQuery.data,
});
const templatePermissionsQuery = useQuery({
...checkAuthorization({
checks: {
canUpdateTemplate: {
object: {
resource_type: "template",
resource_id: templateQuery.data?.id ?? "",
},
action: "update",
},
},
}),
enabled: !!templateQuery.data,
});
const realizedVersionId =
customVersionId ?? templateQuery.data?.active_version_id;
const organizationId = templateQuery.data?.organization_id;
Expand All @@ -90,9 +104,13 @@ const CreateWorkspacePage: FC = () => {
const isLoadingFormData =
templateQuery.isLoading ||
permissionsQuery.isLoading ||
templatePermissionsQuery.isLoading ||
richParametersQuery.isLoading;
const loadFormDataError =
templateQuery.error ?? permissionsQuery.error ?? richParametersQuery.error;
templateQuery.error ??
permissionsQuery.error ??
templatePermissionsQuery.error ??
richParametersQuery.error;

const title = autoCreateWorkspaceMutation.isPending
? "Creating workspace..."
Expand Down Expand Up @@ -208,6 +226,9 @@ const CreateWorkspacePage: FC = () => {
startPollingExternalAuth={startPollingExternalAuth}
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
permissions={permissionsQuery.data as CreateWorkspacePermissions}
templatePermissions={
templatePermissionsQuery.data as { canUpdateTemplate: boolean }
}
parameters={realizedParameters as TemplateVersionParameter[]}
presets={templateVersionPresetsQuery.data ?? []}
creatingWorkspace={createWorkspaceMutation.isPending}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const meta: Meta<typeof CreateWorkspacePageView> = {
createWorkspaceForAny: true,
},
onCancel: action("onCancel"),
templatePermissions: { canUpdateTemplate: true },
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Switch } from "components/Switch/Switch";
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
import { type FormikContextType, useFormik } from "formik";
import type { ExternalAuthPollingState } from "hooks/useExternalAuth";
import { ClassicParameterFlowDeprecationWarning } from "modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning";
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import {
Expand Down Expand Up @@ -66,6 +67,7 @@ interface CreateWorkspacePageViewProps {
autofillParameters: AutofillBuildParameter[];
presets: TypesGen.Preset[];
permissions: CreateWorkspacePermissions;
templatePermissions: { canUpdateTemplate: boolean };
creatingWorkspace: boolean;
onCancel: () => void;
onSubmit: (
Expand All @@ -91,6 +93,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
autofillParameters,
presets = [],
permissions,
templatePermissions,
creatingWorkspace,
onSubmit,
onCancel,
Expand Down Expand Up @@ -245,6 +248,12 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
</Stack>
</PageHeader>

<ClassicParameterFlowDeprecationWarning
organizationName={template.organization_name}
templateName={template.name}
isEnabled={templatePermissions.canUpdateTemplate}
/>

<HorizontalForm
name="create-workspace-form"
onSubmit={form.handleSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export const CreateWorkspacePageViewExperimental: FC<
<br />
<Link
href={docs(
"/admin/templates/extending-templates/parameters#enable-dynamic-parameters-early-access",
"/admin/templates/extending-templates/dynamic-parameters",
)}
>
View docs
Expand Down Expand Up @@ -542,7 +542,7 @@ export const CreateWorkspacePageViewExperimental: FC<
parameters cannot be modified once the workspace is created.
<Link
href={docs(
"/admin/templates/extending-templates/parameters#enable-dynamic-parameters-early-access",
"/admin/templates/extending-templates/dynamic-parameters",
)}
>
View docs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,19 +245,20 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
label={
<StackLabel>
<span className="flex flex-row gap-2">
Enable dynamic parameters for workspace creation
Enable dynamic parameters for workspace creation (recommended)
</span>
<StackLabelHelperText>
<div>
The new workspace form allows you to design your template
with new form types and identity-aware conditional
parameters. The form will only present options that are
compatible and available.
The dynamic workspace form allows you to design your
template with additional form types and identity-aware
conditional parameters. This is the default option for new
templates. The classic workspace creation flow will be
deprecated in a future release.
</div>
<Link
className="text-xs"
href={docs(
"/admin/templates/extending-templates/parameters#dynamic-parameters-beta",
"/admin/templates/extending-templates/dynamic-parameters",
)}
>
Learn more
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
import { Spinner } from "components/Spinner/Spinner";
import { useFormik } from "formik";
import { ClassicParameterFlowDeprecationWarning } from "modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning";
import type { FC } from "react";
import { getFormHelpers } from "utils/formUtils";
import {
Expand All @@ -33,6 +34,7 @@ interface WorkspaceParameterFormProps {
autofillParams: AutofillBuildParameter[];
isSubmitting: boolean;
canChangeVersions: boolean;
templatePermissions: { canUpdateTemplate: boolean } | undefined;
error: unknown;
onCancel: () => void;
onSubmit: (values: WorkspaceParametersFormValues) => void;
Expand All @@ -46,6 +48,7 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
autofillParams,
error,
canChangeVersions,
templatePermissions,
isSubmitting,
}) => {
const form = useFormik<WorkspaceParametersFormValues>({
Expand Down Expand Up @@ -81,12 +84,16 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
return (
<>
{disabled && (
<Alert severity="warning" css={{ marginBottom: 48 }}>
<Alert severity="warning">
The template for this workspace requires automatic updates. Update the
workspace to edit parameters.
</Alert>
)}

<ClassicParameterFlowDeprecationWarning
organizationName={workspace.organization_name}
templateName={workspace.template_name}
isEnabled={templatePermissions?.canUpdateTemplate ?? false}
/>
<HorizontalForm onSubmit={form.handleSubmit} data-testid="form">
{hasNonEphemeralParameters && (
<FormSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ const WorkspaceParametersPage: FC = () => {
const permissions = permissionsQuery.data as WorkspacePermissions | undefined;
const canChangeVersions = Boolean(permissions?.updateWorkspaceVersion);

const templatePermissionsQuery = useQuery({
...checkAuthorization({
checks: {
canUpdateTemplate: {
object: {
resource_type: "template",
resource_id: workspace.template_id,
},
action: "update",
},
},
}),
enabled: workspace !== undefined,
});

const templatePermissions = templatePermissionsQuery.data as
| { canUpdateTemplate: boolean }
| undefined;

return (
<>
<Helmet>
Expand All @@ -60,6 +79,7 @@ const WorkspaceParametersPage: FC = () => {
<WorkspaceParametersPageView
workspace={workspace}
canChangeVersions={canChangeVersions}
templatePermissions={templatePermissions}
data={parameters.data}
submitError={updateParameters.error}
isSubmitting={updateParameters.isPending}
Expand Down Expand Up @@ -94,6 +114,7 @@ const WorkspaceParametersPage: FC = () => {
type WorkspaceParametersPageViewProps = {
workspace: Workspace;
canChangeVersions: boolean;
templatePermissions: { canUpdateTemplate: boolean } | undefined;
data: Awaited<ReturnType<typeof API.getWorkspaceParameters>> | undefined;
submitError: unknown;
isSubmitting: boolean;
Expand All @@ -106,6 +127,7 @@ export const WorkspaceParametersPageView: FC<
> = ({
workspace,
canChangeVersions,
templatePermissions,
data,
submitError,
onSubmit,
Expand All @@ -129,6 +151,7 @@ export const WorkspaceParametersPageView: FC<
<WorkspaceParametersForm
workspace={workspace}
canChangeVersions={canChangeVersions}
templatePermissions={templatePermissions}
autofillParams={data.buildParameters.map((p) => ({
...p,
source: "active_build",
Expand Down
Loading