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
|
| Field | Tracked |
| | id | false |
| reconciliation_paused | true |
|
| RoleSyncSettings
| | Field | Tracked |
| | field | true |
| mapping | true |
|
| TaskTable
| | Field | Tracked |
| | created_at | false |
| deleted_at | false |
| display_name | true |
| id | true |
| name | true |
| organization_id | false |
| owner_id | true |
| prompt | true |
| template_parameters | true |
| template_version_id | true |
| workspace_id | true |
|
-| Template
write, delete | | Field | Tracked |
| | active_version_id | true |
| activity_bump | true |
| agents_allowed | true |
| allow_user_autostart | true |
| allow_user_autostop | true |
| allow_user_cancel_workspace_jobs | true |
| autostart_block_days_of_week | true |
| autostop_requirement_days_of_week | true |
| autostop_requirement_weeks | true |
| cors_behavior | true |
| created_at | false |
| created_by | true |
| created_by_avatar_url | false |
| created_by_name | false |
| created_by_username | false |
| default_ttl | true |
| deleted | false |
| deprecated | true |
| description | true |
| disable_module_cache | true |
| display_name | true |
| failure_ttl | true |
| group_acl | true |
| icon | true |
| id | true |
| max_port_sharing_level | true |
| name | true |
| organization_display_name | false |
| organization_icon | false |
| organization_id | false |
| organization_name | false |
| provisioner | true |
| require_active_version | true |
| time_til_autostop_notify | true |
| time_til_dormant | true |
| time_til_dormant_autodelete | true |
| updated_at | false |
| use_classic_parameter_flow | true |
| user_acl | true |
|
+| Template
write, delete | | Field | Tracked |
| | active_version_id | true |
| activity_bump | true |
| agents_allowed | true |
| allow_user_autostart | true |
| allow_user_autostop | true |
| allow_user_cancel_workspace_jobs | true |
| allow_workspace_renames | true |
| autostart_block_days_of_week | true |
| autostop_requirement_days_of_week | true |
| autostop_requirement_weeks | true |
| cors_behavior | true |
| created_at | false |
| created_by | true |
| created_by_avatar_url | false |
| created_by_name | false |
| created_by_username | false |
| default_ttl | true |
| deleted | false |
| deprecated | true |
| description | true |
| disable_module_cache | true |
| display_name | true |
| failure_ttl | true |
| group_acl | true |
| icon | true |
| id | true |
| max_port_sharing_level | true |
| name | true |
| organization_display_name | false |
| organization_icon | false |
| organization_id | false |
| organization_name | false |
| provisioner | true |
| require_active_version | true |
| time_til_autostop_notify | true |
| time_til_dormant | true |
| time_til_dormant_autodelete | true |
| updated_at | false |
| use_classic_parameter_flow | true |
| user_acl | true |
|
| TemplateVersion
create, write | | Field | Tracked |
| | archived | true |
| created_at | false |
| created_by | true |
| created_by_avatar_url | false |
| created_by_name | false |
| created_by_username | false |
| external_auth_providers | false |
| has_ai_task | false |
| has_external_agent | false |
| id | true |
| job_id | false |
| message | false |
| name | true |
| organization_id | false |
| readme | true |
| source_example_id | false |
| template_id | true |
| updated_at | false |
|
| User
create, write, delete | | Field | Tracked |
| | avatar_url | false |
| chat_spend_limit_micros | true |
| created_at | false |
| deleted | true |
| email | true |
| github_com_user_id | false |
| hashed_one_time_passcode | false |
| hashed_password | true |
| id | true |
| is_service_account | true |
| is_system | true |
| last_seen_at | false |
| login_type | true |
| name | true |
| one_time_passcode_expires_at | true |
| quiet_hours_schedule | true |
| rbac_roles | true |
| status | true |
| updated_at | false |
| username | true |
|
| UserSecret
create, write, delete | | Field | Tracked |
| | created_at | false |
| description | true |
| enabled | true |
| env_name | true |
| file_path | true |
| id | true |
| name | true |
| updated_at | false |
| user_id | true |
| value | true |
| value_key_id | false |
|
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.

+## 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 = {