chore: add template toggle to disable module caching (#21931)

There exists use cases to disable the new module caching behavior of
workspace builds. This was the legacy behavior.
This commit is contained in:
Steven Masley
2026-02-05 14:38:55 -06:00
committed by GitHub
parent 62fa0e8caa
commit efd98bd93a
21 changed files with 204 additions and 41 deletions
+8
View File
@@ -18813,6 +18813,10 @@ const docTemplate = `{
"description": {
"type": "string"
},
"disable_module_cache": {
"description": "DisableModuleCache disables the use of cached Terraform modules during\nprovisioning.",
"type": "boolean"
},
"display_name": {
"type": "string"
},
@@ -19769,6 +19773,10 @@ const docTemplate = `{
"description": "DisableEveryoneGroupAccess allows optionally disabling the default\nbehavior of granting the 'everyone' group access to use the template.\nIf this is set to true, the template will not be available to all users,\nand must be explicitly granted to users or groups in the permissions settings\nof the template.",
"type": "boolean"
},
"disable_module_cache": {
"description": "DisableModuleCache disables the using of cached Terraform modules during\nprovisioning. It is recommended not to disable this.",
"type": "boolean"
},
"display_name": {
"type": "string"
},
+8
View File
@@ -17208,6 +17208,10 @@
"description": {
"type": "string"
},
"disable_module_cache": {
"description": "DisableModuleCache disables the use of cached Terraform modules during\nprovisioning.",
"type": "boolean"
},
"display_name": {
"type": "string"
},
@@ -18118,6 +18122,10 @@
"description": "DisableEveryoneGroupAccess allows optionally disabling the default\nbehavior of granting the 'everyone' group access to use the template.\nIf this is set to true, the template will not be available to all users,\nand must be explicitly granted to users or groups in the permissions settings\nof the template.",
"type": "boolean"
},
"disable_module_cache": {
"description": "DisableModuleCache disables the using of cached Terraform modules during\nprovisioning. It is recommended not to disable this.",
"type": "boolean"
},
"display_name": {
"type": "string"
},
+3 -1
View File
@@ -2290,7 +2290,8 @@ CREATE TABLE templates (
activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL,
max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL,
use_classic_parameter_flow boolean DEFAULT false NOT NULL,
cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL
cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL,
disable_module_cache boolean DEFAULT false NOT NULL
);
COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.';
@@ -2344,6 +2345,7 @@ CREATE VIEW template_with_names AS
templates.max_port_sharing_level,
templates.use_classic_parameter_flow,
templates.cors_behavior,
templates.disable_module_cache,
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,
@@ -0,0 +1,16 @@
DROP VIEW template_with_names;
ALTER TABLE templates DROP COLUMN disable_module_cache;
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.';
@@ -0,0 +1,16 @@
DROP VIEW template_with_names;
ALTER TABLE templates ADD COLUMN disable_module_cache BOOL NOT NULL DEFAULT false;
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.';
+1
View File
@@ -127,6 +127,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
&i.MaxPortSharingLevel,
&i.UseClassicParameterFlow,
&i.CorsBehavior,
&i.DisableModuleCache,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
+2
View File
@@ -4338,6 +4338,7 @@ type Template struct {
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"`
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"`
@@ -4387,6 +4388,7 @@ type TemplateTable struct {
// Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable.
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"`
}
// Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute.
+13 -6
View File
@@ -13729,7 +13729,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, 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, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon
FROM
template_with_names
WHERE
@@ -13772,6 +13772,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
&i.MaxPortSharingLevel,
&i.UseClassicParameterFlow,
&i.CorsBehavior,
&i.DisableModuleCache,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -13784,7 +13785,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, 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, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon
FROM
template_with_names AS templates
WHERE
@@ -13835,6 +13836,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
&i.MaxPortSharingLevel,
&i.UseClassicParameterFlow,
&i.CorsBehavior,
&i.DisableModuleCache,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -13846,7 +13848,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, 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, 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
`
@@ -13890,6 +13892,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
&i.MaxPortSharingLevel,
&i.UseClassicParameterFlow,
&i.CorsBehavior,
&i.DisableModuleCache,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -13912,7 +13915,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.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.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
@@ -14071,6 +14074,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
&i.MaxPortSharingLevel,
&i.UseClassicParameterFlow,
&i.CorsBehavior,
&i.DisableModuleCache,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -14256,7 +14260,8 @@ SET
group_acl = $8,
max_port_sharing_level = $9,
use_classic_parameter_flow = $10,
cors_behavior = $11
cors_behavior = $11,
disable_module_cache = $12
WHERE
id = $1
`
@@ -14273,6 +14278,7 @@ type UpdateTemplateMetaByIDParams struct {
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"`
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"`
}
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error {
@@ -14288,6 +14294,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
arg.MaxPortSharingLevel,
arg.UseClassicParameterFlow,
arg.CorsBehavior,
arg.DisableModuleCache,
)
return err
}
@@ -22843,7 +22850,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
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
FROM
templates
WHERE
+2 -1
View File
@@ -173,7 +173,8 @@ SET
group_acl = $8,
max_port_sharing_level = $9,
use_classic_parameter_flow = $10,
cors_behavior = $11
cors_behavior = $11,
disable_module_cache = $12
WHERE
id = $1
;
@@ -512,13 +512,15 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
// Fetch the file id of the cached module files if it exists.
versionModulesFile := ""
tfvals, err := s.Database.GetTemplateVersionTerraformValues(ctx, templateVersion.ID)
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
// Older templates (before dynamic parameters) will not have cached module files.
return nil, failJob(fmt.Sprintf("get template version terraform values: %s", err))
}
if err == nil && tfvals.CachedModuleFiles.Valid {
versionModulesFile = tfvals.CachedModuleFiles.UUID.String()
if !template.DisableModuleCache {
tfvals, err := s.Database.GetTemplateVersionTerraformValues(ctx, templateVersion.ID)
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
// Older templates (before dynamic parameters) will not have cached module files.
return nil, failJob(fmt.Sprintf("get template version terraform values: %s", err))
}
if err == nil && tfvals.CachedModuleFiles.Valid {
versionModulesFile = tfvals.CachedModuleFiles.UUID.String()
}
}
var ownerSSHPublicKey, ownerSSHPrivateKey string
+7
View File
@@ -775,6 +775,10 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
if req.UseClassicParameterFlow != nil {
classicTemplateFlow = *req.UseClassicParameterFlow
}
disableModuleCache := template.DisableModuleCache
if req.DisableModuleCache != nil {
disableModuleCache = *req.DisableModuleCache
}
displayName := ptr.NilToDefault(req.DisplayName, template.DisplayName)
description := ptr.NilToDefault(req.Description, template.Description)
@@ -800,6 +804,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
req.RequireActiveVersion == template.RequireActiveVersion &&
(deprecationMessage == template.Deprecated) &&
(classicTemplateFlow == template.UseClassicParameterFlow) &&
(disableModuleCache == template.DisableModuleCache) &&
maxPortShareLevel == template.MaxPortSharingLevel &&
corsBehavior == template.CorsBehavior {
return nil
@@ -844,6 +849,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
MaxPortSharingLevel: maxPortShareLevel,
UseClassicParameterFlow: classicTemplateFlow,
CorsBehavior: corsBehavior,
DisableModuleCache: disableModuleCache,
})
if err != nil {
return xerrors.Errorf("update template metadata: %w", err)
@@ -1128,6 +1134,7 @@ func (api *API) convertTemplate(
MaxPortShareLevel: maxPortShareLevel,
UseClassicParameterFlow: template.UseClassicParameterFlow,
CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior),
DisableModuleCache: template.DisableModuleCache,
}
}
+33
View File
@@ -1616,6 +1616,39 @@ func TestPatchTemplateMeta(t *testing.T) {
assert.False(t, updated.UseClassicParameterFlow, "expected false")
})
t.Run("DisableModuleCache", 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.DisableModuleCache, "default is false")
req := codersdk.UpdateTemplateMeta{
DisableModuleCache: ptr.Ref(true),
}
ctx := testutil.Context(t, testutil.WaitLong)
// set to true
updated, err := client.UpdateTemplateMeta(ctx, template.ID, req)
require.NoError(t, err)
assert.True(t, updated.DisableModuleCache, "expected true")
// noop - should stay true when not specified
req.DisableModuleCache = nil
updated, err = client.UpdateTemplateMeta(ctx, template.ID, req)
require.NoError(t, err)
assert.True(t, updated.DisableModuleCache, "expected true")
// back to false
req.DisableModuleCache = ptr.Ref(false)
updated, err = client.UpdateTemplateMeta(ctx, template.ID, req)
require.NoError(t, err)
assert.False(t, updated.DisableModuleCache, "expected false")
})
t.Run("SupportEmptyOrDefaultFields", func(t *testing.T) {
t.Parallel()