Files
coder/coderd/database/queries/templates.sql
T
Ethan b3485d9b3a chore: add agents_allowed to templates (#27284)
Relates to CODAGT-713

This adds `templates.agents_allowed` as a default-true, auditable template attribute, along with nullable database filtering. Migration `000562` translates the effective legacy `agents_template_allowlist` state for existing templates: a valid nonempty list allows matching templates and blocks the rest, missing or empty values leave templates allowed, whilst corrupt values fail closed by blocking all existing templates. As per the linear issue, new templates deliberately default to allowed under the per-template model.

This is the database-only first PR in the stack. #27285 makes the field authoritative in the API and chatd whilst temporarily retaining the compatibility routes needed by the shipped frontend. Later PRs migrate the UI, remove the legacy storage, routes, SDK types, and utility, then add CLI flags.
2026-08-06 14:04:23 +10:00

261 lines
6.2 KiB
SQL

-- name: GetTemplateByID :one
SELECT
*
FROM
template_with_names
WHERE
id = $1
LIMIT
1;
-- name: GetTemplatesWithFilter :many
SELECT
t.*
FROM
template_with_names AS t
LEFT JOIN
template_versions tv ON t.active_version_id = tv.id
WHERE
-- Optionally include deleted templates
t.deleted = @deleted
-- Filter by organization_id
AND CASE
WHEN @organization_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.organization_id = @organization_id
ELSE true
END
-- Filter by exact name
AND CASE
WHEN @exact_name :: text != '' THEN
LOWER(t.name) = LOWER(@exact_name)
ELSE true
END
-- Filter by exact display name
AND CASE
WHEN @exact_display_name :: text != '' THEN
LOWER(t.display_name) = LOWER(@exact_display_name)
ELSE true
END
-- Filter by name, matching on substring
AND CASE
WHEN @fuzzy_name :: text != '' THEN
lower(t.name) ILIKE '%' || lower(@fuzzy_name) || '%'
ELSE true
END
-- Filter by display_name, matching on substring (fallback to name if display_name is empty)
AND CASE
WHEN @fuzzy_display_name :: text != '' THEN
CASE
WHEN t.display_name IS NOT NULL AND t.display_name != '' THEN
lower(t.display_name) ILIKE '%' || lower(@fuzzy_display_name) || '%'
ELSE
-- Remove spaces if present since 't.name' cannot have any spaces
lower(t.name) ILIKE '%' || REPLACE(lower(@fuzzy_display_name), ' ', '') || '%'
END
ELSE true
END
-- Filter by ids
AND CASE
WHEN array_length(@ids :: uuid[], 1) > 0 THEN
t.id = ANY(@ids)
ELSE true
END
-- Filter by deprecated
AND CASE
WHEN sqlc.narg('deprecated') :: boolean IS NOT NULL THEN
CASE
WHEN sqlc.narg('deprecated') :: boolean THEN
t.deprecated != ''
ELSE
t.deprecated = ''
END
ELSE true
END
-- Filter by has_ai_task in latest version
AND CASE
WHEN sqlc.narg('has_ai_task') :: boolean IS NOT NULL THEN
tv.has_ai_task = sqlc.narg('has_ai_task') :: boolean
ELSE true
END
-- Filter by agents_allowed
AND CASE
WHEN sqlc.narg('agents_allowed') :: boolean IS NOT NULL THEN
t.agents_allowed = sqlc.narg('agents_allowed') :: boolean
ELSE true
END
-- Filter by author_id
AND CASE
WHEN @author_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.created_by = @author_id
ELSE true
END
-- Filter by author_username
AND CASE
WHEN @author_username :: text != '' THEN
t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower(@author_username) AND deleted = false)
ELSE true
END
-- Filter by has_external_agent in latest version
AND CASE
WHEN sqlc.narg('has_external_agent') :: boolean IS NOT NULL THEN
tv.has_external_agent = sqlc.narg('has_external_agent') :: boolean
ELSE true
END
-- Authorize Filter clause will be injected below in GetAuthorizedTemplates
-- @authorize_filter
ORDER BY (t.name, t.id) ASC
;
-- name: GetTemplateByOrganizationAndName :one
SELECT
*
FROM
template_with_names AS templates
WHERE
organization_id = @organization_id
AND deleted = @deleted
AND LOWER("name") = LOWER(@name)
LIMIT
1;
-- name: GetTemplates :many
SELECT * FROM template_with_names AS templates
ORDER BY (name, id) ASC
;
-- name: InsertTemplate :exec
INSERT INTO
templates (
id,
created_at,
updated_at,
organization_id,
"name",
provisioner,
active_version_id,
description,
created_by,
icon,
user_acl,
group_acl,
display_name,
allow_user_cancel_workspace_jobs,
max_port_sharing_level,
use_classic_parameter_flow,
cors_behavior,
agents_allowed
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18);
-- name: UpdateTemplateActiveVersionByID :exec
UPDATE
templates
SET
active_version_id = $2,
updated_at = $3
WHERE
id = $1;
-- name: UpdateTemplateDeletedByID :exec
UPDATE
templates
SET
deleted = $2,
updated_at = $3
WHERE
id = $1;
-- name: UpdateTemplateMetaByID :exec
UPDATE
templates
SET
updated_at = $2,
description = $3,
name = $4,
icon = $5,
display_name = $6,
allow_user_cancel_workspace_jobs = $7,
group_acl = $8,
max_port_sharing_level = $9,
use_classic_parameter_flow = $10,
cors_behavior = $11,
disable_module_cache = $12,
agents_allowed = $13
WHERE
id = $1
;
-- name: UpdateTemplateScheduleByID :exec
UPDATE
templates
SET
updated_at = $2,
allow_user_autostart = $3,
allow_user_autostop = $4,
default_ttl = $5,
activity_bump = $6,
autostop_requirement_days_of_week = $7,
autostop_requirement_weeks = $8,
autostart_block_days_of_week = $9,
failure_ttl = $10,
time_til_dormant = $11,
time_til_dormant_autodelete = $12,
time_til_autostop_notify = $13
WHERE
id = $1
;
-- name: UpdateTemplateACLByID :exec
UPDATE
templates
SET
group_acl = $1,
user_acl = $2
WHERE
id = $3
;
-- name: GetTemplateAverageBuildTime :one
WITH build_times AS (
SELECT
EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at))::FLOAT AS exec_time_sec,
workspace_builds.transition
FROM
workspace_builds
JOIN template_versions ON
workspace_builds.template_version_id = template_versions.id
JOIN provisioner_jobs pj ON
workspace_builds.job_id = pj.id
WHERE
template_versions.template_id = @template_id AND
(pj.completed_at IS NOT NULL) AND (pj.started_at IS NOT NULL) AND
(pj.canceled_at IS NULL) AND
((pj.error IS NULL) OR (pj.error = ''))
ORDER BY
workspace_builds.created_at DESC
LIMIT 100
)
SELECT
-- Postgres offers no clear way to DRY this short of a function or other
-- complexities.
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'start')), -1)::FLOAT AS start_50,
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'stop')), -1)::FLOAT AS stop_50,
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'delete')), -1)::FLOAT AS delete_50,
coalesce((PERCENTILE_DISC(0.95) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'start')), -1)::FLOAT AS start_95,
coalesce((PERCENTILE_DISC(0.95) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'stop')), -1)::FLOAT AS stop_95,
coalesce((PERCENTILE_DISC(0.95) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'delete')), -1)::FLOAT AS delete_95
FROM build_times
;
-- name: UpdateTemplateAccessControlByID :exec
UPDATE
templates
SET
require_active_version = $2,
deprecated = $3
WHERE
id = $1
;