diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go
index 545a94b0f6..edc175ada5 100644
--- a/coderd/database/dbauthz/dbauthz.go
+++ b/coderd/database/dbauthz/dbauthz.go
@@ -2221,6 +2221,14 @@ func (q *querier) GetTemplateParameterInsights(ctx context.Context, arg database
return q.db.GetTemplateParameterInsights(ctx, arg)
}
+func (q *querier) GetTemplatePrebuildState(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplatePrebuildStateRow, error) {
+ // TODO: authz
+ if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate); err != nil {
+ return nil, err
+ }
+ return q.db.GetTemplatePrebuildState(ctx, templateID)
+}
+
func (q *querier) GetTemplateUsageStats(ctx context.Context, arg database.GetTemplateUsageStatsParams) ([]database.TemplateUsageStat, error) {
if err := q.authorizeTemplateInsights(ctx, arg.TemplateIDs); err != nil {
return nil, err
diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go
index 780a180f1f..95a8f312f8 100644
--- a/coderd/database/dbmem/dbmem.go
+++ b/coderd/database/dbmem/dbmem.go
@@ -5464,6 +5464,10 @@ func (q *FakeQuerier) GetTemplateParameterInsights(ctx context.Context, arg data
return rows, nil
}
+func (q *FakeQuerier) GetTemplatePrebuildState(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplatePrebuildStateRow, error) {
+ panic("not implemented")
+}
+
func (q *FakeQuerier) GetTemplateUsageStats(_ context.Context, arg database.GetTemplateUsageStatsParams) ([]database.TemplateUsageStat, error) {
err := validateDatabaseType(arg)
if err != nil {
diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go
index fc84f556aa..82c1f09456 100644
--- a/coderd/database/dbmetrics/querymetrics.go
+++ b/coderd/database/dbmetrics/querymetrics.go
@@ -1253,6 +1253,13 @@ func (m queryMetricsStore) GetTemplateParameterInsights(ctx context.Context, arg
return r0, r1
}
+func (m queryMetricsStore) GetTemplatePrebuildState(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplatePrebuildStateRow, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetTemplatePrebuildState(ctx, templateID)
+ m.queryLatencies.WithLabelValues("GetTemplatePrebuildState").Observe(time.Since(start).Seconds())
+ return r0, r1
+}
+
func (m queryMetricsStore) GetTemplateUsageStats(ctx context.Context, arg database.GetTemplateUsageStatsParams) ([]database.TemplateUsageStat, error) {
start := time.Now()
r0, r1 := m.s.GetTemplateUsageStats(ctx, arg)
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 20e7d14b57..026684164f 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -769,6 +769,7 @@ CREATE TABLE users (
github_com_user_id bigint,
hashed_one_time_passcode bytea,
one_time_passcode_expires_at timestamp with time zone,
+ is_system boolean DEFAULT false,
CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL))))
);
@@ -784,6 +785,8 @@ COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-pass
COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.';
+COMMENT ON COLUMN users.is_system IS 'Determines if a user is a system user, and therefore cannot login or perform normal actions';
+
CREATE VIEW group_members_expanded AS
WITH all_members AS (
SELECT group_members.user_id,
@@ -1780,6 +1783,52 @@ CREATE TABLE workspace_modules (
created_at timestamp with time zone NOT NULL
);
+CREATE VIEW workspace_prebuild_builds AS
+ SELECT workspace_builds.workspace_id
+ FROM workspace_builds
+ WHERE (workspace_builds.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid);
+
+CREATE TABLE workspaces (
+ id uuid NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ owner_id uuid NOT NULL,
+ organization_id uuid NOT NULL,
+ template_id uuid NOT NULL,
+ deleted boolean DEFAULT false NOT NULL,
+ name character varying(64) NOT NULL,
+ autostart_schedule text,
+ ttl bigint,
+ last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
+ dormant_at timestamp with time zone,
+ deleting_at timestamp with time zone,
+ automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL,
+ favorite boolean DEFAULT false NOT NULL,
+ next_start_at timestamp with time zone
+);
+
+COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';
+
+CREATE VIEW workspace_prebuilds AS
+ SELECT workspaces.id,
+ workspaces.created_at,
+ workspaces.updated_at,
+ workspaces.owner_id,
+ workspaces.organization_id,
+ workspaces.template_id,
+ workspaces.deleted,
+ workspaces.name,
+ workspaces.autostart_schedule,
+ workspaces.ttl,
+ workspaces.last_used_at,
+ workspaces.dormant_at,
+ workspaces.deleting_at,
+ workspaces.automatic_updates,
+ workspaces.favorite,
+ workspaces.next_start_at
+ FROM workspaces
+ WHERE (workspaces.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid);
+
CREATE TABLE workspace_proxies (
id uuid NOT NULL,
name text NOT NULL,
@@ -1850,27 +1899,6 @@ CREATE TABLE workspace_resources (
module_path text
);
-CREATE TABLE workspaces (
- id uuid NOT NULL,
- created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL,
- owner_id uuid NOT NULL,
- organization_id uuid NOT NULL,
- template_id uuid NOT NULL,
- deleted boolean DEFAULT false NOT NULL,
- name character varying(64) NOT NULL,
- autostart_schedule text,
- ttl bigint,
- last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
- dormant_at timestamp with time zone,
- deleting_at timestamp with time zone,
- automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL,
- favorite boolean DEFAULT false NOT NULL,
- next_start_at timestamp with time zone
-);
-
-COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';
-
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
@@ -2247,6 +2275,8 @@ COMMENT ON INDEX template_usage_stats_start_time_template_id_user_id_idx IS 'Ind
CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);
+CREATE INDEX user_is_system_idx ON users USING btree (is_system);
+
CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text);
CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false);
diff --git a/coderd/database/migrations/000289_system_user.down.sql b/coderd/database/migrations/000289_system_user.down.sql
new file mode 100644
index 0000000000..c81e35ae4e
--- /dev/null
+++ b/coderd/database/migrations/000289_system_user.down.sql
@@ -0,0 +1,4 @@
+ALTER TABLE users
+ DROP COLUMN IF EXISTS is_system;
+
+DROP INDEX IF EXISTS user_is_system_idx;
diff --git a/coderd/database/migrations/000289_system_user.up.sql b/coderd/database/migrations/000289_system_user.up.sql
new file mode 100644
index 0000000000..0fef68299f
--- /dev/null
+++ b/coderd/database/migrations/000289_system_user.up.sql
@@ -0,0 +1,6 @@
+ALTER TABLE users
+ ADD COLUMN is_system bool DEFAULT false;
+
+CREATE INDEX user_is_system_idx ON users USING btree (is_system);
+
+COMMENT ON COLUMN users.is_system IS 'Determines if a user is a system user, and therefore cannot login or perform normal actions';
diff --git a/coderd/database/migrations/000290_prebuilds.down.sql b/coderd/database/migrations/000290_prebuilds.down.sql
new file mode 100644
index 0000000000..b6e56ba383
--- /dev/null
+++ b/coderd/database/migrations/000290_prebuilds.down.sql
@@ -0,0 +1,7 @@
+-- Revert prebuild views
+DROP VIEW IF EXISTS workspace_prebuild_builds;
+DROP VIEW IF EXISTS workspace_prebuilds;
+
+-- Revert user operations
+DELETE FROM user_status_changes WHERE user_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
+DELETE FROM users WHERE id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
diff --git a/coderd/database/migrations/000290_prebuilds.up.sql b/coderd/database/migrations/000290_prebuilds.up.sql
new file mode 100644
index 0000000000..18eda0f1d5
--- /dev/null
+++ b/coderd/database/migrations/000290_prebuilds.up.sql
@@ -0,0 +1,14 @@
+-- TODO: using "none" for login type produced this error: 'unsafe use of new value "none" of enum type login_type' -> not sure why
+INSERT INTO users (id, email, username, name, created_at, updated_at, status, rbac_roles, hashed_password, is_system)
+VALUES ('c42fdf75-3097-471c-8c33-fb52454d81c0', 'prebuilds@system', 'prebuilds', 'Prebuilds Owner', now(), now(),
+ 'active', '{}', 'none', true);
+
+CREATE VIEW workspace_prebuilds AS
+SELECT *
+FROM workspaces
+WHERE owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
+
+CREATE VIEW workspace_prebuild_builds AS
+SELECT workspace_id
+FROM workspace_builds
+WHERE initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
diff --git a/coderd/database/migrations/000291_preset_prebuilds.down.sql b/coderd/database/migrations/000291_preset_prebuilds.down.sql
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/coderd/database/migrations/000291_preset_prebuilds.up.sql b/coderd/database/migrations/000291_preset_prebuilds.up.sql
new file mode 100644
index 0000000000..c6392fdabb
--- /dev/null
+++ b/coderd/database/migrations/000291_preset_prebuilds.up.sql
@@ -0,0 +1 @@
+CREATE TABLE
diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go
index 78f6285e3c..fff872b37f 100644
--- a/coderd/database/modelqueries.go
+++ b/coderd/database/modelqueries.go
@@ -422,6 +422,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams,
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
&i.Count,
); err != nil {
return nil, err
diff --git a/coderd/database/models.go b/coderd/database/models.go
index fc11e1f4f5..c59a54ab80 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -3035,6 +3035,8 @@ type User struct {
HashedOneTimePasscode []byte `db:"hashed_one_time_passcode" json:"hashed_one_time_passcode"`
// The time when the one-time-passcode expires.
OneTimePasscodeExpiresAt sql.NullTime `db:"one_time_passcode_expires_at" json:"one_time_passcode_expires_at"`
+ // Determines if a user is a system user, and therefore cannot login or perform normal actions
+ IsSystem sql.NullBool `db:"is_system" json:"is_system"`
}
// Tracks when users were deleted
@@ -3352,6 +3354,29 @@ type WorkspaceModule struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
+type WorkspacePrebuild struct {
+ ID uuid.UUID `db:"id" json:"id"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+ OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
+ OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
+ TemplateID uuid.UUID `db:"template_id" json:"template_id"`
+ Deleted bool `db:"deleted" json:"deleted"`
+ Name string `db:"name" json:"name"`
+ AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
+ Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
+ LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
+ DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
+ DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
+ AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
+ Favorite bool `db:"favorite" json:"favorite"`
+ NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
+}
+
+type WorkspacePrebuildBuild struct {
+ WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
+}
+
type WorkspaceProxy struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
diff --git a/coderd/database/querier.go b/coderd/database/querier.go
index 5f9856028b..edddd4ac14 100644
--- a/coderd/database/querier.go
+++ b/coderd/database/querier.go
@@ -265,6 +265,8 @@ type sqlcQuerier interface {
// created in the timeframe and return the aggregate usage counts of parameter
// values.
GetTemplateParameterInsights(ctx context.Context, arg GetTemplateParameterInsightsParams) ([]GetTemplateParameterInsightsRow, error)
+ // TODO: need to store the desired instances & autoscaling schedules in db; use desired value here
+ GetTemplatePrebuildState(ctx context.Context, templateID uuid.UUID) ([]GetTemplatePrebuildStateRow, error)
GetTemplateUsageStats(ctx context.Context, arg GetTemplateUsageStatsParams) ([]TemplateUsageStat, error)
GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (TemplateVersion, error)
GetTemplateVersionByJobID(ctx context.Context, jobID uuid.UUID) (TemplateVersion, error)
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index d8c2b3a77d..bf2bace079 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -5395,6 +5395,75 @@ func (q *sqlQuerier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.
return items, nil
}
+const getTemplatePrebuildState = `-- name: GetTemplatePrebuildState :many
+WITH latest_workspace_builds AS (SELECT wb.id,
+ wb.workspace_id,
+ wbmax.template_id,
+ wb.template_version_id
+ FROM (SELECT tv.template_id,
+ wbmax.workspace_id,
+ MAX(wbmax.build_number) as max_build_number
+ FROM workspace_builds wbmax
+ JOIN template_versions tv ON (tv.id = wbmax.template_version_id)
+ GROUP BY tv.template_id, wbmax.workspace_id) wbmax
+ JOIN workspace_builds wb ON (
+ wb.workspace_id = wbmax.workspace_id
+ AND wb.build_number = wbmax.max_build_number
+ ))
+SELECT CAST(1 AS integer) AS desired,
+ CAST(COUNT(wp.*) AS integer) AS actual,
+ CAST(0 AS integer) AS extraneous, -- TODO: calculate this by subtracting actual from count not matching template version
+ t.deleted,
+ t.deprecated,
+ tv.id AS template_version_id
+FROM latest_workspace_builds lwb
+ JOIN template_versions tv ON lwb.template_version_id = tv.id
+ JOIN templates t ON t.id = lwb.template_id
+ LEFT JOIN workspace_prebuilds wp ON wp.id = lwb.workspace_id
+WHERE t.id = $1::uuid
+GROUP BY t.id, t.deleted, t.deprecated, tv.id
+`
+
+type GetTemplatePrebuildStateRow struct {
+ Desired int32 `db:"desired" json:"desired"`
+ Actual int32 `db:"actual" json:"actual"`
+ Extraneous int32 `db:"extraneous" json:"extraneous"`
+ Deleted bool `db:"deleted" json:"deleted"`
+ Deprecated string `db:"deprecated" json:"deprecated"`
+ TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
+}
+
+// TODO: need to store the desired instances & autoscaling schedules in db; use desired value here
+func (q *sqlQuerier) GetTemplatePrebuildState(ctx context.Context, templateID uuid.UUID) ([]GetTemplatePrebuildStateRow, error) {
+ rows, err := q.db.QueryContext(ctx, getTemplatePrebuildState, templateID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetTemplatePrebuildStateRow
+ for rows.Next() {
+ var i GetTemplatePrebuildStateRow
+ if err := rows.Scan(
+ &i.Desired,
+ &i.Actual,
+ &i.Extraneous,
+ &i.Deleted,
+ &i.Deprecated,
+ &i.TemplateVersionID,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getPresetByWorkspaceBuildID = `-- name: GetPresetByWorkspaceBuildID :one
SELECT
template_version_presets.id, template_version_presets.template_version_id, template_version_presets.name, template_version_presets.created_at
@@ -10863,6 +10932,7 @@ func (q *sqlQuerier) UpdateUserLinkedID(ctx context.Context, arg UpdateUserLinke
const allUserIDs = `-- name: AllUserIDs :many
SELECT DISTINCT id FROM USERS
+ WHERE is_system = false
`
// AllUserIDs returns all UserIDs regardless of user status or deletion.
@@ -10896,6 +10966,7 @@ FROM
users
WHERE
status = 'active'::user_status AND deleted = false
+ AND is_system = false
`
func (q *sqlQuerier) GetActiveUserCount(ctx context.Context) (int64, error) {
@@ -10972,7 +11043,7 @@ func (q *sqlQuerier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.
const getUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one
SELECT
- id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
FROM
users
WHERE
@@ -11009,13 +11080,14 @@ func (q *sqlQuerier) GetUserByEmailOrUsername(ctx context.Context, arg GetUserBy
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
const getUserByID = `-- name: GetUserByID :one
SELECT
- id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
FROM
users
WHERE
@@ -11046,6 +11118,7 @@ func (q *sqlQuerier) GetUserByID(ctx context.Context, id uuid.UUID) (User, error
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11057,6 +11130,7 @@ FROM
users
WHERE
deleted = false
+ AND is_system = false
`
func (q *sqlQuerier) GetUserCount(ctx context.Context) (int64, error) {
@@ -11068,11 +11142,12 @@ func (q *sqlQuerier) GetUserCount(ctx context.Context) (int64, error) {
const getUsers = `-- name: GetUsers :many
SELECT
- id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, COUNT(*) OVER() AS count
+ id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system, COUNT(*) OVER() AS count
FROM
users
WHERE
users.deleted = false
+ AND is_system = false
AND CASE
-- This allows using the last element on a page as effectively a cursor.
-- This is an important option for scripts that need to paginate without
@@ -11183,6 +11258,7 @@ type GetUsersRow struct {
GithubComUserID sql.NullInt64 `db:"github_com_user_id" json:"github_com_user_id"`
HashedOneTimePasscode []byte `db:"hashed_one_time_passcode" json:"hashed_one_time_passcode"`
OneTimePasscodeExpiresAt sql.NullTime `db:"one_time_passcode_expires_at" json:"one_time_passcode_expires_at"`
+ IsSystem sql.NullBool `db:"is_system" json:"is_system"`
Count int64 `db:"count" json:"count"`
}
@@ -11226,6 +11302,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
&i.Count,
); err != nil {
return nil, err
@@ -11242,7 +11319,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse
}
const getUsersByIDs = `-- name: GetUsersByIDs :many
-SELECT id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at FROM users WHERE id = ANY($1 :: uuid [ ])
+SELECT id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system FROM users WHERE id = ANY($1 :: uuid [ ])
`
// This shouldn't check for deleted, because it's frequently used
@@ -11276,6 +11353,7 @@ func (q *sqlQuerier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
); err != nil {
return nil, err
}
@@ -11309,7 +11387,7 @@ VALUES
-- if the status passed in is empty, fallback to dormant, which is what
-- we were doing before.
COALESCE(NULLIF($10::text, '')::user_status, 'dormant'::user_status)
- ) RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ ) RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type InsertUserParams struct {
@@ -11358,6 +11436,7 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11371,6 +11450,7 @@ SET
WHERE
last_seen_at < $2 :: timestamp
AND status = 'active'::user_status
+ AND is_system = false
RETURNING id, email, username, last_seen_at
`
@@ -11422,7 +11502,7 @@ SET
updated_at = $3
WHERE
id = $1
-RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserAppearanceSettingsParams struct {
@@ -11453,6 +11533,7 @@ func (q *sqlQuerier) UpdateUserAppearanceSettings(ctx context.Context, arg Updat
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11539,7 +11620,7 @@ SET
last_seen_at = $2,
updated_at = $3
WHERE
- id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserLastSeenAtParams struct {
@@ -11570,6 +11651,7 @@ func (q *sqlQuerier) UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLas
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11587,7 +11669,7 @@ SET
'':: bytea
END
WHERE
- id = $2 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ id = $2 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserLoginTypeParams struct {
@@ -11617,6 +11699,7 @@ func (q *sqlQuerier) UpdateUserLoginType(ctx context.Context, arg UpdateUserLogi
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11632,7 +11715,7 @@ SET
name = $6
WHERE
id = $1
-RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserProfileParams struct {
@@ -11673,6 +11756,7 @@ func (q *sqlQuerier) UpdateUserProfile(ctx context.Context, arg UpdateUserProfil
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11684,7 +11768,7 @@ SET
quiet_hours_schedule = $2
WHERE
id = $1
-RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserQuietHoursScheduleParams struct {
@@ -11714,6 +11798,7 @@ func (q *sqlQuerier) UpdateUserQuietHoursSchedule(ctx context.Context, arg Updat
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11726,7 +11811,7 @@ SET
rbac_roles = ARRAY(SELECT DISTINCT UNNEST($1 :: text[]))
WHERE
id = $2
-RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserRolesParams struct {
@@ -11756,6 +11841,7 @@ func (q *sqlQuerier) UpdateUserRoles(ctx context.Context, arg UpdateUserRolesPar
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
@@ -11767,7 +11853,7 @@ SET
status = $2,
updated_at = $3
WHERE
- id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at
+ id = $1 RETURNING id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, login_type, avatar_url, deleted, last_seen_at, quiet_hours_schedule, theme_preference, name, github_com_user_id, hashed_one_time_passcode, one_time_passcode_expires_at, is_system
`
type UpdateUserStatusParams struct {
@@ -11798,6 +11884,7 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP
&i.GithubComUserID,
&i.HashedOneTimePasscode,
&i.OneTimePasscodeExpiresAt,
+ &i.IsSystem,
)
return i, err
}
diff --git a/coderd/database/queries/prebuilds.sql b/coderd/database/queries/prebuilds.sql
new file mode 100644
index 0000000000..cd70333b31
--- /dev/null
+++ b/coderd/database/queries/prebuilds.sql
@@ -0,0 +1,28 @@
+-- name: GetTemplatePrebuildState :many
+WITH latest_workspace_builds AS (SELECT wb.id,
+ wb.workspace_id,
+ wbmax.template_id,
+ wb.template_version_id
+ FROM (SELECT tv.template_id,
+ wbmax.workspace_id,
+ MAX(wbmax.build_number) as max_build_number
+ FROM workspace_builds wbmax
+ JOIN template_versions tv ON (tv.id = wbmax.template_version_id)
+ GROUP BY tv.template_id, wbmax.workspace_id) wbmax
+ JOIN workspace_builds wb ON (
+ wb.workspace_id = wbmax.workspace_id
+ AND wb.build_number = wbmax.max_build_number
+ ))
+-- TODO: need to store the desired instances & autoscaling schedules in db; use desired value here
+SELECT CAST(1 AS integer) AS desired,
+ CAST(COUNT(wp.*) AS integer) AS actual,
+ CAST(0 AS integer) AS extraneous, -- TODO: calculate this by subtracting actual from count not matching template version
+ t.deleted,
+ t.deprecated,
+ tv.id AS template_version_id
+FROM latest_workspace_builds lwb
+ JOIN template_versions tv ON lwb.template_version_id = tv.id
+ JOIN templates t ON t.id = lwb.template_id
+ LEFT JOIN workspace_prebuilds wp ON wp.id = lwb.workspace_id
+WHERE t.id = @template_id::uuid
+GROUP BY t.id, t.deleted, t.deprecated, tv.id;
diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql
index 1f30a2c2c1..814e1e3381 100644
--- a/coderd/database/queries/users.sql
+++ b/coderd/database/queries/users.sql
@@ -46,7 +46,8 @@ SELECT
FROM
users
WHERE
- deleted = false;
+ deleted = false
+ AND is_system = false;
-- name: GetActiveUserCount :one
SELECT
@@ -54,7 +55,8 @@ SELECT
FROM
users
WHERE
- status = 'active'::user_status AND deleted = false;
+ status = 'active'::user_status AND deleted = false
+ AND is_system = false;
-- name: InsertUser :one
INSERT INTO
@@ -144,6 +146,7 @@ FROM
users
WHERE
users.deleted = false
+ AND is_system = false
AND CASE
-- This allows using the last element on a page as effectively a cursor.
-- This is an important option for scripts that need to paginate without
@@ -302,11 +305,13 @@ SET
WHERE
last_seen_at < @last_seen_after :: timestamp
AND status = 'active'::user_status
+ AND is_system = false
RETURNING id, email, username, last_seen_at;
-- AllUserIDs returns all UserIDs regardless of user status or deletion.
-- name: AllUserIDs :many
-SELECT DISTINCT id FROM USERS;
+SELECT DISTINCT id FROM USERS
+ WHERE is_system = false;
-- name: UpdateUserHashedOneTimePasscode :exec
UPDATE
diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index 2131e7746d..f87c6fbc9f 100644
--- a/docs/admin/security/audit-logs.md
+++ b/docs/admin/security/audit-logs.md
@@ -28,7 +28,7 @@ We track the following resources:
| RoleSyncSettings
|
Field | Tracked |
| field | true |
mapping | true |
|
| Template
write, delete | Field | Tracked |
| active_version_id | true |
activity_bump | 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 |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_username | false |
default_ttl | true |
deleted | false |
deprecated | true |
description | 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_dormant | true |
time_til_dormant_autodelete | true |
updated_at | false |
user_acl | true |
|
| TemplateVersion
create, write | Field | Tracked |
| archived | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_username | false |
external_auth_providers | 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 |
created_at | false |
deleted | true |
email | true |
github_com_user_id | false |
hashed_one_time_passcode | false |
hashed_password | true |
id | 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 |
theme_preference | false |
updated_at | false |
username | true |
|
+| User
create, write, delete | Field | Tracked |
| avatar_url | false |
created_at | false |
deleted | true |
email | true |
github_com_user_id | false |
hashed_one_time_passcode | false |
hashed_password | true |
id | 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 |
theme_preference | false |
updated_at | false |
username | true |
|
| WorkspaceBuild
start, stop | Field | Tracked |
| build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
id | false |
initiator_by_avatar_url | false |
initiator_by_username | false |
initiator_id | false |
job_id | false |
max_deadline | false |
provisioner_state | false |
reason | false |
template_version_id | true |
template_version_preset_id | false |
transition | false |
updated_at | false |
workspace_id | false |
|
| WorkspaceProxy
| Field | Tracked |
| created_at | true |
deleted | false |
derp_enabled | true |
derp_only | true |
display_name | true |
icon | true |
id | true |
name | true |
region_id | true |
token_hashed_secret | true |
updated_at | false |
url | true |
version | true |
wildcard_hostname | true |
|
| WorkspaceTable
| Field | Tracked |
| automatic_updates | true |
autostart_schedule | true |
created_at | false |
deleted | false |
deleting_at | true |
dormant_at | true |
favorite | true |
id | true |
last_used_at | false |
name | true |
next_start_at | true |
organization_id | false |
owner_id | true |
template_id | true |
ttl | true |
updated_at | false |
|
diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go
index d43b2e224e..3e06778a12 100644
--- a/enterprise/audit/table.go
+++ b/enterprise/audit/table.go
@@ -150,6 +150,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
"github_com_user_id": ActionIgnore,
"hashed_one_time_passcode": ActionIgnore,
"one_time_passcode_expires_at": ActionTrack,
+ "is_system": ActionTrack, // Should never change, but track it anyway.
},
&database.WorkspaceTable{}: {
"id": ActionTrack,