feat: support dynamic parameters on create template request (#18636)

Future work is to add this checkbox to the UI to opt into dynamic
parameters from the first template create.
This commit is contained in:
Steven Masley
2025-07-02 09:44:01 -05:00
committed by GitHub
parent 91aa583ea4
commit 4072d228c5
13 changed files with 72 additions and 40 deletions
+4
View File
@@ -11412,6 +11412,10 @@ const docTemplate = `{
"description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.",
"type": "boolean"
},
"template_use_classic_parameter_flow": {
"description": "UseClassicParameterFlow allows optionally specifying whether\nthe template should use the classic parameter flow. The default if unset is\ntrue, and is why ` + "`" + `*bool` + "`" + ` is used here. When dynamic parameters becomes\nthe default, this will default to false.",
"type": "boolean"
},
"template_version_id": {
"description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.",
"type": "string",
+4
View File
@@ -10170,6 +10170,10 @@
"description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.",
"type": "boolean"
},
"template_use_classic_parameter_flow": {
"description": "UseClassicParameterFlow allows optionally specifying whether\nthe template should use the classic parameter flow. The default if unset is\ntrue, and is why `*bool` is used here. When dynamic parameters becomes\nthe default, this will default to false.",
"type": "boolean"
},
"template_version_id": {
"description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.",
"type": "string",
+13 -10
View File
@@ -50,22 +50,25 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU
})
AwaitTemplateVersionJobCompleted(t, client, version.ID)
tplID := args.TemplateID
if args.TemplateID == uuid.Nil {
tpl := CreateTemplate(t, client, org, version.ID)
tplID = tpl.ID
}
var tpl codersdk.Template
var err error
tpl, err := client.UpdateTemplateMeta(t.Context(), tplID, codersdk.UpdateTemplateMeta{
UseClassicParameterFlow: ptr.Ref(false),
})
require.NoError(t, err)
if args.TemplateID == uuid.Nil {
tpl = CreateTemplate(t, client, org, version.ID, func(request *codersdk.CreateTemplateRequest) {
request.UseClassicParameterFlow = ptr.Ref(false)
})
} else {
tpl, err = client.UpdateTemplateMeta(t.Context(), args.TemplateID, codersdk.UpdateTemplateMeta{
UseClassicParameterFlow: ptr.Ref(false),
})
require.NoError(t, err)
}
err = client.UpdateActiveTemplateVersion(t.Context(), tpl.ID, codersdk.UpdateActiveTemplateVersion{
ID: version.ID,
})
require.NoError(t, err)
require.Equal(t, tpl.UseClassicParameterFlow, false, "template should use dynamic parameters")
return tpl, version
}
+1
View File
@@ -100,6 +100,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),
})
require.NoError(t, err, "insert template")
+1 -1
View File
@@ -9330,7 +9330,7 @@ func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
AllowUserAutostart: true,
AllowUserAutostop: true,
MaxPortSharingLevel: arg.MaxPortSharingLevel,
UseClassicParameterFlow: true,
UseClassicParameterFlow: arg.UseClassicParameterFlow,
}
q.templates = append(q.templates, template)
return nil
+5 -2
View File
@@ -11031,10 +11031,11 @@ INSERT INTO
group_acl,
display_name,
allow_user_cancel_workspace_jobs,
max_port_sharing_level
max_port_sharing_level,
use_classic_parameter_flow
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
`
type InsertTemplateParams struct {
@@ -11053,6 +11054,7 @@ type InsertTemplateParams struct {
DisplayName string `db:"display_name" json:"display_name"`
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
}
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error {
@@ -11072,6 +11074,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
arg.DisplayName,
arg.AllowUserCancelWorkspaceJobs,
arg.MaxPortSharingLevel,
arg.UseClassicParameterFlow,
)
return err
}
+3 -2
View File
@@ -98,10 +98,11 @@ INSERT INTO
group_acl,
display_name,
allow_user_cancel_workspace_jobs,
max_port_sharing_level
max_port_sharing_level,
use_classic_parameter_flow
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);
-- name: UpdateTemplateActiveVersionByID :exec
UPDATE
+11 -6
View File
@@ -197,16 +197,20 @@ 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)
// 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
// the template is inserted into the db.
templateAudit.New = database.Template{
OrganizationID: organization.ID,
Name: createTemplate.Name,
Description: createTemplate.Description,
CreatedBy: apiKey.UserID,
Icon: createTemplate.Icon,
DisplayName: createTemplate.DisplayName,
OrganizationID: organization.ID,
Name: createTemplate.Name,
Description: createTemplate.Description,
CreatedBy: apiKey.UserID,
Icon: createTemplate.Icon,
DisplayName: createTemplate.DisplayName,
UseClassicParameterFlow: useClassicParameterFlow,
}
_, err := api.Database.GetTemplateByOrganizationAndName(ctx, database.GetTemplateByOrganizationAndNameParams{
@@ -404,6 +408,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
Icon: createTemplate.Icon,
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
MaxPortSharingLevel: maxPortShareLevel,
UseClassicParameterFlow: useClassicParameterFlow,
})
if err != nil {
return xerrors.Errorf("insert template: %s", err)
+1
View File
@@ -77,6 +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
require.Len(t, auditor.AuditLogs(), 3)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action)