From 29995c9ef845877d718bf0121ff579c7ea5c42f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McKayla=20=E3=81=AF=E3=81=AA?= Date: Tue, 25 Aug 2026 11:05:42 -0600 Subject: [PATCH] feat: add a per-template workspace rename setting (#27558) --- cli/testdata/coder_server_--help.golden | 7 - cli/testdata/server-config.yaml.golden | 8 +- coderd/apidoc/docs.go | 14 ++ coderd/apidoc/swagger.json | 14 ++ coderd/database/dbgen/dbgen.go | 1 + coderd/database/dump.sql | 6 +- ..._template_allow_workspace_renames.down.sql | 17 ++ ...86_template_allow_workspace_renames.up.sql | 19 ++ coderd/database/modelqueries.go | 1 + coderd/database/models.go | 3 + coderd/database/queries.sql.go | 26 ++- coderd/database/queries/templates.sql | 8 +- coderd/templatebuilder_handler.go | 1 + coderd/templates.go | 4 + coderd/templates_meta_update.go | 2 + coderd/templates_meta_update_internal_test.go | 9 + coderd/templates_test.go | 32 ++++ coderd/workspaces.go | 35 ++-- coderd/workspaces_test.go | 39 ++++- codersdk/deployment.go | 34 ++-- codersdk/organizations.go | 5 + codersdk/templates.go | 9 + codersdk/workspaces.go | 9 +- docs/admin/security/audit-logs.md | 2 +- docs/admin/setup/configuration-reference.md | 9 - .../resource-persistence.md | 11 +- .../templates/managing-templates/index.md | 43 +++++ docs/reference/api/schemas.md | 165 +++++++++--------- docs/reference/api/templatebuilder.md | 1 + docs/reference/api/templates.md | 62 ++++--- docs/reference/cli/server.md | 11 -- docs/user-guides/workspace-management.md | 4 + enterprise/audit/table.go | 1 + .../cli/testdata/coder_server_--help.golden | 7 - site/src/api/typesGenerated.ts | 26 +++ .../TemplateSettingsForm.test.tsx | 82 +++++++++ .../TemplateSettingsForm.tsx | 36 ++++ .../TemplateSettingsPage.test.tsx | 1 + site/src/testHelpers/entities.ts | 1 + 39 files changed, 574 insertions(+), 191 deletions(-) create mode 100644 coderd/database/migrations/000586_template_allow_workspace_renames.down.sql create mode 100644 coderd/database/migrations/000586_template_allow_workspace_renames.up.sql create mode 100644 site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.test.tsx diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 14df0435ee1..030a18f2bd0 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -17,13 +17,6 @@ SUBCOMMANDS: PostgreSQL deployment. OPTIONS: - --allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false) - Allow users to rename their workspaces. WARNING: Renaming a workspace - can cause Terraform resources that depend on the workspace name to be - destroyed and recreated, potentially causing data loss. Only enable - this if your templates do not use workspace names in resource - identifiers, or if you understand the risks. - --cache-dir string, $CODER_CACHE_DIRECTORY (default: [cache dir]) The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 2324e85ee4e..20b3e71aba9 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -635,10 +635,10 @@ userQuietHoursSchedule: # change their quiet hours schedule and the site default is always used. # (default: true, type: bool) allowCustomQuietHours: true -# Allow users to rename their workspaces. WARNING: Renaming a workspace can cause -# Terraform resources that depend on the workspace name to be destroyed and -# recreated, potentially causing data loss. Only enable this if your templates do -# not use workspace names in resource identifiers, or if you understand the risks. +# Deprecated: use the per-template "Allow workspace renames" setting instead. +# While set, it force-enables renames for every template in the deployment. +# WARNING: Renaming a workspace can cause Terraform resources that depend on the +# workspace name to be destroyed and recreated, potentially causing data loss. # (default: false, type: bool) allowWorkspaceRenames: false # Configure how emails are sent. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 84bdcd7f833..94c41bf60ec 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -20753,6 +20753,10 @@ const docTemplate = `{ "description": "Allow users to cancel in-progress workspace jobs.\n*bool as the default value is \"true\".", "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name, so this defaults to false.", + "type": "boolean" + }, "autostart_requirement": { "description": "AutostartRequirement allows optionally specifying the autostart allowed days\nfor workspaces created from this template. This is an enterprise feature.", "allOf": [ @@ -21678,6 +21682,7 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.AIConfig" }, "allow_workspace_renames": { + "description": "Deprecated: Use the per-template allow_workspace_renames setting instead.", "type": "boolean" }, "autobuild_poll_interval": { @@ -26543,6 +26548,10 @@ const docTemplate = `{ "allow_user_cancel_workspace_jobs": { "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name.", + "type": "boolean" + }, "autostart_requirement": { "$ref": "#/definitions/codersdk.TemplateAutostartRequirement" }, @@ -28102,6 +28111,10 @@ const docTemplate = `{ "allow_user_cancel_workspace_jobs": { "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name.", + "type": "boolean" + }, "autostart_requirement": { "$ref": "#/definitions/codersdk.TemplateAutostartRequirement" }, @@ -29139,6 +29152,7 @@ const docTemplate = `{ "type": "object", "properties": { "allow_renames": { + "description": "AllowRenames is the effective rename permission for this workspace,\nderived from the template's allow_workspace_renames setting and the\ndeprecated deployment-wide flag.", "type": "boolean" }, "automatic_updates": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 03e9e31c92d..4ce1e67d1ec 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -18828,6 +18828,10 @@ "description": "Allow users to cancel in-progress workspace jobs.\n*bool as the default value is \"true\".", "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name, so this defaults to false.", + "type": "boolean" + }, "autostart_requirement": { "description": "AutostartRequirement allows optionally specifying the autostart allowed days\nfor workspaces created from this template. This is an enterprise feature.", "allOf": [ @@ -19717,6 +19721,7 @@ "$ref": "#/definitions/codersdk.AIConfig" }, "allow_workspace_renames": { + "description": "Deprecated: Use the per-template allow_workspace_renames setting instead.", "type": "boolean" }, "autobuild_poll_interval": { @@ -24405,6 +24410,10 @@ "allow_user_cancel_workspace_jobs": { "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name.", + "type": "boolean" + }, "autostart_requirement": { "$ref": "#/definitions/codersdk.TemplateAutostartRequirement" }, @@ -25876,6 +25885,10 @@ "allow_user_cancel_workspace_jobs": { "type": "boolean" }, + "allow_workspace_renames": { + "description": "AllowWorkspaceRenames permits users to rename workspaces built from this\ntemplate. Renaming can be destructive for templates whose Terraform\nreferences the workspace name.", + "type": "boolean" + }, "autostart_requirement": { "$ref": "#/definitions/codersdk.TemplateAutostartRequirement" }, @@ -26862,6 +26875,7 @@ "type": "object", "properties": { "allow_renames": { + "description": "AllowRenames is the effective rename permission for this workspace,\nderived from the template's allow_workspace_renames setting and the\ndeprecated deployment-wide flag.", "type": "boolean" }, "automatic_updates": { diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index e54bc35ef22..e52b2dc8803 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -596,6 +596,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database. UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, false), CorsBehavior: takeFirst(seed.CorsBehavior, database.CorsBehaviorSimple), AgentsAllowed: seed.AgentsAllowed, + AllowWorkspaceRenames: seed.AllowWorkspaceRenames, }) require.NoError(t, err, "insert template") diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 123388aea42..03b68c8d56f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -3571,7 +3571,8 @@ CREATE TABLE templates ( cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL, disable_module_cache boolean DEFAULT false NOT NULL, time_til_autostop_notify bigint DEFAULT 0 NOT NULL, - agents_allowed boolean DEFAULT true NOT NULL + agents_allowed boolean DEFAULT true NOT NULL, + allow_workspace_renames boolean DEFAULT false NOT NULL ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -3598,6 +3599,8 @@ COMMENT ON COLUMN templates.time_til_autostop_notify IS 'How long before the wor COMMENT ON COLUMN templates.agents_allowed IS 'Whether Coder Agents can create workspaces using this template.'; +COMMENT ON COLUMN templates.allow_workspace_renames IS 'Whether workspaces built from this template may be renamed. Renaming can be destructive for templates whose Terraform references the workspace name.'; + CREATE VIEW template_with_names AS SELECT templates.id, templates.created_at, @@ -3632,6 +3635,7 @@ CREATE VIEW template_with_names AS templates.disable_module_cache, templates.time_til_autostop_notify, templates.agents_allowed, + templates.allow_workspace_renames, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, COALESCE(visible_users.name, ''::text) AS created_by_name, diff --git a/coderd/database/migrations/000586_template_allow_workspace_renames.down.sql b/coderd/database/migrations/000586_template_allow_workspace_renames.down.sql new file mode 100644 index 00000000000..57441168043 --- /dev/null +++ b/coderd/database/migrations/000586_template_allow_workspace_renames.down.sql @@ -0,0 +1,17 @@ +DROP VIEW template_with_names; + +ALTER TABLE templates DROP COLUMN allow_workspace_renames; + +CREATE VIEW template_with_names AS +SELECT templates.*, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon +FROM ((templates + LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))) + LEFT JOIN organizations ON ((templates.organization_id = organizations.id))); + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000586_template_allow_workspace_renames.up.sql b/coderd/database/migrations/000586_template_allow_workspace_renames.up.sql new file mode 100644 index 00000000000..7c7de353601 --- /dev/null +++ b/coderd/database/migrations/000586_template_allow_workspace_renames.up.sql @@ -0,0 +1,19 @@ +ALTER TABLE templates ADD COLUMN allow_workspace_renames boolean DEFAULT false NOT NULL; + +COMMENT ON COLUMN templates.allow_workspace_renames IS 'Whether workspaces built from this template may be renamed. Renaming can be destructive for templates whose Terraform references the workspace name.'; + +DROP VIEW template_with_names; + +CREATE VIEW template_with_names AS +SELECT templates.*, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon +FROM ((templates + LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))) + LEFT JOIN organizations ON ((templates.organization_id = organizations.id))); + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 23956a7ab40..8345b8bd5a4 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -192,6 +192,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.DisableModuleCache, &i.TimeTilAutostopNotify, &i.AgentsAllowed, + &i.AllowWorkspaceRenames, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, diff --git a/coderd/database/models.go b/coderd/database/models.go index 36378b125f0..91b8c9f2b86 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -6112,6 +6112,7 @@ type Template struct { DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"` TimeTilAutostopNotify int64 `db:"time_til_autostop_notify" json:"time_til_autostop_notify"` AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"` + AllowWorkspaceRenames bool `db:"allow_workspace_renames" json:"allow_workspace_renames"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` CreatedByName string `db:"created_by_name" json:"created_by_name"` @@ -6166,6 +6167,8 @@ type TemplateTable struct { TimeTilAutostopNotify int64 `db:"time_til_autostop_notify" json:"time_til_autostop_notify"` // Whether Coder Agents can create workspaces using this template. AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"` + // Whether workspaces built from this template may be renamed. Renaming can be destructive for templates whose Terraform references the workspace name. + AllowWorkspaceRenames bool `db:"allow_workspace_renames" json:"allow_workspace_renames"` } // Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e111e2683e0..904e6763d0d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -27674,7 +27674,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, templateID const getTemplateByID = `-- name: GetTemplateByID :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, allow_workspace_renames, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names WHERE @@ -27720,6 +27720,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.DisableModuleCache, &i.TimeTilAutostopNotify, &i.AgentsAllowed, + &i.AllowWorkspaceRenames, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -27732,7 +27733,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, allow_workspace_renames, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates WHERE @@ -27786,6 +27787,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.DisableModuleCache, &i.TimeTilAutostopNotify, &i.AgentsAllowed, + &i.AllowWorkspaceRenames, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -27797,7 +27799,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many -SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates +SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, allow_workspace_renames, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates ORDER BY (name, id) ASC ` @@ -27844,6 +27846,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.DisableModuleCache, &i.TimeTilAutostopNotify, &i.AgentsAllowed, + &i.AllowWorkspaceRenames, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -27866,7 +27869,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT - t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.cors_behavior, t.disable_module_cache, t.time_til_autostop_notify, t.agents_allowed, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon + t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.cors_behavior, t.disable_module_cache, t.time_til_autostop_notify, t.agents_allowed, t.allow_workspace_renames, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon FROM template_with_names AS t LEFT JOIN @@ -28036,6 +28039,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.DisableModuleCache, &i.TimeTilAutostopNotify, &i.AgentsAllowed, + &i.AllowWorkspaceRenames, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -28076,10 +28080,11 @@ INSERT INTO max_port_sharing_level, use_classic_parameter_flow, cors_behavior, - agents_allowed + agents_allowed, + allow_workspace_renames ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) ` type InsertTemplateParams struct { @@ -28101,6 +28106,7 @@ type InsertTemplateParams struct { UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"` + AllowWorkspaceRenames bool `db:"allow_workspace_renames" json:"allow_workspace_renames"` } func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error { @@ -28123,6 +28129,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam arg.UseClassicParameterFlow, arg.CorsBehavior, arg.AgentsAllowed, + arg.AllowWorkspaceRenames, ) return err } @@ -28226,7 +28233,8 @@ SET use_classic_parameter_flow = $10, cors_behavior = $11, disable_module_cache = $12, - agents_allowed = $13 + agents_allowed = $13, + allow_workspace_renames = $14 WHERE id = $1 ` @@ -28245,6 +28253,7 @@ type UpdateTemplateMetaByIDParams struct { CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"` AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"` + AllowWorkspaceRenames bool `db:"allow_workspace_renames" json:"allow_workspace_renames"` } func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error { @@ -28262,6 +28271,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl arg.CorsBehavior, arg.DisableModuleCache, arg.AgentsAllowed, + arg.AllowWorkspaceRenames, ) return err } @@ -39515,7 +39525,7 @@ LEFT JOIN LATERAL ( ) latest_build ON TRUE LEFT JOIN LATERAL ( SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, disable_module_cache, time_til_autostop_notify, agents_allowed, allow_workspace_renames FROM templates WHERE diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index aea8169094d..ba483608644 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -144,10 +144,11 @@ INSERT INTO max_port_sharing_level, use_classic_parameter_flow, cors_behavior, - agents_allowed + agents_allowed, + allow_workspace_renames ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18); + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19); -- name: UpdateTemplateActiveVersionByID :exec UPDATE @@ -182,7 +183,8 @@ SET use_classic_parameter_flow = $10, cors_behavior = $11, disable_module_cache = $12, - agents_allowed = $13 + agents_allowed = $13, + allow_workspace_renames = $14 WHERE id = $1 ; diff --git a/coderd/templatebuilder_handler.go b/coderd/templatebuilder_handler.go index dc939a9c8bb..953401d5c63 100644 --- a/coderd/templatebuilder_handler.go +++ b/coderd/templatebuilder_handler.go @@ -556,6 +556,7 @@ func (api *API) templateBuilderCreateTemplate(rw http.ResponseWriter, r *http.Re UseClassicParameterFlow: false, CorsBehavior: database.CorsBehaviorSimple, AgentsAllowed: true, + AllowWorkspaceRenames: false, }) if err != nil { if database.IsUniqueViolation(err, database.UniqueTemplatesOrganizationIDNameIndex) { diff --git a/coderd/templates.go b/coderd/templates.go index c8a48a0b5e4..fe84d26e827 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -420,6 +420,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque allowUserCancelWorkspaceJobs = ptr.NilToDefault(createTemplate.AllowUserCancelWorkspaceJobs, false) allowUserAutostart = ptr.NilToDefault(createTemplate.AllowUserAutostart, true) allowUserAutostop = ptr.NilToDefault(createTemplate.AllowUserAutostop, true) + allowWorkspaceRenames = ptr.NilToDefault(createTemplate.AllowWorkspaceRenames, false) ) defaultsGroups := database.TemplateACL{} @@ -450,6 +451,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque UseClassicParameterFlow: useClassicParameterFlow, CorsBehavior: corsBehavior, AgentsAllowed: agentsAllowed, + AllowWorkspaceRenames: allowWorkspaceRenames, }) if err != nil { return xerrors.Errorf("insert template: %s", err) @@ -782,6 +784,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { CorsBehavior: resolved.corsBehavior, DisableModuleCache: resolved.disableModuleCache, AgentsAllowed: resolved.agentsAllowed, + AllowWorkspaceRenames: resolved.allowWorkspaceRenames, }) if err != nil { return xerrors.Errorf("update template metadata: %w", err) @@ -1059,6 +1062,7 @@ func (api *API) convertTemplate( CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior), DisableModuleCache: template.DisableModuleCache, AgentsAllowed: template.AgentsAllowed, + AllowWorkspaceRenames: template.AllowWorkspaceRenames, } } diff --git a/coderd/templates_meta_update.go b/coderd/templates_meta_update.go index 2a16505629b..b1d44878cdb 100644 --- a/coderd/templates_meta_update.go +++ b/coderd/templates_meta_update.go @@ -34,6 +34,7 @@ type templateMetaUpdate struct { deprecationMessage string useClassicTemplateFlow bool disableModuleCache bool + allowWorkspaceRenames bool corsBehavior database.CorsBehavior autostopRequirementDaysOfWeekParsed uint8 autostartRequirementDaysOfWeekParsed uint8 @@ -87,6 +88,7 @@ func resolveTemplateMetaUpdate( deprecationMessage: ptr.NilToDefault(req.DeprecationMessage, template.Deprecated), useClassicTemplateFlow: ptr.NilToDefault(req.UseClassicParameterFlow, template.UseClassicParameterFlow), disableModuleCache: ptr.NilToDefault(req.DisableModuleCache, template.DisableModuleCache), + allowWorkspaceRenames: ptr.NilToDefault(req.AllowWorkspaceRenames, template.AllowWorkspaceRenames), groupACL: template.GroupACL, // Default to the original values diff --git a/coderd/templates_meta_update_internal_test.go b/coderd/templates_meta_update_internal_test.go index 8afb3418cd2..1889e7d833a 100644 --- a/coderd/templates_meta_update_internal_test.go +++ b/coderd/templates_meta_update_internal_test.go @@ -44,6 +44,7 @@ func baselineTemplate() database.Template { UseClassicParameterFlow: true, CorsBehavior: database.CorsBehaviorPassthru, DisableModuleCache: true, + AllowWorkspaceRenames: true, GroupACL: database.TemplateACL{ orgID.String(): {"read"}, }, @@ -87,6 +88,7 @@ func baselineResolved() templateMetaUpdate { deprecationMessage: tpl.Deprecated, useClassicTemplateFlow: tpl.UseClassicParameterFlow, disableModuleCache: tpl.DisableModuleCache, + allowWorkspaceRenames: tpl.AllowWorkspaceRenames, corsBehavior: tpl.CorsBehavior, autostopRequirementDaysOfWeekParsed: 0b0000001, autostartRequirementDaysOfWeekParsed: 0b1000000, @@ -290,6 +292,13 @@ func TestResolveTemplateMetaUpdate(t *testing.T) { r.disableModuleCache = false }}, }, + { + name: "AllowWorkspaceRenames", + req: codersdk.UpdateTemplateMeta{AllowWorkspaceRenames: ptr.Ref(false)}, + expected: expected{override: func(r *templateMetaUpdate) { + r.allowWorkspaceRenames = false + }}, + }, // CORS behavior. { diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 0b716c44da6..228a939d3f0 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -1844,6 +1844,38 @@ func TestPatchTemplateMeta(t *testing.T) { assert.False(t, updated.DisableModuleCache, "expected false") }) + t.Run("AllowWorkspaceRenames", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + require.False(t, template.AllowWorkspaceRenames, "default is false") + + ctx := testutil.Context(t, testutil.WaitLong) + + req := codersdk.UpdateTemplateMeta{ + AllowWorkspaceRenames: ptr.Ref(true), + } + updated, err := client.UpdateTemplateMeta(ctx, template.ID, req) + require.NoError(t, err) + assert.True(t, updated.AllowWorkspaceRenames, "expected true") + + // Omitting the field preserves the existing value. + req.AllowWorkspaceRenames = nil + _, err = client.UpdateTemplateMeta(ctx, template.ID, req) + require.NoError(t, err) + updated, err = client.Template(ctx, template.ID) + require.NoError(t, err) + assert.True(t, updated.AllowWorkspaceRenames, "expected true") + + req.AllowWorkspaceRenames = ptr.Ref(false) + updated, err = client.UpdateTemplateMeta(ctx, template.ID, req) + require.NoError(t, err) + assert.False(t, updated.AllowWorkspaceRenames, "expected false") + }) + t.Run("SupportEmptyOrDefaultFields", func(t *testing.T) { t.Parallel() diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 6502740cc2a..c554394bf44 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1154,9 +1154,17 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { // patched in the future, it's enough if one changes. name := workspace.Name if req.Name != "" || req.Name != workspace.Name { - if !api.AllowWorkspaceRenames { + template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace template.", + Detail: err.Error(), + }) + return + } + if !api.AllowWorkspaceRenames && !template.AllowWorkspaceRenames { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Workspace renames are not allowed.", + Message: "Workspace renames are not enabled for this template.", }) return } @@ -2596,10 +2604,11 @@ func (api *API) patchWorkspaceACL(rw http.ResponseWriter, r *http.Request) { } type workspaceData struct { - templates []database.Template - builds []codersdk.WorkspaceBuild - appStatuses []codersdk.WorkspaceAppStatus - allowRenames bool + templates []database.Template + builds []codersdk.WorkspaceBuild + appStatuses []codersdk.WorkspaceAppStatus + // deploymentAllowsRenames is the deprecated deployment-wide rename flag. + deploymentAllowsRenames bool } // @Summary Completely clears the workspace's user and group ACLs. @@ -2757,10 +2766,10 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa } return workspaceData{ - templates: templates, - appStatuses: db2sdk.WorkspaceAppStatuses(appStatuses), - builds: apiBuilds, - allowRenames: api.AllowWorkspaceRenames, + templates: templates, + appStatuses: db2sdk.WorkspaceAppStatuses(appStatuses), + builds: apiBuilds, + deploymentAllowsRenames: api.AllowWorkspaceRenames, }, nil } @@ -2846,7 +2855,7 @@ func convertWorkspaces( workspace, build, template, - data.allowRenames, + data.deploymentAllowsRenames, appStatus, ) if err != nil { @@ -2865,7 +2874,7 @@ func convertWorkspace( workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, template database.Template, - allowRenames bool, + deploymentAllowsRenames bool, latestAppStatus codersdk.WorkspaceAppStatus, ) (codersdk.Workspace, error) { if requesterID == uuid.Nil { @@ -2953,7 +2962,7 @@ func convertWorkspace( FailingAgents: failingAgents, }, AutomaticUpdates: codersdk.AutomaticUpdates(workspace.AutomaticUpdates), - AllowRenames: allowRenames, + AllowRenames: deploymentAllowsRenames || template.AllowWorkspaceRenames, Favorite: requesterFavorite, NextStartAt: nextStartAt, IsPrebuild: workspace.IsPrebuild(), diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index e84e633e444..d1106c66b50 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -220,7 +220,44 @@ func TestWorkspace(t *testing.T) { err := client.UpdateWorkspace(ctx, ws1.ID, codersdk.UpdateWorkspaceRequest{ Name: want, }) - require.ErrorContains(t, err, "Workspace renames are not allowed") + require.ErrorContains(t, err, "Workspace renames are not enabled for this template") + }) + + t.Run("RenameAllowedByTemplate", func(t *testing.T) { + t.Parallel() + // The deployment-wide flag is off. The template setting alone must be + // enough to permit renames. + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + AllowWorkspaceRenames: false, + }) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(req *codersdk.CreateTemplateRequest) { + req.AllowWorkspaceRenames = ptr.Ref(true) + }) + ws1 := coderdtest.CreateWorkspace(t, client, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws1.LatestBuild.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) + defer cancel() + + require.True(t, template.AllowWorkspaceRenames) + + fetched, err := client.Workspace(ctx, ws1.ID) + require.NoError(t, err) + require.True(t, fetched.AllowRenames) + + const want = "new-name" + err = client.UpdateWorkspace(ctx, ws1.ID, codersdk.UpdateWorkspaceRequest{ + Name: want, + }) + require.NoError(t, err, "workspace rename failed") + + ws, err := client.Workspace(ctx, ws1.ID) + require.NoError(t, err) + require.Equal(t, want, ws.Name) }) t.Run("TemplateProperties", func(t *testing.T) { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index cc0c77fba66..2782ce982a1 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -741,19 +741,20 @@ type DeploymentValues struct { EnableTerraformDebugMode serpent.Bool `json:"enable_terraform_debug_mode,omitempty" typescript:",notnull"` UserQuietHoursSchedule UserQuietHoursScheduleConfig `json:"user_quiet_hours_schedule,omitempty" typescript:",notnull"` WebTerminalRenderer serpent.String `json:"web_terminal_renderer,omitempty" typescript:",notnull"` - AllowWorkspaceRenames serpent.Bool `json:"allow_workspace_renames,omitempty" typescript:",notnull"` - Healthcheck HealthcheckConfig `json:"healthcheck,omitempty" typescript:",notnull"` - Retention RetentionConfig `json:"retention,omitempty" typescript:",notnull"` - CLIUpgradeMessage serpent.String `json:"cli_upgrade_message,omitempty" typescript:",notnull"` - TermsOfServiceURL serpent.String `json:"terms_of_service_url,omitempty" typescript:",notnull"` - Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"` - AdditionalCSPPolicy serpent.StringArray `json:"additional_csp_policy,omitempty" typescript:",notnull"` - WorkspaceHostnameSuffix serpent.String `json:"workspace_hostname_suffix,omitempty" typescript:",notnull"` - Prebuilds PrebuildsConfig `json:"workspace_prebuilds,omitempty" typescript:",notnull"` - EnableAITasks serpent.Bool `json:"enable_ai_tasks,omitempty" typescript:",notnull"` - AI AIConfig `json:"ai,omitempty"` - StatsCollection StatsCollectionConfig `json:"stats_collection,omitempty" typescript:",notnull"` - TemplateBuilder TemplateBuilderConfig `json:"template_builder,omitempty"` + // Deprecated: Use the per-template allow_workspace_renames setting instead. + AllowWorkspaceRenames serpent.Bool `json:"allow_workspace_renames,omitempty" typescript:",notnull"` + Healthcheck HealthcheckConfig `json:"healthcheck,omitempty" typescript:",notnull"` + Retention RetentionConfig `json:"retention,omitempty" typescript:",notnull"` + CLIUpgradeMessage serpent.String `json:"cli_upgrade_message,omitempty" typescript:",notnull"` + TermsOfServiceURL serpent.String `json:"terms_of_service_url,omitempty" typescript:",notnull"` + Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"` + AdditionalCSPPolicy serpent.StringArray `json:"additional_csp_policy,omitempty" typescript:",notnull"` + WorkspaceHostnameSuffix serpent.String `json:"workspace_hostname_suffix,omitempty" typescript:",notnull"` + Prebuilds PrebuildsConfig `json:"workspace_prebuilds,omitempty" typescript:",notnull"` + EnableAITasks serpent.Bool `json:"enable_ai_tasks,omitempty" typescript:",notnull"` + AI AIConfig `json:"ai,omitempty"` + StatsCollection StatsCollectionConfig `json:"stats_collection,omitempty" typescript:",notnull"` + TemplateBuilder TemplateBuilderConfig `json:"template_builder,omitempty"` Config serpent.YAMLConfigPath `json:"config,omitempty" typescript:",notnull"` WriteConfig serpent.Bool `json:"write_config,omitempty" typescript:",notnull"` @@ -3962,13 +3963,14 @@ Write out the current server config as YAML to stdout.`, }, { Name: "Allow Workspace Renames", - Description: "Allow users to rename their workspaces. " + + Description: "Deprecated: use the per-template \"Allow workspace renames\" setting instead. " + + "While set, it force-enables renames for every template in the deployment. " + "WARNING: Renaming a workspace can cause Terraform resources that depend on the " + - "workspace name to be destroyed and recreated, potentially causing data loss. " + - "Only enable this if your templates do not use workspace names in resource identifiers, or if you understand the risks.", + "workspace name to be destroyed and recreated, potentially causing data loss.", Flag: "allow-workspace-renames", Env: "CODER_ALLOW_WORKSPACE_RENAMES", Default: "false", + Hidden: true, Value: &c.AllowWorkspaceRenames, YAML: "allowWorkspaceRenames", }, diff --git a/codersdk/organizations.go b/codersdk/organizations.go index d50577be171..c56ef51ef42 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -233,6 +233,11 @@ type CreateTemplateRequest struct { // AgentsAllowed controls whether Coder Agents can create workspaces using // this template. Defaults to true. AgentsAllowed *bool `json:"agents_allowed,omitempty"` + + // AllowWorkspaceRenames permits users to rename workspaces built from this + // template. Renaming can be destructive for templates whose Terraform + // references the workspace name, so this defaults to false. + AllowWorkspaceRenames *bool `json:"allow_workspace_renames,omitempty"` } // CreateWorkspaceRequest provides options for creating a new workspace. diff --git a/codersdk/templates.go b/codersdk/templates.go index 4c382b6ae46..a5610747825 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -73,6 +73,11 @@ type Template struct { // DisableModuleCache disables the use of cached Terraform modules during // provisioning. DisableModuleCache bool `json:"disable_module_cache"` + + // AllowWorkspaceRenames permits users to rename workspaces built from this + // template. Renaming can be destructive for templates whose Terraform + // references the workspace name. + AllowWorkspaceRenames bool `json:"allow_workspace_renames"` } // WeekdaysToBitmap converts a list of weekdays to a bitmap in accordance with @@ -286,6 +291,10 @@ type UpdateTemplateMeta struct { // AgentsAllowed controls whether Coder Agents can create workspaces using // this template. If omitted, the current value is preserved. AgentsAllowed *bool `json:"agents_allowed,omitempty"` + // AllowWorkspaceRenames permits users to rename workspaces built from this + // template. Renaming can be destructive for templates whose Terraform + // references the workspace name. + AllowWorkspaceRenames *bool `json:"allow_workspace_renames,omitempty"` } type TemplateExample struct { diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 94ab3dee86e..c17d14619ef 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -66,9 +66,12 @@ type Workspace struct { // what is causing an unhealthy status. Health WorkspaceHealth `json:"health"` AutomaticUpdates AutomaticUpdates `json:"automatic_updates" enums:"always,never"` - AllowRenames bool `json:"allow_renames"` - Favorite bool `json:"favorite"` - NextStartAt *time.Time `json:"next_start_at" format:"date-time"` + // AllowRenames is the effective rename permission for this workspace, + // derived from the template's allow_workspace_renames setting and the + // deprecated deployment-wide flag. + AllowRenames bool `json:"allow_renames"` + Favorite bool `json:"favorite"` + NextStartAt *time.Time `json:"next_start_at" format:"date-time"` // IsPrebuild indicates whether the workspace is a prebuilt workspace. // Prebuilt workspaces are owned by the prebuilds system user and have specific behavior, // such as being managed differently from regular workspaces. diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 214cc2cb0de..ef8022988d2 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -45,7 +45,7 @@ We track the following resources: | PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| | RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| | TaskTable
| |
FieldTracked
created_atfalse
deleted_atfalse
display_nametrue
idtrue
nametrue
organization_idfalse
owner_idtrue
prompttrue
template_parameterstrue
template_version_idtrue
workspace_idtrue
| -| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
agents_allowedtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
disable_module_cachetrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_autostop_notifytrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| +| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
agents_allowedtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
allow_workspace_renamestrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
disable_module_cachetrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_autostop_notifytrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| | TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
chat_spend_limit_microstrue
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_service_accounttrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| | UserSecret
create, write, delete | |
FieldTracked
created_atfalse
descriptiontrue
enabledtrue
env_nametrue
file_pathtrue
idtrue
nametrue
updated_atfalse
user_idtrue
valuetrue
value_key_idfalse
| diff --git a/docs/admin/setup/configuration-reference.md b/docs/admin/setup/configuration-reference.md index 75d8ea58873..cf997e98e0e 100644 --- a/docs/admin/setup/configuration-reference.md +++ b/docs/admin/setup/configuration-reference.md @@ -22,15 +22,6 @@ Deprecated options are listed at the end of each section. ## General -### Allow workspace renames - -Allow users to rename their workspaces. WARNING: Renaming a workspace can cause Terraform resources that depend on the workspace name to be destroyed and recreated, potentially causing data loss. Only enable this if your templates do not use workspace names in resource identifiers, or if you understand the risks. - -- Environment variable: `CODER_ALLOW_WORKSPACE_RENAMES` -- CLI flag: [`--allow-workspace-renames`](../../reference/cli/server.md#--allow-workspace-renames) -- YAML key: `allowWorkspaceRenames` -- Default value: `false` - ### Cache directory The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. This directory is NOT safe to be configured as a shared directory across coderd/provisionerd replicas. diff --git a/docs/admin/templates/extending-templates/resource-persistence.md b/docs/admin/templates/extending-templates/resource-persistence.md index a0ccbeea606..e8034106313 100644 --- a/docs/admin/templates/extending-templates/resource-persistence.md +++ b/docs/admin/templates/extending-templates/resource-persistence.md @@ -52,12 +52,19 @@ Because we depend on `coder_workspace.me.owner`, if the owner changes their username, Terraform will recreate the volume (wiping its data!) the next time that Coder starts the workspace. -To prevent this, use immutable IDs: +✅ To prevent this, use immutable IDs: - `coder_workspace.me.owner_id` - `coder_workspace.me.id` -You should also avoid using `coder_workspace.me.name` if your deployment allows workspace renaming via `CODER_ALLOW_WORKSPACE_RENAMES` or `--allow-workspace-renames`. +⚠️ Avoid mutable IDs: + +- `coder_workspace.me.name` +- `coder_workspace.me.owner` + +Workspace renaming is disabled by default to help prevent this kind of +destruction, but a template admin can enable it. Refer to +[Workspace renaming](../managing-templates/index.md#workspace-renaming). ```tf data "coder_workspace" "me" { diff --git a/docs/admin/templates/managing-templates/index.md b/docs/admin/templates/managing-templates/index.md index 413b99adc19..4c6f984abbe 100644 --- a/docs/admin/templates/managing-templates/index.md +++ b/docs/admin/templates/managing-templates/index.md @@ -77,6 +77,49 @@ automatically updated on the next startup. ![Template update policies](../../../images/templates/update-policies.png) +## Workspace renaming + +Workspace renaming is disabled by default on every template. +Enable it per template, once you have confirmed that a rename won't destroy anything. + +Terraform exposes the workspace name as `data.coder_workspace.me.name`. +If a template uses that value in an attribute Terraform can't change in place, renaming the workspace makes Terraform destroy the resource and create a new one on the next build. +When that resource is the home volume, the developer loses their data. + +Other uses of the name are harmless. +A Kubernetes label or an environment variable changes without replacing anything. +Coder can't tell the two cases apart, so the decision stays with the template admin. + +### Check whether your template is safe to rename + +Search the template for uses of the workspace name: + +```sh +grep -rn 'coder_workspace\.[a-z_]*\.name' . +``` + +For each result, ask whether Terraform would replace the resource if that value changed. +Attributes such as a volume name, a disk name, or an instance name usually force replacement. +Labels, tags, and environment variables usually don't. + +If a resource would be replaced, reference an immutable identifier instead, such as `data.coder_workspace.me.id`. +Refer to [Resource persistence](../extending-templates/resource-persistence.md) for the full set of practices. + +### Enable renaming for a template + +1. Go to the template, then select **Settings**. +1. On the **General** page, under **Operations**, select **Allow users to rename their workspaces**. +1. Select **Save**. + +Developers can then rename a workspace from the workspace's **Settings** page, or with `coder rename`. + +While the setting is off, the workspace name field is disabled and the API rejects renames. + +> [!WARNING] +> The deployment-wide `CODER_ALLOW_WORKSPACE_RENAMES` option is deprecated. +> While it is set, renaming is enabled for every template in the deployment, and the per-template setting can't turn it off. +> Unset it, then enable renaming on the templates that need it. + ## Delete templates You can delete a template using both the coder CLI and UI. Only diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 04849e8cfc6..e8b89c5c593 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -6449,6 +6449,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -6487,6 +6488,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `allow_user_autostart` | boolean | false | | Allow user autostart allows users to set a schedule for autostarting their workspace. By default this is true. This can only be disabled when using an enterprise license. | | `allow_user_autostop` | boolean | false | | Allow user autostop allows users to set a custom workspace TTL to use in place of the template's DefaultTTL field. By default this is true. If false, the DefaultTTL will always be used. This can only be disabled when using an enterprise license. | | `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. *bool as the default value is "true". | +| `allow_workspace_renames` | boolean | false | | Allow workspace renames permits users to rename workspaces built from this template. Renaming can be destructive for templates whose Terraform references the workspace name, so this defaults to false. | | `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | Autostart requirement allows optionally specifying the autostart allowed days for workspaces created from this template. This is an enterprise feature. | | `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement allows optionally specifying the autostop requirement for workspaces created from this template. This is an enterprise feature. | | `cors_behavior` | [codersdk.CORSBehavior](#codersdkcorsbehavior) | false | | Cors behavior allows optionally specifying the CORS behavior for all shared ports. | @@ -8421,84 +8423,84 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o ### Properties -| Name | Type | Required | Restrictions | Description | -|------------------------------------------------|------------------------------------------------------------------------------------------------------|----------|--------------|--------------------------------------------------------------------| -| `access_url` | [serpent.URL](#serpenturl) | false | | | -| `additional_csp_policy` | array of string | false | | | -| `address` | [serpent.HostPort](#serpenthostport) | false | | Deprecated: Use HTTPAddress or TLS.Address instead. | -| `agent_fallback_troubleshooting_url` | [serpent.URL](#serpenturl) | false | | | -| `agent_stat_refresh_interval` | integer | false | | | -| `ai` | [codersdk.AIConfig](#codersdkaiconfig) | false | | | -| `allow_workspace_renames` | boolean | false | | | -| `autobuild_poll_interval` | integer | false | | | -| `browser_only` | boolean | false | | | -| `cache_directory` | string | false | | | -| `cli_upgrade_message` | string | false | | | -| `cluster` | [codersdk.ClusterConfig](#codersdkclusterconfig) | false | | | -| `config` | string | false | | | -| `config_ssh` | [codersdk.SSHConfig](#codersdksshconfig) | false | | | -| `dangerous` | [codersdk.DangerousConfig](#codersdkdangerousconfig) | false | | | -| `derp` | [codersdk.DERP](#codersdkderp) | false | | | -| `disable_chat_sharing` | boolean | false | | | -| `disable_owner_workspace_exec` | boolean | false | | | -| `disable_password_auth` | boolean | false | | | -| `disable_path_apps` | boolean | false | | | -| `disable_workspace_agent_context_sync` | boolean | false | | | -| `disable_workspace_sharing` | boolean | false | | | -| `docs_url` | [serpent.URL](#serpenturl) | false | | | -| `enable_ai_tasks` | boolean | false | | | -| `enable_authz_recording` | boolean | false | | | -| `enable_terraform_debug_mode` | boolean | false | | | -| `ephemeral_deployment` | boolean | false | | | -| `experiments` | array of string | false | | | -| `external_auth` | [serpent.Struct-array_codersdk_ExternalAuthConfig](#serpentstruct-array_codersdk_externalauthconfig) | false | | | -| `external_auth_github_default_provider_enable` | boolean | false | | | -| `external_token_encryption_keys` | array of string | false | | | -| `healthcheck` | [codersdk.HealthcheckConfig](#codersdkhealthcheckconfig) | false | | | -| `http_address` | string | false | | Http address is a string because it may be set to zero to disable. | -| `http_cookies` | [codersdk.HTTPCookieConfig](#codersdkhttpcookieconfig) | false | | | -| `job_hang_detector_interval` | integer | false | | | -| `logging` | [codersdk.LoggingConfig](#codersdkloggingconfig) | false | | | -| `metrics_cache_refresh_interval` | integer | false | | | -| `notifications` | [codersdk.NotificationsConfig](#codersdknotificationsconfig) | false | | | -| `oauth2` | [codersdk.OAuth2Config](#codersdkoauth2config) | false | | | -| `oidc` | [codersdk.OIDCConfig](#codersdkoidcconfig) | false | | | -| `pg_auth` | string | false | | | -| `pg_conn_max_idle` | string | false | | | -| `pg_conn_max_open` | integer | false | | | -| `pg_connection_url` | string | false | | | -| `pprof` | [codersdk.PprofConfig](#codersdkpprofconfig) | false | | | -| `prometheus` | [codersdk.PrometheusConfig](#codersdkprometheusconfig) | false | | | -| `provisioner` | [codersdk.ProvisionerConfig](#codersdkprovisionerconfig) | false | | | -| `proxy_health_status_interval` | integer | false | | | -| `proxy_trusted_headers` | array of string | false | | | -| `proxy_trusted_origins` | array of string | false | | | -| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | | -| `redirect_to_access_url` | boolean | false | | | -| `retention` | [codersdk.RetentionConfig](#codersdkretentionconfig) | false | | | -| `scim_api_key` | string | false | | | -| `scim_use_legacy` | boolean | false | | | -| `session_lifetime` | [codersdk.SessionLifetime](#codersdksessionlifetime) | false | | | -| `ssh_keygen_algorithm` | string | false | | | -| `stats_collection` | [codersdk.StatsCollectionConfig](#codersdkstatscollectionconfig) | false | | | -| `strict_transport_security` | integer | false | | | -| `strict_transport_security_options` | array of string | false | | | -| `support` | [codersdk.SupportConfig](#codersdksupportconfig) | false | | | -| `swagger` | [codersdk.SwaggerConfig](#codersdkswaggerconfig) | false | | | -| `telemetry` | [codersdk.TelemetryConfig](#codersdktelemetryconfig) | false | | | -| `template_builder` | [codersdk.TemplateBuilderConfig](#codersdktemplatebuilderconfig) | false | | | -| `terms_of_service_url` | string | false | | | -| `tls` | [codersdk.TLSConfig](#codersdktlsconfig) | false | | | -| `trace` | [codersdk.TraceConfig](#codersdktraceconfig) | false | | | -| `update_check` | boolean | false | | | -| `user_quiet_hours_schedule` | [codersdk.UserQuietHoursScheduleConfig](#codersdkuserquiethoursscheduleconfig) | false | | | -| `verbose` | boolean | false | | | -| `web_terminal_renderer` | string | false | | | -| `wgtunnel_host` | string | false | | | -| `wildcard_access_url` | string | false | | | -| `workspace_hostname_suffix` | string | false | | | -| `workspace_prebuilds` | [codersdk.PrebuildsConfig](#codersdkprebuildsconfig) | false | | | -| `write_config` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +|------------------------------------------------|------------------------------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------| +| `access_url` | [serpent.URL](#serpenturl) | false | | | +| `additional_csp_policy` | array of string | false | | | +| `address` | [serpent.HostPort](#serpenthostport) | false | | Deprecated: Use HTTPAddress or TLS.Address instead. | +| `agent_fallback_troubleshooting_url` | [serpent.URL](#serpenturl) | false | | | +| `agent_stat_refresh_interval` | integer | false | | | +| `ai` | [codersdk.AIConfig](#codersdkaiconfig) | false | | | +| `allow_workspace_renames` | boolean | false | | Deprecated: Use the per-template allow_workspace_renames setting instead. | +| `autobuild_poll_interval` | integer | false | | | +| `browser_only` | boolean | false | | | +| `cache_directory` | string | false | | | +| `cli_upgrade_message` | string | false | | | +| `cluster` | [codersdk.ClusterConfig](#codersdkclusterconfig) | false | | | +| `config` | string | false | | | +| `config_ssh` | [codersdk.SSHConfig](#codersdksshconfig) | false | | | +| `dangerous` | [codersdk.DangerousConfig](#codersdkdangerousconfig) | false | | | +| `derp` | [codersdk.DERP](#codersdkderp) | false | | | +| `disable_chat_sharing` | boolean | false | | | +| `disable_owner_workspace_exec` | boolean | false | | | +| `disable_password_auth` | boolean | false | | | +| `disable_path_apps` | boolean | false | | | +| `disable_workspace_agent_context_sync` | boolean | false | | | +| `disable_workspace_sharing` | boolean | false | | | +| `docs_url` | [serpent.URL](#serpenturl) | false | | | +| `enable_ai_tasks` | boolean | false | | | +| `enable_authz_recording` | boolean | false | | | +| `enable_terraform_debug_mode` | boolean | false | | | +| `ephemeral_deployment` | boolean | false | | | +| `experiments` | array of string | false | | | +| `external_auth` | [serpent.Struct-array_codersdk_ExternalAuthConfig](#serpentstruct-array_codersdk_externalauthconfig) | false | | | +| `external_auth_github_default_provider_enable` | boolean | false | | | +| `external_token_encryption_keys` | array of string | false | | | +| `healthcheck` | [codersdk.HealthcheckConfig](#codersdkhealthcheckconfig) | false | | | +| `http_address` | string | false | | Http address is a string because it may be set to zero to disable. | +| `http_cookies` | [codersdk.HTTPCookieConfig](#codersdkhttpcookieconfig) | false | | | +| `job_hang_detector_interval` | integer | false | | | +| `logging` | [codersdk.LoggingConfig](#codersdkloggingconfig) | false | | | +| `metrics_cache_refresh_interval` | integer | false | | | +| `notifications` | [codersdk.NotificationsConfig](#codersdknotificationsconfig) | false | | | +| `oauth2` | [codersdk.OAuth2Config](#codersdkoauth2config) | false | | | +| `oidc` | [codersdk.OIDCConfig](#codersdkoidcconfig) | false | | | +| `pg_auth` | string | false | | | +| `pg_conn_max_idle` | string | false | | | +| `pg_conn_max_open` | integer | false | | | +| `pg_connection_url` | string | false | | | +| `pprof` | [codersdk.PprofConfig](#codersdkpprofconfig) | false | | | +| `prometheus` | [codersdk.PrometheusConfig](#codersdkprometheusconfig) | false | | | +| `provisioner` | [codersdk.ProvisionerConfig](#codersdkprovisionerconfig) | false | | | +| `proxy_health_status_interval` | integer | false | | | +| `proxy_trusted_headers` | array of string | false | | | +| `proxy_trusted_origins` | array of string | false | | | +| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | | +| `redirect_to_access_url` | boolean | false | | | +| `retention` | [codersdk.RetentionConfig](#codersdkretentionconfig) | false | | | +| `scim_api_key` | string | false | | | +| `scim_use_legacy` | boolean | false | | | +| `session_lifetime` | [codersdk.SessionLifetime](#codersdksessionlifetime) | false | | | +| `ssh_keygen_algorithm` | string | false | | | +| `stats_collection` | [codersdk.StatsCollectionConfig](#codersdkstatscollectionconfig) | false | | | +| `strict_transport_security` | integer | false | | | +| `strict_transport_security_options` | array of string | false | | | +| `support` | [codersdk.SupportConfig](#codersdksupportconfig) | false | | | +| `swagger` | [codersdk.SwaggerConfig](#codersdkswaggerconfig) | false | | | +| `telemetry` | [codersdk.TelemetryConfig](#codersdktelemetryconfig) | false | | | +| `template_builder` | [codersdk.TemplateBuilderConfig](#codersdktemplatebuilderconfig) | false | | | +| `terms_of_service_url` | string | false | | | +| `tls` | [codersdk.TLSConfig](#codersdktlsconfig) | false | | | +| `trace` | [codersdk.TraceConfig](#codersdktraceconfig) | false | | | +| `update_check` | boolean | false | | | +| `user_quiet_hours_schedule` | [codersdk.UserQuietHoursScheduleConfig](#codersdkuserquiethoursscheduleconfig) | false | | | +| `verbose` | boolean | false | | | +| `web_terminal_renderer` | string | false | | | +| `wgtunnel_host` | string | false | | | +| `wildcard_access_url` | string | false | | | +| `workspace_hostname_suffix` | string | false | | | +| `workspace_prebuilds` | [codersdk.PrebuildsConfig](#codersdkprebuildsconfig) | false | | | +| `write_config` | boolean | false | | | ## codersdk.DiagnosticExtra @@ -13613,6 +13615,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -13675,6 +13678,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | | `allow_user_autostop` | boolean | false | | | | `allow_user_cancel_workspace_jobs` | boolean | false | | | +| `allow_workspace_renames` | boolean | false | | Allow workspace renames permits users to rename workspaces built from this template. Renaming can be destructive for templates whose Terraform references the workspace name. | | `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | | | `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | | `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | @@ -14079,6 +14083,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -15578,6 +15583,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -15619,6 +15625,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W | `allow_user_autostart` | boolean | false | | | | `allow_user_autostop` | boolean | false | | | | `allow_user_cancel_workspace_jobs` | boolean | false | | | +| `allow_workspace_renames` | boolean | false | | Allow workspace renames permits users to rename workspaces built from this template. Renaming can be destructive for templates whose Terraform references the workspace name. | | `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | | | `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement can only be set if your license includes the advanced template scheduling feature. If you attempt to set this value while unlicensed, it will be ignored. | | `cors_behavior` | [codersdk.CORSBehavior](#codersdkcorsbehavior) | false | | | @@ -17049,7 +17056,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | Name | Type | Required | Restrictions | Description | |---------------------------------------------|-------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `allow_renames` | boolean | false | | | +| `allow_renames` | boolean | false | | Allow renames is the effective rename permission for this workspace, derived from the template's allow_workspace_renames setting and the deprecated deployment-wide flag. | | `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | | `autostart_schedule` | string | false | | | | `created_at` | string | false | | | diff --git a/docs/reference/api/templatebuilder.md b/docs/reference/api/templatebuilder.md index 183c7c23514..caedb3de089 100644 --- a/docs/reference/api/templatebuilder.md +++ b/docs/reference/api/templatebuilder.md @@ -169,6 +169,7 @@ curl -X POST http://coder-server:8080/api/v2/templatebuilder/compose/template \ "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index 8c5387da743..c596e0f5d22 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -42,6 +42,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -104,19 +105,20 @@ To include deprecated templates, specify `deprecated:true` in the search query. Status Code **200** -| Name | Type | Required | Restrictions | Description | -|--------------------------------------|------------------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `[array item]` | array | false | | | -| `» active_user_count` | integer | false | | Active user count is set to -1 when loading. | -| `» active_version_id` | string(uuid) | false | | | -| `» activity_bump_ms` | integer | false | | | -| `» agents_allowed` | boolean | false | | | -| `» allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | -| `» allow_user_autostop` | boolean | false | | | -| `» allow_user_cancel_workspace_jobs` | boolean | false | | | -| `» autostart_requirement` | [codersdk.TemplateAutostartRequirement](schemas.md#codersdktemplateautostartrequirement) | false | | | -| `»» days_of_week` | array | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | -| `» autostop_requirement` | [codersdk.TemplateAutostopRequirement](schemas.md#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | +| Name | Type | Required | Restrictions | Description | +|--------------------------------------|------------------------------------------------------------------------------------------|----------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `[array item]` | array | false | | | +| `» active_user_count` | integer | false | | Active user count is set to -1 when loading. | +| `» active_version_id` | string(uuid) | false | | | +| `» activity_bump_ms` | integer | false | | | +| `» agents_allowed` | boolean | false | | | +| `» allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | +| `» allow_user_autostop` | boolean | false | | | +| `» allow_user_cancel_workspace_jobs` | boolean | false | | | +| `» allow_workspace_renames` | boolean | false | | Allow workspace renames permits users to rename workspaces built from this template. Renaming can be destructive for templates whose Terraform references the workspace name. | +| `» autostart_requirement` | [codersdk.TemplateAutostartRequirement](schemas.md#codersdktemplateautostartrequirement) | false | | | +| `»» days_of_week` | array | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | +| `» autostop_requirement` | [codersdk.TemplateAutostopRequirement](schemas.md#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | |`»» days_of_week`|array|false||Days of week is a list of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required. Weekdays cannot be specified twice. Restarts will only happen on weekdays in this list on weeks which line up with Weeks.| |`»» weeks`|integer|false||Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc.| @@ -185,6 +187,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -234,6 +237,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -388,6 +392,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -812,6 +817,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -874,19 +880,20 @@ To include deprecated templates, specify `deprecated:true` in the search query. Status Code **200** -| Name | Type | Required | Restrictions | Description | -|--------------------------------------|------------------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `[array item]` | array | false | | | -| `» active_user_count` | integer | false | | Active user count is set to -1 when loading. | -| `» active_version_id` | string(uuid) | false | | | -| `» activity_bump_ms` | integer | false | | | -| `» agents_allowed` | boolean | false | | | -| `» allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | -| `» allow_user_autostop` | boolean | false | | | -| `» allow_user_cancel_workspace_jobs` | boolean | false | | | -| `» autostart_requirement` | [codersdk.TemplateAutostartRequirement](schemas.md#codersdktemplateautostartrequirement) | false | | | -| `»» days_of_week` | array | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | -| `» autostop_requirement` | [codersdk.TemplateAutostopRequirement](schemas.md#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | +| Name | Type | Required | Restrictions | Description | +|--------------------------------------|------------------------------------------------------------------------------------------|----------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `[array item]` | array | false | | | +| `» active_user_count` | integer | false | | Active user count is set to -1 when loading. | +| `» active_version_id` | string(uuid) | false | | | +| `» activity_bump_ms` | integer | false | | | +| `» agents_allowed` | boolean | false | | | +| `» allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | +| `» allow_user_autostop` | boolean | false | | | +| `» allow_user_cancel_workspace_jobs` | boolean | false | | | +| `» allow_workspace_renames` | boolean | false | | Allow workspace renames permits users to rename workspaces built from this template. Renaming can be destructive for templates whose Terraform references the workspace name. | +| `» autostart_requirement` | [codersdk.TemplateAutostartRequirement](schemas.md#codersdktemplateautostartrequirement) | false | | | +| `»» days_of_week` | array | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | +| `» autostop_requirement` | [codersdk.TemplateAutostopRequirement](schemas.md#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | |`»» days_of_week`|array|false||Days of week is a list of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required. Weekdays cannot be specified twice. Restarts will only happen on weekdays in this list on weeks which line up with Weeks.| |`»» weeks`|integer|false||Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc.| @@ -1020,6 +1027,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -1146,6 +1154,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" @@ -1198,6 +1207,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, + "allow_workspace_renames": true, "autostart_requirement": { "days_of_week": [ "monday" diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index aed9ff99de3..60f096e779e 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1370,17 +1370,6 @@ Allow users to set their own quiet hours schedule for workspaces to stop in (dep The renderer to use when opening a web terminal. Valid values are 'canvas', 'webgl', or 'dom'. -### --allow-workspace-renames - -| | | -|-------------|---------------------------------------------| -| Type | bool | -| Environment | $CODER_ALLOW_WORKSPACE_RENAMES | -| YAML | allowWorkspaceRenames | -| Default | false | - -Allow users to rename their workspaces. WARNING: Renaming a workspace can cause Terraform resources that depend on the workspace name to be destroyed and recreated, potentially causing data loss. Only enable this if your templates do not use workspace names in resource identifiers, or if you understand the risks. - ### --health-check-refresh | | | diff --git a/docs/user-guides/workspace-management.md b/docs/user-guides/workspace-management.md index 013b0a29ab8..c688b2ff516 100644 --- a/docs/user-guides/workspace-management.md +++ b/docs/user-guides/workspace-management.md @@ -45,6 +45,10 @@ coder show | Reserved names | Cannot use `new` or `create` | | Uniqueness | Must be unique within your workspaces | +Renaming a workspace after you create it is off by default. A template admin can +allow it per template. Refer to +[Workspace renaming](../admin/templates/managing-templates/index.md#workspace-renaming). + ## Workspace filtering In the Coder UI, you can filter your workspaces using pre-defined filters or diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index a0963914d1e..e8cf69003c0 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -136,6 +136,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "disable_module_cache": ActionTrack, "time_til_autostop_notify": ActionTrack, "agents_allowed": ActionTrack, + "allow_workspace_renames": ActionTrack, }, &database.TemplateVersion{}: { "id": ActionTrack, diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 4cf33116469..8890db6821d 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -18,13 +18,6 @@ SUBCOMMANDS: PostgreSQL deployment. OPTIONS: - --allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false) - Allow users to rename their workspaces. WARNING: Renaming a workspace - can cause Terraform resources that depend on the workspace name to be - destroyed and recreated, potentially causing data loss. Only enable - this if your templates do not use workspace names in resource - identifiers, or if you understand the risks. - --cache-dir string, $CODER_CACHE_DIRECTORY (default: [cache dir]) The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d8320f2cb68..4502fd9eac4 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -4198,6 +4198,12 @@ export interface CreateTemplateRequest { * this template. Defaults to true. */ readonly agents_allowed?: boolean; + /** + * AllowWorkspaceRenames permits users to rename workspaces built from this + * template. Renaming can be destructive for templates whose Terraform + * references the workspace name, so this defaults to false. + */ + readonly allow_workspace_renames?: boolean; } // From codersdk/templateversions.go @@ -4835,6 +4841,9 @@ export interface DeploymentValues { readonly enable_terraform_debug_mode?: boolean; readonly user_quiet_hours_schedule?: UserQuietHoursScheduleConfig; readonly web_terminal_renderer?: string; + /** + * @deprecated Use the per-template allow_workspace_renames setting instead. + */ readonly allow_workspace_renames?: boolean; readonly healthcheck?: HealthcheckConfig; readonly retention?: RetentionConfig; @@ -9146,6 +9155,12 @@ export interface Template { * provisioning. */ readonly disable_module_cache: boolean; + /** + * AllowWorkspaceRenames permits users to rename workspaces built from this + * template. Renaming can be destructive for templates whose Terraform + * references the workspace name. + */ + readonly allow_workspace_renames: boolean; } // From codersdk/templates.go @@ -10135,6 +10150,12 @@ export interface UpdateTemplateMeta { * this template. If omitted, the current value is preserved. */ readonly agents_allowed?: boolean; + /** + * AllowWorkspaceRenames permits users to rename workspaces built from this + * template. Renaming can be destructive for templates whose Terraform + * references the workspace name. + */ + readonly allow_workspace_renames?: boolean; } // From codersdk/users.go @@ -10965,6 +10986,11 @@ export interface Workspace { */ readonly health: WorkspaceHealth; readonly automatic_updates: AutomaticUpdates; + /** + * AllowRenames is the effective rename permission for this workspace, + * derived from the template's allow_workspace_renames setting and the + * deprecated deployment-wide flag. + */ readonly allow_renames: boolean; readonly favorite: boolean; readonly next_start_at: string | null; diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.test.tsx new file mode 100644 index 00000000000..bb29d859a36 --- /dev/null +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.test.tsx @@ -0,0 +1,82 @@ +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MockTemplate } from "#/testHelpers/entities"; +import { renderComponent } from "#/testHelpers/renderHelpers"; +import { TemplateSettingsForm } from "./TemplateSettingsForm"; + +const renameCheckbox = () => + screen.getByRole("checkbox", { + name: /allow users to rename their workspaces/i, + }); + +const renderForm = ( + overrides: Partial[0]> = {}, +) => { + const onSubmit = vi.fn(); + renderComponent( + , + ); + return { onSubmit }; +}; + +describe("TemplateSettingsForm", () => { + describe("allow_workspace_renames", () => { + it("reflects the template's current value", () => { + renderForm({ + template: { ...MockTemplate, allow_workspace_renames: true }, + }); + expect(renameCheckbox()).toBeChecked(); + }); + + it("is unchecked when the template disallows renames", () => { + renderForm({ + template: { ...MockTemplate, allow_workspace_renames: false }, + }); + expect(renameCheckbox()).not.toBeChecked(); + }); + + it("submits the new value when toggled on", async () => { + const user = userEvent.setup(); + const { onSubmit } = renderForm({ + template: { ...MockTemplate, allow_workspace_renames: false }, + }); + + await user.click(renameCheckbox()); + await user.click(screen.getByRole("button", { name: /save/i })); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ allow_workspace_renames: true }), + // Formik passes its helpers as a second argument. + expect.anything(), + ); + }); + }); + + it("submits the new value when toggled off", async () => { + const user = userEvent.setup(); + const { onSubmit } = renderForm({ + template: { ...MockTemplate, allow_workspace_renames: true }, + }); + + await user.click(renameCheckbox()); + await user.click(screen.getByRole("button", { name: /save/i })); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ allow_workspace_renames: false }), + expect.anything(), + ); + }); + }); + }); +}); diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 12930faf374..4ab63295cbb 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -19,6 +19,7 @@ import { import { FormField } from "#/components/FormField/FormField"; import { IconField } from "#/components/IconField/IconField"; import { Label } from "#/components/Label/Label"; +import { Link } from "#/components/Link/Link"; import { Select, SelectContent, @@ -33,6 +34,7 @@ import { } from "#/components/StackLabel/StackLabel"; import { Textarea } from "#/components/Textarea/Textarea"; import { cn } from "#/utils/cn"; +import { docs } from "#/utils/docs"; import { displayNameValidator, getFormHelpers, @@ -53,6 +55,7 @@ export const validationSchema = Yup.object({ ), allow_user_cancel_workspace_jobs: Yup.boolean(), agents_allowed: Yup.boolean(), + allow_workspace_renames: Yup.boolean(), icon: iconValidator, require_active_version: Yup.boolean(), disable_module_cache: Yup.boolean(), @@ -102,6 +105,7 @@ export const TemplateSettingsForm: FC = ({ max_port_share_level: template.max_port_share_level, cors_behavior: template.cors_behavior, disable_module_cache: template.disable_module_cache, + allow_workspace_renames: template.allow_workspace_renames, }, validationSchema, onSubmit, @@ -307,6 +311,38 @@ export const TemplateSettingsForm: FC = ({ + +
+ { + form.setFieldValue("allow_workspace_renames", checked === true); + }} + disabled={isSubmitting} + /> + +
diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx index 33b952a2040..aae5f91fd1b 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx @@ -46,6 +46,7 @@ const validFormValues: FormValues = { use_classic_parameter_flow: true, cors_behavior: "simple", disable_module_cache: false, + allow_workspace_renames: false, }; describe("TemplateSettingsPage", () => { diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 55800026e46..96350592356 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -963,6 +963,7 @@ export const MockTemplate: TypesGen.Template = { use_classic_parameter_flow: false, cors_behavior: "simple", disable_module_cache: false, + allow_workspace_renames: false, }; const _MockTemplateVersionFiles: TemplateVersionFiles = {