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.
This commit is contained in:
Ethan
2026-08-06 14:04:23 +10:00
committed by GitHub
parent c98d6bb535
commit b3485d9b3a
14 changed files with 467 additions and 19 deletions
+1
View File
@@ -555,6 +555,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
MaxPortSharingLevel: takeFirst(seed.MaxPortSharingLevel, database.AppSharingLevelOwner),
UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, false),
CorsBehavior: takeFirst(seed.CorsBehavior, database.CorsBehaviorSimple),
AgentsAllowed: seed.AgentsAllowed,
})
require.NoError(t, err, "insert template")
+5 -1
View File
@@ -3461,7 +3461,8 @@ CREATE TABLE templates (
use_classic_parameter_flow boolean DEFAULT false NOT NULL,
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
time_til_autostop_notify bigint DEFAULT 0 NOT NULL,
agents_allowed boolean DEFAULT true NOT NULL
);
COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.';
@@ -3486,6 +3487,8 @@ COMMENT ON COLUMN templates.use_classic_parameter_flow IS 'Determines whether to
COMMENT ON COLUMN templates.time_til_autostop_notify IS 'How long before the workspace autostop deadline to send a reminder notification, in nanoseconds. 0 disables the notification.';
COMMENT ON COLUMN templates.agents_allowed IS 'Whether Coder Agents can create workspaces using this template.';
CREATE VIEW template_with_names AS
SELECT templates.id,
templates.created_at,
@@ -3519,6 +3522,7 @@ CREATE VIEW template_with_names AS
templates.cors_behavior,
templates.disable_module_cache,
templates.time_til_autostop_notify,
templates.agents_allowed,
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,17 @@
DROP VIEW template_with_names;
ALTER TABLE templates DROP COLUMN agents_allowed;
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,64 @@
ALTER TABLE templates ADD COLUMN agents_allowed boolean DEFAULT true NOT NULL;
COMMENT ON COLUMN templates.agents_allowed IS 'Whether Coder Agents can create workspaces using this template.';
DO $$
DECLARE
raw text;
parsed jsonb;
parsed_ids uuid[];
BEGIN
SELECT value INTO raw
FROM site_configs
WHERE key = 'agents_template_allowlist';
IF raw IS NULL OR btrim(raw) = '' THEN
RETURN;
END IF;
BEGIN
parsed := raw::jsonb;
IF parsed = 'null'::jsonb THEN
RETURN;
END IF;
IF jsonb_typeof(parsed) <> 'array' THEN
RAISE EXCEPTION 'value is not a JSON array';
END IF;
IF jsonb_array_length(parsed) = 0 THEN
RETURN;
END IF;
SELECT array_agg(entry::uuid)
INTO parsed_ids
FROM jsonb_array_elements_text(parsed) AS entries(entry);
IF array_position(parsed_ids, NULL) IS NOT NULL THEN
RAISE EXCEPTION 'contains a null template ID';
END IF;
EXCEPTION WHEN others THEN
RAISE WARNING 'agents_template_allowlist is corrupt (%); blocking all templates', SQLERRM;
parsed_ids := ARRAY[]::uuid[];
END;
-- A valid nonempty list allows matching existing templates only. Missing, null,
-- or empty data leaves templates allowed. Corrupt data blocks all templates.
UPDATE templates
SET agents_allowed = (id = ANY(parsed_ids));
END $$;
-- As usual, recreate the view so templates.* is expanded to include the new column.
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.';
+238
View File
@@ -1865,6 +1865,244 @@ func TestMigration000558AuditOAuth2ProviderSettingsEnumInSingleTxn(t *testing.T)
require.NoError(t, err)
}
//nolint:tparallel,paralleltest // Subtests share one database and exercise sequential migration state.
func TestMigration000563TemplateAgentsAllowedBackfill(t *testing.T) {
t.Parallel()
sqlDB, ctx, orgID, userID, templateIDs := setupMigration000563Templates(t)
upSQL, err := os.ReadFile("000563_template_agents_allowed.up.sql")
require.NoError(t, err)
downSQL, err := os.ReadFile("000563_template_agents_allowed.down.sql")
require.NoError(t, err)
staleID := uuid.New()
tests := []struct {
name string
value string
present bool
checkPostMigrationDefault bool
want map[uuid.UUID]bool
}{
{
name: "valid nonempty list",
value: fmt.Sprintf(`[%q]`, templateIDs[0]),
present: true,
checkPostMigrationDefault: true,
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: false,
},
},
{
name: "stale template ID",
value: fmt.Sprintf(`[%q,%q]`, templateIDs[0], staleID),
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: false,
},
},
{
name: "missing",
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: true,
},
},
{
name: "empty string",
value: "",
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: true,
},
},
{
name: "JSON null",
value: "null",
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: true,
},
},
{
name: "invalid JSON",
value: "{",
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "JSON object",
value: `{}`,
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "JSON scalar",
value: `"value"`,
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "invalid UUID element",
value: `["not-a-uuid"]`,
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "null element",
value: `[null]`,
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "mixed valid and invalid elements",
value: fmt.Sprintf(`[%q,"not-a-uuid"]`, templateIDs[0]),
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: false,
templateIDs[1]: false,
},
},
{
name: "empty array",
value: "[]",
present: true,
want: map[uuid.UUID]bool{
templateIDs[0]: true,
templateIDs[1]: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := sqlDB.ExecContext(ctx, `DELETE FROM site_configs WHERE key = 'agents_template_allowlist'`)
require.NoError(t, err)
if tt.present {
_, err = sqlDB.ExecContext(ctx, `INSERT INTO site_configs (key, value) VALUES ('agents_template_allowlist', $1)`, tt.value)
require.NoError(t, err)
}
_, err = sqlDB.ExecContext(ctx, string(upSQL))
require.NoError(t, err)
t.Cleanup(func() {
_, err := sqlDB.ExecContext(ctx, string(downSQL))
require.NoError(t, err)
})
rows, err := sqlDB.QueryContext(ctx, `SELECT id, agents_allowed FROM templates`)
require.NoError(t, err)
got := make(map[uuid.UUID]bool, len(templateIDs))
for rows.Next() {
var id uuid.UUID
var agentsAllowed bool
require.NoError(t, rows.Scan(&id, &agentsAllowed))
got[id] = agentsAllowed
}
require.NoError(t, rows.Close())
require.NoError(t, rows.Err())
require.Equal(t, tt.want, got)
var stored string
err = sqlDB.QueryRowContext(ctx, `SELECT value FROM site_configs WHERE key = 'agents_template_allowlist'`).Scan(&stored)
if tt.present {
require.NoError(t, err)
require.Equal(t, tt.value, stored)
} else {
require.ErrorIs(t, err, sql.ErrNoRows)
}
if tt.checkPostMigrationDefault {
newTemplateID := uuid.New()
_, err = sqlDB.ExecContext(ctx, `
INSERT INTO templates (id, organization_id, name, created_at, updated_at, provisioner, active_version_id, created_by)
VALUES ($1, $2, $3, NOW(), NOW(), 'terraform', $4, $5)
`, newTemplateID, orgID, "post-migration-template", uuid.New(), userID)
require.NoError(t, err)
t.Cleanup(func() {
_, err := sqlDB.ExecContext(ctx, `DELETE FROM templates WHERE id = $1`, newTemplateID)
require.NoError(t, err)
})
var agentsAllowed bool
err = sqlDB.QueryRowContext(ctx, `SELECT agents_allowed FROM template_with_names WHERE id = $1`, newTemplateID).Scan(&agentsAllowed)
require.NoError(t, err)
require.True(t, agentsAllowed)
}
})
}
}
func setupMigration000563Templates(t *testing.T) (
sqlDB *sql.DB,
ctx context.Context,
orgID uuid.UUID,
userID uuid.UUID,
templateIDs []uuid.UUID,
) {
t.Helper()
const migrationVersion = 562
sqlDB = testSQLDB(t)
next, err := migrations.Stepper(sqlDB)
require.NoError(t, err)
for {
version, more, err := next()
require.NoError(t, err)
if !more {
t.Fatalf("migration %d not found", migrationVersion)
}
if version == migrationVersion-1 {
break
}
}
ctx = testutil.Context(t, testutil.WaitSuperLong)
now := time.Now().UTC().Truncate(time.Microsecond)
orgID = uuid.New()
userID = uuid.New()
templateIDs = []uuid.UUID{uuid.New(), uuid.New()}
_, err = sqlDB.ExecContext(ctx, `
INSERT INTO organizations (id, name, display_name, description, created_at, updated_at, default_org_member_roles)
VALUES ($1, $2, $3, $4, $5, $5, '{}')
`, orgID, "agents-allowed-org", "Agents Allowed Org", "Migration test", now)
require.NoError(t, err)
_, err = sqlDB.ExecContext(ctx, `
INSERT INTO users (id, username, email, hashed_password, created_at, updated_at, status, rbac_roles, login_type)
VALUES ($1, $2, $3, $4, $5, $5, 'active', '{}', 'password')
`, userID, "agents-allowed-user", "agents-allowed@example.com", []byte{}, now)
require.NoError(t, err)
for i, templateID := range templateIDs {
_, err = sqlDB.ExecContext(ctx, `
INSERT INTO templates (id, organization_id, name, created_at, updated_at, provisioner, active_version_id, created_by)
VALUES ($1, $2, $3, $4, $4, 'terraform', $5, $6)
`, templateID, orgID, fmt.Sprintf("agents-allowed-template-%d", i), now, uuid.New(), userID)
require.NoError(t, err)
}
return sqlDB, ctx, orgID, userID, templateIDs
}
func TestMigration000498SoftDeleteStaleWorkspaceAgents(t *testing.T) {
t.Parallel()
+2
View File
@@ -86,6 +86,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
pq.Array(arg.IDs),
arg.Deprecated,
arg.HasAITask,
arg.AgentsAllowed,
arg.AuthorID,
arg.AuthorUsername,
arg.HasExternalAgent,
@@ -130,6 +131,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
&i.CorsBehavior,
&i.DisableModuleCache,
&i.TimeTilAutostopNotify,
&i.AgentsAllowed,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
+3
View File
@@ -5910,6 +5910,7 @@ type Template struct {
CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"`
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"`
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"`
@@ -5962,6 +5963,8 @@ type TemplateTable struct {
DisableModuleCache bool `db:"disable_module_cache" json:"disable_module_cache"`
// How long before the workspace autostop deadline to send a reminder notification, in nanoseconds. 0 disables the notification.
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"`
}
// Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute.
+88
View File
@@ -890,6 +890,94 @@ func TestGetWorkspaceAgentUsageStats(t *testing.T) {
})
}
//nolint:tparallel,paralleltest // Subtests share one database seeded by the parent test.
func TestGetTemplatesWithAgentsAllowedFilter(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitMedium)
org := dbgen.Organization(t, db, database.Organization{})
user := dbgen.User(t, db, database.User{})
allowed := dbgen.Template(t, db, database.Template{
OrganizationID: org.ID,
CreatedBy: user.ID,
AgentsAllowed: true,
})
require.True(t, allowed.AgentsAllowed)
blocked := dbgen.Template(t, db, database.Template{
OrganizationID: org.ID,
CreatedBy: user.ID,
AgentsAllowed: false,
})
tests := []struct {
name string
value sql.NullBool
want []uuid.UUID
}{
{
name: "unset",
want: []uuid.UUID{allowed.ID, blocked.ID},
},
{
name: "allowed",
value: sql.NullBool{Bool: true, Valid: true},
want: []uuid.UUID{allowed.ID},
},
{
name: "blocked",
value: sql.NullBool{Bool: false, Valid: true},
want: []uuid.UUID{blocked.ID},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := db.GetTemplatesWithFilter(ctx, database.GetTemplatesWithFilterParams{
Deleted: false,
OrganizationID: org.ID,
AgentsAllowed: tt.value,
})
require.NoError(t, err)
gotIDs := make([]uuid.UUID, 0, len(got))
for _, template := range got {
gotIDs = append(gotIDs, template.ID)
}
require.ElementsMatch(t, tt.want, gotIDs)
})
}
byID, err := db.GetTemplateByID(ctx, blocked.ID)
require.NoError(t, err)
require.False(t, byID.AgentsAllowed)
all, err := db.GetTemplates(ctx)
require.NoError(t, err)
require.Len(t, all, 2)
for _, template := range all {
if template.ID == blocked.ID {
require.False(t, template.AgentsAllowed)
}
}
prepared, err := (&coderdtest.FakeAuthorizer{}).Prepare(
ctx,
rbac.Subject{},
policy.ActionRead,
rbac.ResourceTemplate.Type,
)
require.NoError(t, err)
authorized, err := db.GetAuthorizedTemplates(ctx, database.GetTemplatesWithFilterParams{
Deleted: false,
OrganizationID: org.ID,
AgentsAllowed: sql.NullBool{Bool: false, Valid: true},
}, prepared)
require.NoError(t, err)
require.Len(t, authorized, 1)
require.Equal(t, blocked.ID, authorized[0].ID)
require.False(t, authorized[0].AgentsAllowed)
}
func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) {
t.Parallel()
+32 -14
View File
@@ -26718,7 +26718,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, 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, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon
FROM
template_with_names
WHERE
@@ -26763,6 +26763,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
&i.CorsBehavior,
&i.DisableModuleCache,
&i.TimeTilAutostopNotify,
&i.AgentsAllowed,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -26775,7 +26776,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, 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, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon
FROM
template_with_names AS templates
WHERE
@@ -26828,6 +26829,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
&i.CorsBehavior,
&i.DisableModuleCache,
&i.TimeTilAutostopNotify,
&i.AgentsAllowed,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -26839,7 +26841,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, 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, 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
`
@@ -26885,6 +26887,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
&i.CorsBehavior,
&i.DisableModuleCache,
&i.TimeTilAutostopNotify,
&i.AgentsAllowed,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -26907,7 +26910,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.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.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
@@ -26974,23 +26977,29 @@ WHERE
tv.has_ai_task = $9 :: boolean
ELSE true
END
-- Filter by agents_allowed
AND CASE
WHEN $10 :: boolean IS NOT NULL THEN
t.agents_allowed = $10 :: boolean
ELSE true
END
-- Filter by author_id
AND CASE
WHEN $10 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.created_by = $10
WHEN $11 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
t.created_by = $11
ELSE true
END
-- Filter by author_username
AND CASE
WHEN $11 :: text != '' THEN
t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($11) AND deleted = false)
WHEN $12 :: text != '' THEN
t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($12) AND deleted = false)
ELSE true
END
-- Filter by has_external_agent in latest version
AND CASE
WHEN $12 :: boolean IS NOT NULL THEN
tv.has_external_agent = $12 :: boolean
WHEN $13 :: boolean IS NOT NULL THEN
tv.has_external_agent = $13 :: boolean
ELSE true
END
-- Authorize Filter clause will be injected below in GetAuthorizedTemplates
@@ -27008,6 +27017,7 @@ type GetTemplatesWithFilterParams struct {
IDs []uuid.UUID `db:"ids" json:"ids"`
Deprecated sql.NullBool `db:"deprecated" json:"deprecated"`
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
AgentsAllowed sql.NullBool `db:"agents_allowed" json:"agents_allowed"`
AuthorID uuid.UUID `db:"author_id" json:"author_id"`
AuthorUsername string `db:"author_username" json:"author_username"`
HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"`
@@ -27024,6 +27034,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
pq.Array(arg.IDs),
arg.Deprecated,
arg.HasAITask,
arg.AgentsAllowed,
arg.AuthorID,
arg.AuthorUsername,
arg.HasExternalAgent,
@@ -27068,6 +27079,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
&i.CorsBehavior,
&i.DisableModuleCache,
&i.TimeTilAutostopNotify,
&i.AgentsAllowed,
&i.CreatedByAvatarURL,
&i.CreatedByUsername,
&i.CreatedByName,
@@ -27107,10 +27119,11 @@ INSERT INTO
allow_user_cancel_workspace_jobs,
max_port_sharing_level,
use_classic_parameter_flow,
cors_behavior
cors_behavior,
agents_allowed
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
`
type InsertTemplateParams struct {
@@ -27131,6 +27144,7 @@ type InsertTemplateParams 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"`
AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"`
}
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error {
@@ -27152,6 +27166,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
arg.MaxPortSharingLevel,
arg.UseClassicParameterFlow,
arg.CorsBehavior,
arg.AgentsAllowed,
)
return err
}
@@ -27254,7 +27269,8 @@ SET
max_port_sharing_level = $9,
use_classic_parameter_flow = $10,
cors_behavior = $11,
disable_module_cache = $12
disable_module_cache = $12,
agents_allowed = $13
WHERE
id = $1
`
@@ -27272,6 +27288,7 @@ type UpdateTemplateMetaByIDParams struct {
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"`
AgentsAllowed bool `db:"agents_allowed" json:"agents_allowed"`
}
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error {
@@ -27288,6 +27305,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
arg.UseClassicParameterFlow,
arg.CorsBehavior,
arg.DisableModuleCache,
arg.AgentsAllowed,
)
return err
}
@@ -38570,7 +38588,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
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
FROM
templates
WHERE
+11 -3
View File
@@ -77,6 +77,12 @@ WHERE
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
@@ -137,10 +143,11 @@ INSERT INTO
allow_user_cancel_workspace_jobs,
max_port_sharing_level,
use_classic_parameter_flow,
cors_behavior
cors_behavior,
agents_allowed
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18);
-- name: UpdateTemplateActiveVersionByID :exec
UPDATE
@@ -174,7 +181,8 @@ SET
max_port_sharing_level = $9,
use_classic_parameter_flow = $10,
cors_behavior = $11,
disable_module_cache = $12
disable_module_cache = $12,
agents_allowed = $13
WHERE
id = $1
;