From eb5cb38b7c9917f95f899c2d8cb9a0d0d41addb7 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 11 Oct 2023 22:26:32 +0000 Subject: [PATCH 01/30] add migration --- coderd/database/dump.sql | 8 +++++ .../000165_template_promotion.down.sql | 24 ++++++++++++++ .../000165_template_promotion.up.sql | 23 +++++++++++++ coderd/database/models.go | 8 +++++ coderd/database/queries.sql.go | 32 +++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 coderd/database/migrations/000165_template_promotion.down.sql create mode 100644 coderd/database/migrations/000165_template_promotion.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index ab6eb95252f2d..392e1172b13af 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -755,7 +755,11 @@ CREATE TABLE templates ( time_til_dormant_autodelete bigint DEFAULT 0 NOT NULL, autostop_requirement_days_of_week smallint DEFAULT 0 NOT NULL, autostop_requirement_weeks bigint DEFAULT 0 NOT NULL, +<<<<<<< HEAD autostart_block_days_of_week smallint DEFAULT 0 NOT NULL +======= + require_promoted_version boolean DEFAULT false NOT NULL +>>>>>>> 5786e8135 (add migration) ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -799,7 +803,11 @@ CREATE VIEW template_with_users AS templates.time_til_dormant_autodelete, templates.autostop_requirement_days_of_week, templates.autostop_requirement_weeks, +<<<<<<< HEAD templates.autostart_block_days_of_week, +======= + templates.require_promoted_version, +>>>>>>> 5786e8135 (add migration) COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username FROM (public.templates diff --git a/coderd/database/migrations/000165_template_promotion.down.sql b/coderd/database/migrations/000165_template_promotion.down.sql new file mode 100644 index 0000000000000..3236609a1c630 --- /dev/null +++ b/coderd/database/migrations/000165_template_promotion.down.sql @@ -0,0 +1,24 @@ +BEGIN; + +ALTER TABLE templates DROP COLUMN require_promoted_version; + +-- Update the template_with_users view; +DROP VIEW template_with_users; +-- If you need to update this view, put 'DROP VIEW template_with_users;' before this. +CREATE VIEW + template_with_users +AS + SELECT + templates.*, + coalesce(visible_users.avatar_url, '') AS created_by_avatar_url, + coalesce(visible_users.username, '') AS created_by_username + FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id; + +COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.'; + +COMMIT; diff --git a/coderd/database/migrations/000165_template_promotion.up.sql b/coderd/database/migrations/000165_template_promotion.up.sql new file mode 100644 index 0000000000000..8d9b89ce60ab6 --- /dev/null +++ b/coderd/database/migrations/000165_template_promotion.up.sql @@ -0,0 +1,23 @@ +BEGIN; + +DROP VIEW template_with_users; + +ALTER TABLE templates ADD COLUMN require_promoted_version boolean NOT NULL DEFAULT 'f'; + +CREATE VIEW + template_with_users +AS + SELECT + templates.*, + coalesce(visible_users.avatar_url, '') AS created_by_avatar_url, + coalesce(visible_users.username, '') AS created_by_username + FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id; + +COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.'; + +COMMIT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 1684e4fe5ebeb..8affdc5484d65 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1891,7 +1891,11 @@ type Template struct { TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` +<<<<<<< HEAD AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` +======= + RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` +>>>>>>> 5786e8135 (add migration) CreatedByAvatarURL sql.NullString `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` } @@ -1928,8 +1932,12 @@ type TemplateTable struct { AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` // The number of weeks between restarts. 0 or 1 weeks means "every week", 2 week means "every second week", etc. Weeks are counted from January 2, 2023, which is the first Monday of 2023. This is to ensure workspaces are started consistently for all customers on the same n-week cycles. AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` +<<<<<<< HEAD // A bitmap of days of week that autostart of a workspace is not allowed. Default allows all days. This is intended as a cost savings measure to prevent auto start on weekends (for example). AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` +======= + RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` +>>>>>>> 5786e8135 (add migration) } // Joins in the username + avatar url of the created by user. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index fc301d427fa8d..e2358318a9ba0 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4726,7 +4726,11 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT +<<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username +>>>>>>> 5786e8135 (add migration) FROM template_with_users WHERE @@ -4763,7 +4767,11 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, +======= + &i.RequirePromotedVersion, +>>>>>>> 5786e8135 (add migration) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4772,7 +4780,11 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT +<<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username +>>>>>>> 5786e8135 (add migration) FROM template_with_users AS templates WHERE @@ -4817,7 +4829,11 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, +======= + &i.RequirePromotedVersion, +>>>>>>> 5786e8135 (add migration) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4825,7 +4841,11 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many +<<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username FROM template_with_users AS templates +>>>>>>> 5786e8135 (add migration) ORDER BY (name, id) ASC ` @@ -4863,7 +4883,11 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, +======= + &i.RequirePromotedVersion, +>>>>>>> 5786e8135 (add migration) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -4882,7 +4906,11 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT +<<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username +>>>>>>> 5786e8135 (add migration) FROM template_with_users AS templates WHERE @@ -4957,7 +4985,11 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, +======= + &i.RequirePromotedVersion, +>>>>>>> 5786e8135 (add migration) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { From f31b2fdb9d931c56d646f4b0315bf5d6b7810ca6 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 11 Oct 2023 23:21:21 +0000 Subject: [PATCH 02/30] add template update plumbing --- coderd/database/modelmethods.go | 2 +- coderd/database/queries.sql.go | 9 +++++++++ coderd/database/queries/templates.sql | 3 ++- coderd/schedule/template.go | 6 ++++++ codersdk/templates.go | 4 ++++ enterprise/coderd/schedule/template.go | 1 + 6 files changed, 23 insertions(+), 2 deletions(-) diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 66e1618bf570f..ed5bf76b784d7 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -179,7 +179,7 @@ func (w Workspace) ApplicationConnectRBAC() rbac.Object { } func (w Workspace) WorkspaceBuildRBAC(transition WorkspaceTransition) rbac.Object { - // If a workspace is locked it cannot be built. + // If a workspace is dormant it cannot be built. // However we need to allow stopping a workspace by a caller once a workspace // is locked (e.g. for autobuild). Additionally, if a user wants to delete // a locked workspace, they shouldn't have to have it unlocked first. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e2358318a9ba0..7bc577f213fe8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5176,10 +5176,17 @@ SET max_ttl = $6, autostop_requirement_days_of_week = $7, autostop_requirement_weeks = $8, +<<<<<<< HEAD autostart_block_days_of_week = $9, failure_ttl = $10, time_til_dormant = $11, time_til_dormant_autodelete = $12 +======= + failure_ttl = $9, + time_til_dormant = $10, + time_til_dormant_autodelete = $11, + require_promoted_version = $12 +>>>>>>> f6a08b15a (add template update plumbing) WHERE id = $1 ` @@ -5197,6 +5204,7 @@ type UpdateTemplateScheduleByIDParams struct { FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"` TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"` TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` + RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` } func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error { @@ -5213,6 +5221,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT arg.FailureTTL, arg.TimeTilDormant, arg.TimeTilDormantAutoDelete, + arg.RequirePromotedVersion, ) return err } diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index e89c245b0391d..572a450e67b38 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -123,7 +123,8 @@ SET autostart_block_days_of_week = $9, failure_ttl = $10, time_til_dormant = $11, - time_til_dormant_autodelete = $12 + time_til_dormant_autodelete = $12, + require_promoted_version = $13 WHERE id = $1 ; diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index fc274d9a7d8ba..92545cfa7d0c9 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -151,6 +151,7 @@ type TemplateScheduleOptions struct { // workspaces whose dormant_at field violates the new template time_til_dormant_autodelete // threshold. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` + RequirePromotedVersion bool `json:"require_promoted_version"` } // TemplateScheduleStore provides an interface for retrieving template @@ -200,6 +201,7 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te FailureTTL: 0, TimeTilDormant: 0, TimeTilDormantAutoDelete: 0, + RequirePromotedVersion: false, }, nil } @@ -214,6 +216,9 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp var template database.Template err := db.InTx(func(db database.Store) error { + // TODO (JonA): This seems ripe for a bug. Should we not + // be reading the template as part of the tx and then also + // setting our isolation level to repeatable read? err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ ID: tpl.ID, UpdatedAt: dbtime.Now(), @@ -229,6 +234,7 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp FailureTTL: tpl.FailureTTL, TimeTilDormant: tpl.TimeTilDormant, TimeTilDormantAutoDelete: tpl.TimeTilDormantAutoDelete, + RequirePromotedVersion: tpl.RequirePromotedVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) diff --git a/codersdk/templates.go b/codersdk/templates.go index d0ee400a29b20..0ce0993cc2af4 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -221,6 +221,10 @@ type UpdateTemplateMeta struct { // from the template. This is useful for preventing dormant workspaces being immediately // deleted when updating the dormant_ttl field to a new, shorter value. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` + // RequirePromotedVersion mandates workspaces built using this template + // use the latest promoted version of the template. This option has no + // effect on template admins. + RequirePromotedVersion bool `json:"require_promoted_version"` } type TemplateExample struct { diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index 26b0c78c6bfc9..dd28787ac1f04 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -151,6 +151,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S FailureTTL: int64(opts.FailureTTL), TimeTilDormant: int64(opts.TimeTilDormant), TimeTilDormantAutoDelete: int64(opts.TimeTilDormantAutoDelete), + RequirePromotedVersion: opts.RequirePromotedVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) From 3f6dabf2761c3740c836aa3e8249af60c14391f9 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 11 Oct 2023 23:37:54 +0000 Subject: [PATCH 03/30] update autobuild --- coderd/autobuild/lifecycle_executor.go | 6 ++++- coderd/wsbuilder/wsbuilder.go | 33 ++++++++------------------ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 07c586b2331bb..16e783384dbaa 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -179,7 +179,7 @@ func (e *Executor) runOnce(t time.Time) Stats { Reason(reason) log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition)) if nextTransition == database.WorkspaceTransitionStart && - ws.AutomaticUpdates == database.AutomaticUpdatesAlways { + useActiveVersion(templateSchedule, ws) { log.Debug(e.ctx, "autostarting with active version") builder = builder.ActiveVersion() } @@ -469,3 +469,7 @@ func auditBuild(ctx context.Context, log slog.Logger, auditor audit.Auditor, par AdditionalFields: raw, }) } + +func useActiveVersion(opts schedule.TemplateScheduleOptions, ws database.Workspace) bool { + return opts.RequirePromotedVersion || ws.AutomaticUpdates == database.AutomaticUpdatesAlways +} diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 008bc88ab72ab..720a1ba2b35b4 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -11,7 +11,6 @@ import ( "time" "github.com/google/uuid" - "github.com/lib/pq" "github.com/sqlc-dev/pqtype" "golang.org/x/xerrors" @@ -210,29 +209,17 @@ func (b *Builder) Build( // RepeatableRead isolation ensures that we get a consistent view of the database while // computing the new build. This simplifies the logic so that we do not need to worry if // later reads are consistent with earlier ones. - var err error - for retries := 0; retries < 5; retries++ { - var workspaceBuild *database.WorkspaceBuild - var provisionerJob *database.ProvisionerJob - err := store.InTx(func(store database.Store) error { - b.store = store - workspaceBuild, provisionerJob, err = b.buildTx(authFunc) - return err - }, &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) - var pqe *pq.Error - if xerrors.As(err, &pqe) { - if pqe.Code == "40001" { - // serialization error, retry - continue - } - } - if err != nil { - // Other (hard) error - return nil, nil, err - } - return workspaceBuild, provisionerJob, nil + var workspaceBuild *database.WorkspaceBuild + var provisionerJob *database.ProvisionerJob + err := database.ReadModifyUpdate(store, func(tx database.Store) error { + var err error + workspaceBuild, provisionerJob, err = b.buildTx(authFunc) + return err + }) + if err != nil { + return nil, nil, xerrors.Errorf("too many errors; last error: %w", err) } - return nil, nil, xerrors.Errorf("too many errors; last error: %w", err) + return workspaceBuild, provisionerJob, nil } // buildTx contains the business logic of computing a new build. Attributes of the new database objects are computed From d9bbfeae9aacd4a301acfb3aec0088debda941fa Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 13 Oct 2023 23:13:58 +0000 Subject: [PATCH 04/30] add tests for autobuild --- coderd/database/dbauthz/dbauthz.go | 25 ++++++- coderd/wsbuilder/wsbuilder.go | 1 + enterprise/audit/table.go | 1 + enterprise/coderd/workspaces_test.go | 98 ++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 038f4e0c92807..65754bc9cd039 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2200,7 +2200,7 @@ func (q *querier) InsertWorkspaceAppStats(ctx context.Context, arg database.Inse func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertWorkspaceBuildParams) error { w, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) if err != nil { - return err + return xerrors.Errorf("get workspace by id: %w", err) } var action rbac.Action = rbac.ActionUpdate @@ -2209,7 +2209,28 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW } if err = q.authorizeContext(ctx, action, w.WorkspaceBuildRBAC(arg.Transition)); err != nil { - return err + return xerrors.Errorf("authorize context: %w", err) + } + + // If we're starting a workspace we need to check the template. + if arg.Transition == database.WorkspaceTransitionStart { + // Fetch the template. We have to ensure that the request + // abides by the update policy of the templates. + t, err := q.db.GetTemplateByID(ctx, w.TemplateID) + if err != nil { + return xerrors.Errorf("get template by id: %w", err) + } + + // If the template requires the promoted version we need to check if + // the user is a template admin. If they aren't and are attempting + // to use a non-promoted version then we must fail the request. + if t.RequirePromotedVersion { + if arg.TemplateVersionID != t.ActiveVersionID { + if err = q.authorizeContext(ctx, rbac.ActionUpdate, t); err != nil { + return xerrors.Errorf("cannot use non-active version: %w", err) + } + } + } } return q.db.InsertWorkspaceBuild(ctx, arg) diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 720a1ba2b35b4..f4f894c07f7d2 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -213,6 +213,7 @@ func (b *Builder) Build( var provisionerJob *database.ProvisionerJob err := database.ReadModifyUpdate(store, func(tx database.Store) error { var err error + b.store = tx workspaceBuild, provisionerJob, err = b.buildTx(authFunc) return err }) diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 49f10d117057c..cd8bcba587bb4 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -85,6 +85,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "failure_ttl": ActionTrack, "time_til_dormant": ActionTrack, "time_til_dormant_autodelete": ActionTrack, + "require_promoted_version": ActionTrack, }, &database.TemplateVersion{}: { "id": ActionTrack, diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index e38d1874e764d..7112a034ccbe8 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -685,6 +685,8 @@ func TestWorkspaceAutobuild(t *testing.T) { ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) + coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) + // Create a new version that will fail when we try to delete a workspace. version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, @@ -734,6 +736,102 @@ func TestWorkspaceAutobuild(t *testing.T) { require.Len(t, stats.Transitions, 1) require.Equal(t, database.WorkspaceTransitionDelete, stats.Transitions[ws.ID]) }) + + t.Run("RequirePromotedVersion", func(t *testing.T) { + t.Parallel() + + var ( + tickCh = make(chan time.Time) + statsCh = make(chan autobuild.Stats) + ctx = testutil.Context(t, testutil.WaitMedium) + ) + + client, user := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + AutobuildTicker: tickCh, + IncludeProvisionerDaemon: true, + AutobuildStats: statsCh, + TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1}, + }, + }) + + sched, err := cron.Weekly("CRON_TZ=UTC 0 * * * *") + require.NoError(t, err) + + // Create a template version1 that passes to get a functioning workspace. + version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.PlanComplete, + ProvisionApply: echo.ApplyComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID) + + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID) + require.Equal(t, version1.ID, template.ActiveVersionID) + + ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.AutostartSchedule = ptr.Ref(sched.String()) + }) + + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) + ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) + + // Create a new version so that we can assert we don't update + // to the latest by default. + version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.PlanComplete, + ProvisionApply: echo.ApplyComplete, + }, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID) + + // Make sure to promote it. + err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: version2.ID, + }) + require.NoError(t, err) + + // Kick of an autostart build. + tickCh <- sched.Next(ws.LatestBuild.CreatedAt) + stats := <-statsCh + require.NoError(t, stats.Error) + require.Len(t, stats.Transitions, 1) + require.Contains(t, stats.Transitions, ws.ID) + require.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[ws.ID]) + + // Validate that we didn't update to the promoted version. + started := coderdtest.MustWorkspace(t, client, ws.ID) + firstBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, started.LatestBuild.ID) + require.Equal(t, version1.ID, ws.LatestBuild.TemplateVersionID) + + // Update the template to require the promoted version. + _, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ + RequirePromotedVersion: true, + AllowUserAutostart: true, + }) + require.NoError(t, err) + + // Reset the workspace to the stopped state so we can try + // to autostart again. + coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) + + // Force an autostart transition again. + tickCh <- sched.Next(firstBuild.CreatedAt) + stats = <-statsCh + require.NoError(t, stats.Error) + require.Len(t, stats.Transitions, 1) + require.Contains(t, stats.Transitions, ws.ID) + require.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[ws.ID]) + + // Validate that we are using the promoted version. + ws = coderdtest.MustWorkspace(t, client, ws.ID) + require.Equal(t, version2.ID, ws.LatestBuild.TemplateVersionID) + }) } // Blocked by autostart requirements From 4b3194d7899eb638ca3e83c8f12cf97131029b7c Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sat, 14 Oct 2023 00:17:46 +0000 Subject: [PATCH 05/30] add more tests --- coderd/coderdtest/coderdtest.go | 15 ++- coderd/database/dbfake/dbfake.go | 1 + coderd/templates.go | 8 +- coderd/wsbuilder/wsbuilder.go | 6 +- codersdk/templates.go | 2 + enterprise/coderd/schedule/template.go | 1 + enterprise/coderd/templates_test.go | 36 ++++++ enterprise/coderd/workspacebuilds_test.go | 140 ++++++++++++++++++++++ enterprise/coderd/workspaces_test.go | 18 +-- 9 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 enterprise/coderd/workspacebuilds_test.go diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 4eea44a226bfa..40c346fabd86f 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -915,7 +915,7 @@ func CreateWorkspace(t testing.TB, client *codersdk.Client, organization uuid.UU } // TransitionWorkspace is a convenience method for transitioning a workspace from one state to another. -func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID uuid.UUID, from, to database.WorkspaceTransition) codersdk.Workspace { +func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID uuid.UUID, from, to database.WorkspaceTransition, muts ...func(req *codersdk.CreateWorkspaceBuildRequest)) codersdk.Workspace { t.Helper() ctx := context.Background() workspace, err := client.Workspace(ctx, workspaceID) @@ -925,10 +925,19 @@ func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID template, err := client.Template(ctx, workspace.TemplateID) require.NoError(t, err, "fetch workspace template") - build, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{ + req := codersdk.CreateWorkspaceBuildRequest{ + // TODO (JonA): I get this is for convenience but we shoul probably + // change this. Tripped me up why my test was passing when it shouldn't + // have. TemplateVersionID: template.ActiveVersionID, Transition: codersdk.WorkspaceTransition(to), - }) + } + + for _, mut := range muts { + mut(&req) + } + + build, err := client.CreateWorkspaceBuild(ctx, workspace.ID, req) require.NoError(t, err, "unexpected error transitioning workspace to %s", to) _ = AwaitWorkspaceBuildJobCompleted(t, client, build.ID) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index bffd855da6b6b..dc4b07be9e62c 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -5729,6 +5729,7 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database tpl.FailureTTL = arg.FailureTTL tpl.TimeTilDormant = arg.TimeTilDormant tpl.TimeTilDormantAutoDelete = arg.TimeTilDormantAutoDelete + tpl.RequirePromotedVersion = arg.RequirePromotedVersion q.templates[idx] = tpl return nil } diff --git a/coderd/templates.go b/coderd/templates.go index 38bc7b1f2c1fa..f7ed47981db3e 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -613,7 +613,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { req.AutostopRequirement.Weeks == scheduleOpts.AutostopRequirement.Weeks && req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() && req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() && - req.TimeTilDormantAutoDeleteMillis == time.Duration(template.TimeTilDormantAutoDelete).Milliseconds() { + req.TimeTilDormantAutoDeleteMillis == time.Duration(template.TimeTilDormantAutoDelete).Milliseconds() && + req.RequirePromotedVersion == template.RequirePromotedVersion { return nil } @@ -657,7 +658,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { inactivityTTL != time.Duration(template.TimeTilDormant) || timeTilDormantAutoDelete != time.Duration(template.TimeTilDormantAutoDelete) || req.AllowUserAutostart != template.AllowUserAutostart || - req.AllowUserAutostop != template.AllowUserAutostop { + req.AllowUserAutostop != template.AllowUserAutostop || + req.RequirePromotedVersion != template.RequirePromotedVersion { updated, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, updated, schedule.TemplateScheduleOptions{ // Some of these values are enterprise-only, but the // TemplateScheduleStore will handle avoiding setting them if @@ -678,6 +680,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { TimeTilDormantAutoDelete: timeTilDormantAutoDelete, UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt, UpdateWorkspaceDormantAt: req.UpdateWorkspaceDormantAt, + RequirePromotedVersion: req.RequirePromotedVersion, }) if err != nil { return xerrors.Errorf("set template schedule options: %w", err) @@ -823,5 +826,6 @@ func (api *API) convertTemplate( AutostartRequirement: codersdk.TemplateAutostartRequirement{ DaysOfWeek: codersdk.BitmapToWeekdays(template.AutostartAllowedDays()), }, + RequirePromotedVersion: template.RequirePromotedVersion, } } diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index f4f894c07f7d2..2987615b2a3ac 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -343,7 +343,11 @@ func (b *Builder) buildTx(authFunc func(action rbac.Action, object rbac.Objecter MaxDeadline: time.Time{}, // set by provisioner upon completion }) if err != nil { - return BuildError{http.StatusInternalServerError, "insert workspace build", err} + code := http.StatusInternalServerError + if rbac.IsUnauthorizedError(err) { + code = http.StatusUnauthorized + } + return BuildError{code, "insert workspace build", err} } names, values, err := b.getParameters() diff --git a/codersdk/templates.go b/codersdk/templates.go index 0ce0993cc2af4..7ebee9241f0fe 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -52,6 +52,8 @@ type Template struct { FailureTTLMillis int64 `json:"failure_ttl_ms"` TimeTilDormantMillis int64 `json:"time_til_dormant_ms"` TimeTilDormantAutoDeleteMillis int64 `json:"time_til_dormant_autodelete_ms"` + + RequirePromotedVersion bool `json:"require_promoted_version"` } // WeekdaysToBitmap converts a list of weekdays to a bitmap in accordance with diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index dd28787ac1f04..dfb44c7dd31d7 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -92,6 +92,7 @@ func (s *EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.S FailureTTL: time.Duration(tpl.FailureTTL), TimeTilDormant: time.Duration(tpl.TimeTilDormant), TimeTilDormantAutoDelete: time.Duration(tpl.TimeTilDormantAutoDelete), + RequirePromotedVersion: tpl.RequirePromotedVersion, }, nil } diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 50bd6ecf0798b..3776870ee9af9 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -18,6 +18,7 @@ import ( "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/enterprise/coderd/schedule" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/testutil" ) @@ -571,6 +572,41 @@ func TestTemplates(t *testing.T) { require.Equal(t, updatedDormantWS.DormantAt, dormantWorkspace.DormantAt) require.True(t, updatedDormantWS.LastUsedAt.After(dormantWorkspace.LastUsedAt)) }) + + t.Run("RequirePromotedVersion", func(t *testing.T) { + t.Parallel() + client, user := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAdvancedTemplateScheduling: 1, + }, + }, + }) + + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + require.False(t, template.RequirePromotedVersion) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + updatedTemplate, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ + RequirePromotedVersion: true, + }) + require.NoError(t, err) + require.True(t, updatedTemplate.RequirePromotedVersion) + + // Assert that fetching a template is no different from the response + // when updating. + template, err = client.Template(ctx, template.ID) + require.NoError(t, err) + require.Equal(t, updatedTemplate, template) + }) } func TestTemplateACL(t *testing.T) { diff --git a/enterprise/coderd/workspacebuilds_test.go b/enterprise/coderd/workspacebuilds_test.go new file mode 100644 index 0000000000000..dade7c7f539ba --- /dev/null +++ b/enterprise/coderd/workspacebuilds_test.go @@ -0,0 +1,140 @@ +package coderd_test + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" + "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/enterprise/coderd/schedule" + "github.com/coder/coder/v2/testutil" +) + +func TestWorkspaceBuild(t *testing.T) { + t.Parallel() + t.Run("TemplateAdminNoActiveVersion", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitMedium) + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAdvancedTemplateScheduling: 1, + codersdk.FeatureTemplateRBAC: 1, + }, + }, + }) + + // Create an initial version. + oldVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + // Create a template that mandates the promoted version. + // This should be enforced for everyone except template admins. + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, oldVersion.ID) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, oldVersion.ID) + require.Equal(t, oldVersion.ID, template.ActiveVersionID) + template, err := ownerClient.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ + RequirePromotedVersion: true, + }) + require.NoError(t, err) + + // Create a new version that we will promote. + activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) + err = ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: activeVersion.ID, + }) + require.NoError(t, err) + + templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + templateACLAdminClient, templateACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + templateGroupACLAdminClient, templateGroupACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + // Create a group so we can also test group template admin ownership. + group, err := ownerClient.CreateGroup(ctx, owner.OrganizationID, codersdk.CreateGroupRequest{ + Name: "test", + }) + require.NoError(t, err) + + // Add the user who gains template admin via group membership. + group, err = ownerClient.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{ + AddUsers: []string{templateGroupACLAdmin.ID.String()}, + }) + require.NoError(t, err) + + // Update the template for both users and groups. + err = ownerClient.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{ + UserPerms: map[string]codersdk.TemplateRole{ + templateACLAdmin.ID.String(): codersdk.TemplateRoleAdmin, + }, + GroupPerms: map[string]codersdk.TemplateRole{ + group.ID.String(): codersdk.TemplateRoleAdmin, + }, + }) + require.NoError(t, err) + + type testcase struct { + Name string + Client *codersdk.Client + ExpectedStatusCode int + } + + cases := []testcase{ + { + Name: "OwnerOK", + Client: ownerClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateAdminOK", + Client: templateAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateACLAdminOK", + Client: templateACLAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateGroupACLAdminOK", + Client: templateGroupACLAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "MemberFails", + Client: memberClient, + ExpectedStatusCode: http.StatusUnauthorized, + }, + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + _, err = c.Client.CreateWorkspace(context.Background(), owner.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateVersionID: oldVersion.ID, + Name: "abc123", + AutomaticUpdates: codersdk.AutomaticUpdatesNever, + }) + if c.ExpectedStatusCode == http.StatusOK { + require.NoError(t, err) + } else { + require.Error(t, err) + cerr, ok := codersdk.AsError(err) + require.True(t, ok) + require.Equal(t, c.ExpectedStatusCode, cerr.StatusCode()) + } + }) + } + }) +} diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 7112a034ccbe8..9cdb069de2e79 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -762,11 +762,7 @@ func TestWorkspaceAutobuild(t *testing.T) { require.NoError(t, err) // Create a template version1 that passes to get a functioning workspace. - version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: echo.PlanComplete, - ProvisionApply: echo.ApplyComplete, - }) + version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID) @@ -781,11 +777,7 @@ func TestWorkspaceAutobuild(t *testing.T) { // Create a new version so that we can assert we don't update // to the latest by default. - version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: echo.PlanComplete, - ProvisionApply: echo.ApplyComplete, - }, func(ctvr *codersdk.CreateTemplateVersionRequest) { + version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { ctvr.TemplateID = template.ID }) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID) @@ -807,7 +799,7 @@ func TestWorkspaceAutobuild(t *testing.T) { // Validate that we didn't update to the promoted version. started := coderdtest.MustWorkspace(t, client, ws.ID) firstBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, started.LatestBuild.ID) - require.Equal(t, version1.ID, ws.LatestBuild.TemplateVersionID) + require.Equal(t, version1.ID, firstBuild.TemplateVersionID) // Update the template to require the promoted version. _, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ @@ -818,7 +810,9 @@ func TestWorkspaceAutobuild(t *testing.T) { // Reset the workspace to the stopped state so we can try // to autostart again. - coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) + coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { + req.TemplateVersionID = ws.LatestBuild.TemplateVersionID + }) // Force an autostart transition again. tickCh <- sched.Next(firstBuild.CreatedAt) From 47da214c72aa3c98dffcea92c9b11fbce97ccb25 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:06:06 +0000 Subject: [PATCH 06/30] require_promoted_version -> require_active_version --- coderd/autobuild/lifecycle_executor.go | 2 +- coderd/database/dbauthz/dbauthz.go | 2 +- coderd/database/dbfake/dbfake.go | 2 +- coderd/database/dump.sql | 8 ++++ .../000165_template_promotion.down.sql | 2 +- .../000165_template_promotion.up.sql | 2 +- coderd/database/models.go | 8 ++++ coderd/database/queries.sql.go | 40 ++++++++++++++++++- coderd/database/queries/templates.sql | 2 +- coderd/schedule/template.go | 8 ++-- coderd/templates.go | 8 ++-- codersdk/templates.go | 8 ++-- enterprise/audit/table.go | 2 +- enterprise/coderd/schedule/template.go | 4 +- enterprise/coderd/templates_test.go | 6 +-- enterprise/coderd/workspacebuilds_test.go | 4 +- enterprise/coderd/workspaces_test.go | 4 +- 17 files changed, 84 insertions(+), 28 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 16e783384dbaa..bc12f5ba31a27 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -471,5 +471,5 @@ func auditBuild(ctx context.Context, log slog.Logger, auditor audit.Auditor, par } func useActiveVersion(opts schedule.TemplateScheduleOptions, ws database.Workspace) bool { - return opts.RequirePromotedVersion || ws.AutomaticUpdates == database.AutomaticUpdatesAlways + return opts.RequireActiveVersion || ws.AutomaticUpdates == database.AutomaticUpdatesAlways } diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 65754bc9cd039..e1118bfbd5c9a 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2224,7 +2224,7 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW // If the template requires the promoted version we need to check if // the user is a template admin. If they aren't and are attempting // to use a non-promoted version then we must fail the request. - if t.RequirePromotedVersion { + if t.RequireActiveVersion { if arg.TemplateVersionID != t.ActiveVersionID { if err = q.authorizeContext(ctx, rbac.ActionUpdate, t); err != nil { return xerrors.Errorf("cannot use non-active version: %w", err) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index dc4b07be9e62c..02aabdde179d9 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -5729,7 +5729,7 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database tpl.FailureTTL = arg.FailureTTL tpl.TimeTilDormant = arg.TimeTilDormant tpl.TimeTilDormantAutoDelete = arg.TimeTilDormantAutoDelete - tpl.RequirePromotedVersion = arg.RequirePromotedVersion + tpl.RequireActiveVersion = arg.RequireActiveVersion q.templates[idx] = tpl return nil } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 392e1172b13af..ac2bf181c9097 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -755,11 +755,15 @@ CREATE TABLE templates ( time_til_dormant_autodelete bigint DEFAULT 0 NOT NULL, autostop_requirement_days_of_week smallint DEFAULT 0 NOT NULL, autostop_requirement_weeks bigint DEFAULT 0 NOT NULL, +<<<<<<< HEAD <<<<<<< HEAD autostart_block_days_of_week smallint DEFAULT 0 NOT NULL ======= require_promoted_version boolean DEFAULT false NOT NULL >>>>>>> 5786e8135 (add migration) +======= + require_active_version boolean DEFAULT false NOT NULL +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -803,11 +807,15 @@ CREATE VIEW template_with_users AS templates.time_til_dormant_autodelete, templates.autostop_requirement_days_of_week, templates.autostop_requirement_weeks, +<<<<<<< HEAD <<<<<<< HEAD templates.autostart_block_days_of_week, ======= templates.require_promoted_version, >>>>>>> 5786e8135 (add migration) +======= + templates.require_active_version, +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username FROM (public.templates diff --git a/coderd/database/migrations/000165_template_promotion.down.sql b/coderd/database/migrations/000165_template_promotion.down.sql index 3236609a1c630..2364ea5f52200 100644 --- a/coderd/database/migrations/000165_template_promotion.down.sql +++ b/coderd/database/migrations/000165_template_promotion.down.sql @@ -1,6 +1,6 @@ BEGIN; -ALTER TABLE templates DROP COLUMN require_promoted_version; +ALTER TABLE templates DROP COLUMN require_active_version; -- Update the template_with_users view; DROP VIEW template_with_users; diff --git a/coderd/database/migrations/000165_template_promotion.up.sql b/coderd/database/migrations/000165_template_promotion.up.sql index 8d9b89ce60ab6..a9255505eede7 100644 --- a/coderd/database/migrations/000165_template_promotion.up.sql +++ b/coderd/database/migrations/000165_template_promotion.up.sql @@ -2,7 +2,7 @@ BEGIN; DROP VIEW template_with_users; -ALTER TABLE templates ADD COLUMN require_promoted_version boolean NOT NULL DEFAULT 'f'; +ALTER TABLE templates ADD COLUMN require_active_version boolean NOT NULL DEFAULT 'f'; CREATE VIEW template_with_users diff --git a/coderd/database/models.go b/coderd/database/models.go index 8affdc5484d65..64ad17b1b3dcd 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1891,11 +1891,15 @@ type Template struct { TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` +<<<<<<< HEAD <<<<<<< HEAD AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` ======= RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` >>>>>>> 5786e8135 (add migration) +======= + RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) CreatedByAvatarURL sql.NullString `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` } @@ -1932,12 +1936,16 @@ type TemplateTable struct { AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` // The number of weeks between restarts. 0 or 1 weeks means "every week", 2 week means "every second week", etc. Weeks are counted from January 2, 2023, which is the first Monday of 2023. This is to ensure workspaces are started consistently for all customers on the same n-week cycles. AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` +<<<<<<< HEAD <<<<<<< HEAD // A bitmap of days of week that autostart of a workspace is not allowed. Default allows all days. This is intended as a cost savings measure to prevent auto start on weekends (for example). AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` ======= RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` >>>>>>> 5786e8135 (add migration) +======= + RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) } // Joins in the username + avatar url of the created by user. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7bc577f213fe8..75012a9636502 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4726,11 +4726,15 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT +<<<<<<< HEAD <<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username ======= 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username >>>>>>> 5786e8135 (add migration) +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) FROM template_with_users WHERE @@ -4767,11 +4771,15 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD <<<<<<< HEAD &i.AutostartBlockDaysOfWeek, ======= &i.RequirePromotedVersion, >>>>>>> 5786e8135 (add migration) +======= + &i.RequireActiveVersion, +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4780,11 +4788,15 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT +<<<<<<< HEAD <<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username ======= 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username >>>>>>> 5786e8135 (add migration) +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) FROM template_with_users AS templates WHERE @@ -4829,11 +4841,15 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD <<<<<<< HEAD &i.AutostartBlockDaysOfWeek, ======= &i.RequirePromotedVersion, >>>>>>> 5786e8135 (add migration) +======= + &i.RequireActiveVersion, +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4842,10 +4858,14 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G const getTemplates = `-- name: GetTemplates :many <<<<<<< HEAD +<<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username FROM template_with_users AS templates >>>>>>> 5786e8135 (add migration) +======= +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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username FROM template_with_users AS templates +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) ORDER BY (name, id) ASC ` @@ -4883,11 +4903,15 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD <<<<<<< HEAD &i.AutostartBlockDaysOfWeek, ======= &i.RequirePromotedVersion, >>>>>>> 5786e8135 (add migration) +======= + &i.RequireActiveVersion, +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -4906,11 +4930,15 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT +<<<<<<< HEAD <<<<<<< HEAD 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, max_ttl, 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, created_by_avatar_url, created_by_username ======= 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username >>>>>>> 5786e8135 (add migration) +======= + 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) FROM template_with_users AS templates WHERE @@ -4985,11 +5013,15 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, +<<<<<<< HEAD <<<<<<< HEAD &i.AutostartBlockDaysOfWeek, ======= &i.RequirePromotedVersion, >>>>>>> 5786e8135 (add migration) +======= + &i.RequireActiveVersion, +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -5185,8 +5217,12 @@ SET failure_ttl = $9, time_til_dormant = $10, time_til_dormant_autodelete = $11, +<<<<<<< HEAD require_promoted_version = $12 >>>>>>> f6a08b15a (add template update plumbing) +======= + require_active_version = $12 +>>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) WHERE id = $1 ` @@ -5204,7 +5240,7 @@ type UpdateTemplateScheduleByIDParams struct { FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"` TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"` TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` - RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` + RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` } func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error { @@ -5221,7 +5257,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT arg.FailureTTL, arg.TimeTilDormant, arg.TimeTilDormantAutoDelete, - arg.RequirePromotedVersion, + arg.RequireActiveVersion, ) return err } diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index 572a450e67b38..4c04768b55671 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -124,7 +124,7 @@ SET failure_ttl = $10, time_til_dormant = $11, time_til_dormant_autodelete = $12, - require_promoted_version = $13 + require_active_version = $13 WHERE id = $1 ; diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 92545cfa7d0c9..3fe9991aaf77e 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -151,7 +151,9 @@ type TemplateScheduleOptions struct { // workspaces whose dormant_at field violates the new template time_til_dormant_autodelete // threshold. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` - RequirePromotedVersion bool `json:"require_promoted_version"` + // RequireActiveVersion requires that a starting a workspace uses the active + // version for a template. + RequireActiveVersion bool `json:"require_promoted_version"` } // TemplateScheduleStore provides an interface for retrieving template @@ -201,7 +203,7 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te FailureTTL: 0, TimeTilDormant: 0, TimeTilDormantAutoDelete: 0, - RequirePromotedVersion: false, + RequireActiveVersion: false, }, nil } @@ -234,7 +236,7 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp FailureTTL: tpl.FailureTTL, TimeTilDormant: tpl.TimeTilDormant, TimeTilDormantAutoDelete: tpl.TimeTilDormantAutoDelete, - RequirePromotedVersion: tpl.RequirePromotedVersion, + RequireActiveVersion: tpl.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) diff --git a/coderd/templates.go b/coderd/templates.go index f7ed47981db3e..f6d71359486d0 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -614,7 +614,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() && req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() && req.TimeTilDormantAutoDeleteMillis == time.Duration(template.TimeTilDormantAutoDelete).Milliseconds() && - req.RequirePromotedVersion == template.RequirePromotedVersion { + req.RequireActiveVersion == template.RequireActiveVersion { return nil } @@ -659,7 +659,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { timeTilDormantAutoDelete != time.Duration(template.TimeTilDormantAutoDelete) || req.AllowUserAutostart != template.AllowUserAutostart || req.AllowUserAutostop != template.AllowUserAutostop || - req.RequirePromotedVersion != template.RequirePromotedVersion { + req.RequireActiveVersion != template.RequireActiveVersion { updated, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, updated, schedule.TemplateScheduleOptions{ // Some of these values are enterprise-only, but the // TemplateScheduleStore will handle avoiding setting them if @@ -680,7 +680,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { TimeTilDormantAutoDelete: timeTilDormantAutoDelete, UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt, UpdateWorkspaceDormantAt: req.UpdateWorkspaceDormantAt, - RequirePromotedVersion: req.RequirePromotedVersion, + RequireActiveVersion: req.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("set template schedule options: %w", err) @@ -826,6 +826,6 @@ func (api *API) convertTemplate( AutostartRequirement: codersdk.TemplateAutostartRequirement{ DaysOfWeek: codersdk.BitmapToWeekdays(template.AutostartAllowedDays()), }, - RequirePromotedVersion: template.RequirePromotedVersion, + RequireActiveVersion: template.RequireActiveVersion, } } diff --git a/codersdk/templates.go b/codersdk/templates.go index 7ebee9241f0fe..3caff708e5ebd 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -53,7 +53,9 @@ type Template struct { TimeTilDormantMillis int64 `json:"time_til_dormant_ms"` TimeTilDormantAutoDeleteMillis int64 `json:"time_til_dormant_autodelete_ms"` - RequirePromotedVersion bool `json:"require_promoted_version"` + // RequireActiveVersion mandates that workspaces are built with the active + // template version. + RequireActiveVersion bool `json:"require_promoted_version"` } // WeekdaysToBitmap converts a list of weekdays to a bitmap in accordance with @@ -223,10 +225,10 @@ type UpdateTemplateMeta struct { // from the template. This is useful for preventing dormant workspaces being immediately // deleted when updating the dormant_ttl field to a new, shorter value. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` - // RequirePromotedVersion mandates workspaces built using this template + // RequireActiveVersion mandates workspaces built using this template // use the latest promoted version of the template. This option has no // effect on template admins. - RequirePromotedVersion bool `json:"require_promoted_version"` + RequireActiveVersion bool `json:"require_promoted_version"` } type TemplateExample struct { diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index cd8bcba587bb4..f272354d649ac 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -85,7 +85,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "failure_ttl": ActionTrack, "time_til_dormant": ActionTrack, "time_til_dormant_autodelete": ActionTrack, - "require_promoted_version": ActionTrack, + "require_active_version": ActionTrack, }, &database.TemplateVersion{}: { "id": ActionTrack, diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index dfb44c7dd31d7..808f3635dd4f5 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -92,7 +92,7 @@ func (s *EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.S FailureTTL: time.Duration(tpl.FailureTTL), TimeTilDormant: time.Duration(tpl.TimeTilDormant), TimeTilDormantAutoDelete: time.Duration(tpl.TimeTilDormantAutoDelete), - RequirePromotedVersion: tpl.RequirePromotedVersion, + RequireActiveVersion: tpl.RequireActiveVersion, }, nil } @@ -152,7 +152,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S FailureTTL: int64(opts.FailureTTL), TimeTilDormant: int64(opts.TimeTilDormant), TimeTilDormantAutoDelete: int64(opts.TimeTilDormantAutoDelete), - RequirePromotedVersion: opts.RequirePromotedVersion, + RequireActiveVersion: opts.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 3776870ee9af9..a5a1d9449da6d 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -590,16 +590,16 @@ func TestTemplates(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - require.False(t, template.RequirePromotedVersion) + require.False(t, template.RequireActiveVersion) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() updatedTemplate, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ - RequirePromotedVersion: true, + RequireActiveVersion: true, }) require.NoError(t, err) - require.True(t, updatedTemplate.RequirePromotedVersion) + require.True(t, updatedTemplate.RequireActiveVersion) // Assert that fetching a template is no different from the response // when updating. diff --git a/enterprise/coderd/workspacebuilds_test.go b/enterprise/coderd/workspacebuilds_test.go index dade7c7f539ba..5039a1cf8d9ad 100644 --- a/enterprise/coderd/workspacebuilds_test.go +++ b/enterprise/coderd/workspacebuilds_test.go @@ -18,7 +18,7 @@ import ( func TestWorkspaceBuild(t *testing.T) { t.Parallel() - t.Run("TemplateAdminNoActiveVersion", func(t *testing.T) { + t.Run("TemplateRequiresActiveVersion", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitMedium) @@ -43,7 +43,7 @@ func TestWorkspaceBuild(t *testing.T) { coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, oldVersion.ID) require.Equal(t, oldVersion.ID, template.ActiveVersionID) template, err := ownerClient.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ - RequirePromotedVersion: true, + RequireActiveVersion: true, }) require.NoError(t, err) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 9cdb069de2e79..e6cbabec400f9 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -803,8 +803,8 @@ func TestWorkspaceAutobuild(t *testing.T) { // Update the template to require the promoted version. _, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ - RequirePromotedVersion: true, - AllowUserAutostart: true, + RequireActiveVersion: true, + AllowUserAutostart: true, }) require.NoError(t, err) From 92594dcff0de37898f985d73fdff765562ee33b1 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:08:25 +0000 Subject: [PATCH 07/30] make gen --- coderd/apidoc/docs.go | 4 ++++ coderd/apidoc/swagger.json | 4 ++++ docs/admin/audit-logs.md | 16 ++++++++++++++++ docs/api/schemas.md | 30 ++++++++++++++++++++++++++++++ docs/api/templates.md | 30 ++++++++++++++++++++++++++++++ site/src/api/typesGenerated.ts | 2 ++ site/src/testHelpers/entities.ts | 1 + 7 files changed, 87 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 3b6ab51263b3d..03a7fbdea36b7 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9966,6 +9966,10 @@ const docTemplate = `{ "terraform" ] }, + "require_promoted_version": { + "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", + "type": "boolean" + }, "time_til_dormant_autodelete_ms": { "type": "integer" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 849387dbfee63..310e29f8baf3e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9004,6 +9004,10 @@ "type": "string", "enum": ["terraform"] }, + "require_promoted_version": { + "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", + "type": "boolean" + }, "time_til_dormant_autodelete_ms": { "type": "integer" }, diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index c7da38bec58d1..6ebf68aca146e 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -8,6 +8,7 @@ We track the following resources: +<<<<<<< HEAD | Resource | | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| @@ -21,6 +22,21 @@ We track the following resources: | Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| | WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
wildcard_hostnametrue
| +======= +| Resource | | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| +| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| +| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| +| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| +| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| +| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
wildcard_hostnametrue
| +>>>>>>> c2a5a874d (make gen) diff --git a/docs/api/schemas.md b/docs/api/schemas.md index cd09260f59546..69a9bae41ed82 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4370,6 +4370,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -4378,6 +4379,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties +<<<<<<< HEAD | Name | Type | Required | Restrictions | Description | | ---------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `active_user_count` | integer | false | | Active user count is set to -1 when loading. | @@ -4404,6 +4406,34 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `time_til_dormant_autodelete_ms` | integer | false | | | | `time_til_dormant_ms` | integer | false | | | | `updated_at` | string | false | | | +======= +| Name | Type | Required | Restrictions | Description | +| ---------------------------------- | ---------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `active_user_count` | integer | false | | Active user count is set to -1 when loading. | +| `active_version_id` | string | 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 | | | +| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement is an enterprise feature. Its value is only used if your license is entitled to use the advanced template scheduling feature. | +| `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | +| `created_at` | string | false | | | +| `created_by_id` | string | false | | | +| `created_by_name` | string | false | | | +| `default_ttl_ms` | integer | false | | | +| `description` | string | false | | | +| `display_name` | string | false | | | +| `failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | +| `icon` | string | false | | | +| `id` | string | false | | | +| `max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured | +| `name` | string | false | | | +| `organization_id` | string | false | | | +| `provisioner` | string | false | | | +| `require_promoted_version` | boolean | false | | Require promoted version mandates that workspaces are built with the active template version. | +| `time_til_dormant_autodelete_ms` | integer | false | | | +| `time_til_dormant_ms` | integer | false | | | +| `updated_at` | string | false | | | +>>>>>>> c2a5a874d (make gen) #### Enumerated Values diff --git a/docs/api/templates.md b/docs/api/templates.md index a6cc2d4b5a367..469cdcc17886c 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -61,6 +61,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -91,6 +92,7 @@ Status Code **200** | `» 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. | +<<<<<<< HEAD | `»» 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. | | `» build_time_stats` | [codersdk.TemplateBuildTimeStats](schemas.md#codersdktemplatebuildtimestats) | false | | | | `»» [any property]` | [codersdk.TransitionStats](schemas.md#codersdktransitionstats) | false | | | @@ -112,6 +114,30 @@ Status Code **200** | `» time_til_dormant_autodelete_ms` | integer | false | | | | `» time_til_dormant_ms` | integer | false | | | | `» updated_at` | string(date-time) | false | | | +======= +| `»» 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. | +| `» build_time_stats` | [codersdk.TemplateBuildTimeStats](schemas.md#codersdktemplatebuildtimestats) | false | | | +| `»» [any property]` | [codersdk.TransitionStats](schemas.md#codersdktransitionstats) | false | | | +| `»»» p50` | integer | false | | | +| `»»» p95` | integer | false | | | +| `» created_at` | string(date-time) | false | | | +| `» created_by_id` | string(uuid) | false | | | +| `» created_by_name` | string | false | | | +| `» default_ttl_ms` | integer | false | | | +| `» description` | string | false | | | +| `» display_name` | string | false | | | +| `» failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | +| `» icon` | string | false | | | +| `» id` | string(uuid) | false | | | +| `» max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured | +| `» name` | string | false | | | +| `» organization_id` | string(uuid) | false | | | +| `» provisioner` | string | false | | | +| `» require_promoted_version` | boolean | false | | Require promoted version mandates that workspaces are built with the active template version. | +| `» time_til_dormant_autodelete_ms` | integer | false | | | +| `» time_til_dormant_ms` | integer | false | | | +| `» updated_at` | string(date-time) | false | | | +>>>>>>> c2a5a874d (make gen) #### Enumerated Values @@ -211,6 +237,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -346,6 +373,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -657,6 +685,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -775,6 +804,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", + "require_promoted_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 483e034775b79..f2df258dba73b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -911,6 +911,7 @@ export interface Template { readonly failure_ttl_ms: number; readonly time_til_dormant_ms: number; readonly time_til_dormant_autodelete_ms: number; + readonly require_promoted_version: boolean; } // From codersdk/templates.go @@ -1161,6 +1162,7 @@ export interface UpdateTemplateMeta { readonly time_til_dormant_autodelete_ms?: number; readonly update_workspace_last_used_at: boolean; readonly update_workspace_dormant_at: boolean; + readonly require_promoted_version: boolean; } // From codersdk/users.go diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 35882aa8dcba4..24ae3b91235c8 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -467,6 +467,7 @@ export const MockTemplate: TypesGen.Template = { time_til_dormant_autodelete_ms: 0, allow_user_autostart: true, allow_user_autostop: true, + require_promoted_version: false, }; export const MockTemplateVersionFiles: TemplateVersionFiles = { From 7abc92b0914b60bdb418eb58e85454742057436d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:12:21 +0000 Subject: [PATCH 08/30] make gen --- coderd/apidoc/docs.go | 2 +- coderd/apidoc/swagger.json | 2 +- coderd/database/dbauthz/dbauthz.go | 4 ++-- coderd/schedule/template.go | 2 +- codersdk/templates.go | 6 +++--- docs/api/schemas.md | 4 ++-- docs/api/templates.md | 12 ++++++------ enterprise/coderd/templates_test.go | 2 +- enterprise/coderd/workspaces_test.go | 2 +- site/src/api/typesGenerated.ts | 4 ++-- site/src/testHelpers/entities.ts | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 03a7fbdea36b7..64e0bbcf120d7 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9966,7 +9966,7 @@ const docTemplate = `{ "terraform" ] }, - "require_promoted_version": { + "require_active_version": { "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", "type": "boolean" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 310e29f8baf3e..7397426af3d8d 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9004,7 +9004,7 @@ "type": "string", "enum": ["terraform"] }, - "require_promoted_version": { + "require_active_version": { "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", "type": "boolean" }, diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index e1118bfbd5c9a..cdea7f0a744c0 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2221,9 +2221,9 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW return xerrors.Errorf("get template by id: %w", err) } - // If the template requires the promoted version we need to check if + // If the template requires the active version we need to check if // the user is a template admin. If they aren't and are attempting - // to use a non-promoted version then we must fail the request. + // to use a non-active version then we must fail the request. if t.RequireActiveVersion { if arg.TemplateVersionID != t.ActiveVersionID { if err = q.authorizeContext(ctx, rbac.ActionUpdate, t); err != nil { diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 3fe9991aaf77e..83bfc0dd68700 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -153,7 +153,7 @@ type TemplateScheduleOptions struct { UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` // RequireActiveVersion requires that a starting a workspace uses the active // version for a template. - RequireActiveVersion bool `json:"require_promoted_version"` + RequireActiveVersion bool `json:"require_active_version"` } // TemplateScheduleStore provides an interface for retrieving template diff --git a/codersdk/templates.go b/codersdk/templates.go index 3caff708e5ebd..3a3240ca711b2 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -55,7 +55,7 @@ type Template struct { // RequireActiveVersion mandates that workspaces are built with the active // template version. - RequireActiveVersion bool `json:"require_promoted_version"` + RequireActiveVersion bool `json:"require_active_version"` } // WeekdaysToBitmap converts a list of weekdays to a bitmap in accordance with @@ -226,9 +226,9 @@ type UpdateTemplateMeta struct { // deleted when updating the dormant_ttl field to a new, shorter value. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` // RequireActiveVersion mandates workspaces built using this template - // use the latest promoted version of the template. This option has no + // use the active version of the template. This option has no // effect on template admins. - RequireActiveVersion bool `json:"require_promoted_version"` + RequireActiveVersion bool `json:"require_active_version"` } type TemplateExample struct { diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 69a9bae41ed82..0bb8c9e5ddec9 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4370,7 +4370,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -4429,7 +4429,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `name` | string | false | | | | `organization_id` | string | false | | | | `provisioner` | string | false | | | -| `require_promoted_version` | boolean | false | | Require promoted version mandates that workspaces are built with the active template version. | +| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `time_til_dormant_autodelete_ms` | integer | false | | | | `time_til_dormant_ms` | integer | false | | | | `updated_at` | string | false | | | diff --git a/docs/api/templates.md b/docs/api/templates.md index 469cdcc17886c..3c09588c191c6 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -61,7 +61,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -133,7 +133,7 @@ Status Code **200** | `» name` | string | false | | | | `» organization_id` | string(uuid) | false | | | | `» provisioner` | string | false | | | -| `» require_promoted_version` | boolean | false | | Require promoted version mandates that workspaces are built with the active template version. | +| `» require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `» time_til_dormant_autodelete_ms` | integer | false | | | | `» time_til_dormant_ms` | integer | false | | | | `» updated_at` | string(date-time) | false | | | @@ -237,7 +237,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -373,7 +373,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -685,7 +685,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" @@ -804,7 +804,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "name": "string", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", "provisioner": "terraform", - "require_promoted_version": true, + "require_active_version": true, "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z" diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index a5a1d9449da6d..67acc2e686738 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -573,7 +573,7 @@ func TestTemplates(t *testing.T) { require.True(t, updatedDormantWS.LastUsedAt.After(dormantWorkspace.LastUsedAt)) }) - t.Run("RequirePromotedVersion", func(t *testing.T) { + t.Run("RequireActiveVersion", func(t *testing.T) { t.Parallel() client, user := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index e6cbabec400f9..4c7e765da5773 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -737,7 +737,7 @@ func TestWorkspaceAutobuild(t *testing.T) { require.Equal(t, database.WorkspaceTransitionDelete, stats.Transitions[ws.ID]) }) - t.Run("RequirePromotedVersion", func(t *testing.T) { + t.Run("RequireActiveVersion", func(t *testing.T) { t.Parallel() var ( diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f2df258dba73b..b534194568c2b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -911,7 +911,7 @@ export interface Template { readonly failure_ttl_ms: number; readonly time_til_dormant_ms: number; readonly time_til_dormant_autodelete_ms: number; - readonly require_promoted_version: boolean; + readonly require_active_version: boolean; } // From codersdk/templates.go @@ -1162,7 +1162,7 @@ export interface UpdateTemplateMeta { readonly time_til_dormant_autodelete_ms?: number; readonly update_workspace_last_used_at: boolean; readonly update_workspace_dormant_at: boolean; - readonly require_promoted_version: boolean; + readonly require_active_version: boolean; } // From codersdk/users.go diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 24ae3b91235c8..7cf0e8c8a6c28 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -467,7 +467,7 @@ export const MockTemplate: TypesGen.Template = { time_til_dormant_autodelete_ms: 0, allow_user_autostart: true, allow_user_autostop: true, - require_promoted_version: false, + require_active_version: false, }; export const MockTemplateVersionFiles: TemplateVersionFiles = { From 402b34260ad5f8bdb6edddd352a8778a5eb9cf3b Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:16:48 +0000 Subject: [PATCH 09/30] errant copy pasta --- enterprise/coderd/workspaces_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 4c7e765da5773..55a53512e5c76 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -685,8 +685,6 @@ func TestWorkspaceAutobuild(t *testing.T) { ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) - coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) - // Create a new version that will fail when we try to delete a workspace. version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, From 508a84d18103fd14f7151966be150220d8dcdd19 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:22:43 +0000 Subject: [PATCH 10/30] rebase --- coderd/database/dump.sql | 16 +--- ...> 000166_template_active_version.down.sql} | 0 ... => 000166_template_active_version.up.sql} | 0 coderd/database/models.go | 14 ---- coderd/database/queries.sql.go | 82 ++----------------- docs/admin/audit-logs.md | 42 +++------- docs/api/schemas.md | 30 +------ docs/api/templates.md | 26 +----- 8 files changed, 22 insertions(+), 188 deletions(-) rename coderd/database/migrations/{000165_template_promotion.down.sql => 000166_template_active_version.down.sql} (100%) rename coderd/database/migrations/{000165_template_promotion.up.sql => 000166_template_active_version.up.sql} (100%) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index ac2bf181c9097..0e79c875f93bc 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -755,15 +755,8 @@ CREATE TABLE templates ( time_til_dormant_autodelete bigint DEFAULT 0 NOT NULL, autostop_requirement_days_of_week smallint DEFAULT 0 NOT NULL, autostop_requirement_weeks bigint DEFAULT 0 NOT NULL, -<<<<<<< HEAD -<<<<<<< HEAD - autostart_block_days_of_week smallint DEFAULT 0 NOT NULL -======= - require_promoted_version boolean DEFAULT false NOT NULL ->>>>>>> 5786e8135 (add migration) -======= + autostart_block_days_of_week smallint DEFAULT 0 NOT NULL, require_active_version boolean DEFAULT false NOT NULL ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -807,15 +800,8 @@ CREATE VIEW template_with_users AS templates.time_til_dormant_autodelete, templates.autostop_requirement_days_of_week, templates.autostop_requirement_weeks, -<<<<<<< HEAD -<<<<<<< HEAD templates.autostart_block_days_of_week, -======= - templates.require_promoted_version, ->>>>>>> 5786e8135 (add migration) -======= templates.require_active_version, ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username FROM (public.templates diff --git a/coderd/database/migrations/000165_template_promotion.down.sql b/coderd/database/migrations/000166_template_active_version.down.sql similarity index 100% rename from coderd/database/migrations/000165_template_promotion.down.sql rename to coderd/database/migrations/000166_template_active_version.down.sql diff --git a/coderd/database/migrations/000165_template_promotion.up.sql b/coderd/database/migrations/000166_template_active_version.up.sql similarity index 100% rename from coderd/database/migrations/000165_template_promotion.up.sql rename to coderd/database/migrations/000166_template_active_version.up.sql diff --git a/coderd/database/models.go b/coderd/database/models.go index 64ad17b1b3dcd..c67afa74faa9a 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1891,15 +1891,8 @@ type Template struct { TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` -<<<<<<< HEAD -<<<<<<< HEAD AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` -======= - RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` ->>>>>>> 5786e8135 (add migration) -======= RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) CreatedByAvatarURL sql.NullString `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` } @@ -1936,16 +1929,9 @@ type TemplateTable struct { AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` // The number of weeks between restarts. 0 or 1 weeks means "every week", 2 week means "every second week", etc. Weeks are counted from January 2, 2023, which is the first Monday of 2023. This is to ensure workspaces are started consistently for all customers on the same n-week cycles. AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"` -<<<<<<< HEAD -<<<<<<< HEAD // A bitmap of days of week that autostart of a workspace is not allowed. Default allows all days. This is intended as a cost savings measure to prevent auto start on weekends (for example). AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` -======= - RequirePromotedVersion bool `db:"require_promoted_version" json:"require_promoted_version"` ->>>>>>> 5786e8135 (add migration) -======= RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) } // Joins in the username + avatar url of the created by user. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 75012a9636502..17fc93496f24b 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4726,15 +4726,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT -<<<<<<< HEAD -<<<<<<< HEAD - 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, max_ttl, 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, created_by_avatar_url, created_by_username -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username ->>>>>>> 5786e8135 (add migration) -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) + 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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users WHERE @@ -4771,15 +4763,8 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, -<<<<<<< HEAD -<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, -======= - &i.RequirePromotedVersion, ->>>>>>> 5786e8135 (add migration) -======= &i.RequireActiveVersion, ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4788,15 +4773,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT -<<<<<<< HEAD -<<<<<<< HEAD - 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, max_ttl, 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, created_by_avatar_url, created_by_username -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username ->>>>>>> 5786e8135 (add migration) -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) + 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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users AS templates WHERE @@ -4841,15 +4818,8 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, -<<<<<<< HEAD -<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, -======= - &i.RequirePromotedVersion, ->>>>>>> 5786e8135 (add migration) -======= &i.RequireActiveVersion, ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -4857,15 +4827,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many -<<<<<<< HEAD -<<<<<<< HEAD -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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username FROM template_with_users AS templates ->>>>>>> 5786e8135 (add migration) -======= -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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username FROM template_with_users AS templates ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) +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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users AS templates ORDER BY (name, id) ASC ` @@ -4903,15 +4865,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, -<<<<<<< HEAD -<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, -======= - &i.RequirePromotedVersion, ->>>>>>> 5786e8135 (add migration) -======= &i.RequireActiveVersion, ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -4930,15 +4885,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT -<<<<<<< HEAD -<<<<<<< HEAD - 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, max_ttl, 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, created_by_avatar_url, created_by_username -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_promoted_version, created_by_avatar_url, created_by_username ->>>>>>> 5786e8135 (add migration) -======= - 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, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, require_active_version, created_by_avatar_url, created_by_username ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) + 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, max_ttl, 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, created_by_avatar_url, created_by_username FROM template_with_users AS templates WHERE @@ -5013,15 +4960,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.TimeTilDormantAutoDelete, &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, -<<<<<<< HEAD -<<<<<<< HEAD &i.AutostartBlockDaysOfWeek, -======= - &i.RequirePromotedVersion, ->>>>>>> 5786e8135 (add migration) -======= &i.RequireActiveVersion, ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -5208,21 +5148,11 @@ SET max_ttl = $6, autostop_requirement_days_of_week = $7, autostop_requirement_weeks = $8, -<<<<<<< HEAD autostart_block_days_of_week = $9, failure_ttl = $10, time_til_dormant = $11, - time_til_dormant_autodelete = $12 -======= - failure_ttl = $9, - time_til_dormant = $10, - time_til_dormant_autodelete = $11, -<<<<<<< HEAD - require_promoted_version = $12 ->>>>>>> f6a08b15a (add template update plumbing) -======= - require_active_version = $12 ->>>>>>> 6586b6fdb (require_promoted_version -> require_active_version) + time_til_dormant_autodelete = $12, + require_active_version = $13 WHERE id = $1 ` diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 6ebf68aca146e..af7a5724458d7 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -8,35 +8,19 @@ We track the following resources: -<<<<<<< HEAD -| Resource | | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| -| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| -| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| -| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| -| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
wildcard_hostnametrue
| -======= -| Resource | | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| -| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| -| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| -| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| -| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| -| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
wildcard_hostnametrue
| ->>>>>>> c2a5a874d (make gen) +| Resource | | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| APIKey
login, logout, register, create, delete |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| +| AuditOAuthConvertState
|
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| +| Group
create, write, delete |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| +| GitSSHKey
create |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| Template
write, delete |
FieldTracked
active_version_idtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_ttltrue
nametrue
organization_idfalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| +| TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| +| User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| +| WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
wildcard_hostnametrue
| diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 0bb8c9e5ddec9..cf3b3663198e8 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -4379,7 +4379,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -<<<<<<< HEAD | Name | Type | Required | Restrictions | Description | | ---------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `active_user_count` | integer | false | | Active user count is set to -1 when loading. | @@ -4403,37 +4402,10 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `name` | string | false | | | | `organization_id` | string | false | | | | `provisioner` | string | false | | | +| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `time_til_dormant_autodelete_ms` | integer | false | | | | `time_til_dormant_ms` | integer | false | | | | `updated_at` | string | false | | | -======= -| Name | Type | Required | Restrictions | Description | -| ---------------------------------- | ---------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `active_user_count` | integer | false | | Active user count is set to -1 when loading. | -| `active_version_id` | string | 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 | | | -| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement is an enterprise feature. Its value is only used if your license is entitled to use the advanced template scheduling feature. | -| `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | -| `created_at` | string | false | | | -| `created_by_id` | string | false | | | -| `created_by_name` | string | false | | | -| `default_ttl_ms` | integer | false | | | -| `description` | string | false | | | -| `display_name` | string | false | | | -| `failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | -| `icon` | string | false | | | -| `id` | string | false | | | -| `max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured | -| `name` | string | false | | | -| `organization_id` | string | false | | | -| `provisioner` | string | false | | | -| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | -| `time_til_dormant_autodelete_ms` | integer | false | | | -| `time_til_dormant_ms` | integer | false | | | -| `updated_at` | string | false | | | ->>>>>>> c2a5a874d (make gen) #### Enumerated Values diff --git a/docs/api/templates.md b/docs/api/templates.md index 3c09588c191c6..26aeb30fac88c 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -92,7 +92,6 @@ Status Code **200** | `» 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. | -<<<<<<< HEAD | `»» 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. | | `» build_time_stats` | [codersdk.TemplateBuildTimeStats](schemas.md#codersdktemplatebuildtimestats) | false | | | | `»» [any property]` | [codersdk.TransitionStats](schemas.md#codersdktransitionstats) | false | | | @@ -111,33 +110,10 @@ Status Code **200** | `» name` | string | false | | | | `» organization_id` | string(uuid) | false | | | | `» provisioner` | string | false | | | +| `» require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `» time_til_dormant_autodelete_ms` | integer | false | | | | `» time_til_dormant_ms` | integer | false | | | | `» updated_at` | string(date-time) | false | | | -======= -| `»» 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. | -| `» build_time_stats` | [codersdk.TemplateBuildTimeStats](schemas.md#codersdktemplatebuildtimestats) | false | | | -| `»» [any property]` | [codersdk.TransitionStats](schemas.md#codersdktransitionstats) | false | | | -| `»»» p50` | integer | false | | | -| `»»» p95` | integer | false | | | -| `» created_at` | string(date-time) | false | | | -| `» created_by_id` | string(uuid) | false | | | -| `» created_by_name` | string | false | | | -| `» default_ttl_ms` | integer | false | | | -| `» description` | string | false | | | -| `» display_name` | string | false | | | -| `» failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | -| `» icon` | string | false | | | -| `» id` | string(uuid) | false | | | -| `» max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured | -| `» name` | string | false | | | -| `» organization_id` | string(uuid) | false | | | -| `» provisioner` | string | false | | | -| `» require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | -| `» time_til_dormant_autodelete_ms` | integer | false | | | -| `» time_til_dormant_ms` | integer | false | | | -| `» updated_at` | string(date-time) | false | | | ->>>>>>> c2a5a874d (make gen) #### Enumerated Values From 193388ce2969c323ef803f662bfd0cb399916fc4 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:24:40 +0000 Subject: [PATCH 11/30] space vs tabs --- coderd/database/queries/templates.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index 4c04768b55671..d63e8cf7634bc 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -124,7 +124,7 @@ SET failure_ttl = $10, time_til_dormant = $11, time_til_dormant_autodelete = $12, - require_active_version = $13 + require_active_version = $13 WHERE id = $1 ; From 3750bda969741b110a270fb1635b70c7e42b377a Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:43:08 +0000 Subject: [PATCH 12/30] tests and gen --- coderd/database/dbauthz/dbauthz_test.go | 5 ++++- coderd/database/queries.sql.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 28866396c8b86..4ca20ab88872a 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1213,7 +1213,10 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(rbac.ResourceWorkspace.WithOwner(u.ID.String()).InOrg(o.ID), rbac.ActionCreate) })) s.Run("Start/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + t := dbgen.Template(s.T(), db, database.Template{}) + w := dbgen.Workspace(s.T(), db, database.Workspace{ + TemplateID: t.ID, + }) check.Args(database.InsertWorkspaceBuildParams{ WorkspaceID: w.ID, Transition: database.WorkspaceTransitionStart, diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 17fc93496f24b..6ec19ee624acb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5152,7 +5152,7 @@ SET failure_ttl = $10, time_til_dormant = $11, time_til_dormant_autodelete = $12, - require_active_version = $13 + require_active_version = $13 WHERE id = $1 ` From 306d63eb80c769ec1b6e5502adca570c7eca5ebc Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:49:11 +0000 Subject: [PATCH 13/30] fix things --- coderd/coderdtest/coderdtest.go | 2 +- .../migrations/000166_template_active_version.down.sql | 5 +++-- .../TemplateGeneralSettingsPage/TemplateSettingsForm.tsx | 1 + .../TemplateSettingsPage.test.tsx | 1 + .../TemplateSchedulePage/TemplateScheduleForm.tsx | 2 ++ .../TemplateSchedulePage/TemplateSchedulePage.test.tsx | 1 + 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 40c346fabd86f..721bd982cab5f 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -926,7 +926,7 @@ func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID require.NoError(t, err, "fetch workspace template") req := codersdk.CreateWorkspaceBuildRequest{ - // TODO (JonA): I get this is for convenience but we shoul probably + // TODO (JonA): I get this is for convenience but we should probably // change this. Tripped me up why my test was passing when it shouldn't // have. TemplateVersionID: template.ActiveVersionID, diff --git a/coderd/database/migrations/000166_template_active_version.down.sql b/coderd/database/migrations/000166_template_active_version.down.sql index 2364ea5f52200..d3b4bba305e02 100644 --- a/coderd/database/migrations/000166_template_active_version.down.sql +++ b/coderd/database/migrations/000166_template_active_version.down.sql @@ -1,9 +1,10 @@ BEGIN; -ALTER TABLE templates DROP COLUMN require_active_version; - -- Update the template_with_users view; DROP VIEW template_with_users; + +ALTER TABLE templates DROP COLUMN require_active_version; + -- If you need to update this view, put 'DROP VIEW template_with_users;' before this. CREATE VIEW template_with_users diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 09f376a83420e..d6afac0fbd504 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -69,6 +69,7 @@ export const TemplateSettingsForm: FC = ({ template.allow_user_cancel_workspace_jobs, update_workspace_last_used_at: false, update_workspace_dormant_at: false, + require_active_version: false, }, validationSchema, onSubmit, diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx index 8df20b8c3d399..c9f2059672fce 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx @@ -43,6 +43,7 @@ const validFormValues: FormValues = { time_til_dormant_autodelete_ms: 0, update_workspace_last_used_at: false, update_workspace_dormant_at: false, + require_active_version: false, }; const renderTemplateSettingsPage = async () => { diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index 7d98f9de12eda..075bb30548795 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -107,6 +107,7 @@ export const TemplateScheduleForm: FC = ({ Boolean(template.time_til_dormant_autodelete_ms), update_workspace_last_used_at: false, update_workspace_dormant_at: false, + require_active_version: false, }, validationSchema, onSubmit: () => { @@ -220,6 +221,7 @@ export const TemplateScheduleForm: FC = ({ allow_user_autostop: form.values.allow_user_autostop, update_workspace_last_used_at: form.values.update_workspace_last_used_at, update_workspace_dormant_at: form.values.update_workspace_dormant_at, + require_active_version: false, }); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx index ac3c07a4f6d93..e780c98dd5d1b 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx @@ -26,6 +26,7 @@ const validFormValues: TemplateScheduleFormValues = { failure_cleanup_enabled: false, inactivity_cleanup_enabled: false, dormant_autodeletion_cleanup_enabled: false, + require_active_version: false, }; const renderTemplateSchedulePage = async () => { From f0c932a1db68b6db84a99830dc309a70653746f7 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 15 Oct 2023 23:56:54 +0000 Subject: [PATCH 14/30] fix query --- coderd/database/modelqueries.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index bb380a8293bdc..67a35bf4f52e3 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -88,6 +88,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.AutostartBlockDaysOfWeek, &i.CreatedByAvatarURL, &i.CreatedByUsername, + &i.RequireActiveVersion, ); err != nil { return nil, err } From 896320cc01087b05853cee7f3b6f7b76a7f66120 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 16 Oct 2023 00:14:54 +0000 Subject: [PATCH 15/30] fix it for real --- coderd/database/modelqueries.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 67a35bf4f52e3..5c78600237e1d 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -86,9 +86,9 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.AutostopRequirementDaysOfWeek, &i.AutostopRequirementWeeks, &i.AutostartBlockDaysOfWeek, + &i.RequireActiveVersion, &i.CreatedByAvatarURL, &i.CreatedByUsername, - &i.RequireActiveVersion, ); err != nil { return nil, err } From 83159337c1a555a845e3c09c7dfe641c95163009 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 16 Oct 2023 00:18:34 +0000 Subject: [PATCH 16/30] remove todo --- coderd/schedule/template.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 83bfc0dd68700..911b93edbb4e5 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -218,9 +218,6 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp var template database.Template err := db.InTx(func(db database.Store) error { - // TODO (JonA): This seems ripe for a bug. Should we not - // be reading the template as part of the tx and then also - // setting our isolation level to repeatable read? err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ ID: tpl.ID, UpdatedAt: dbtime.Now(), From 8e12b6698a8a911303cd31819edca70e38ec051d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 16 Oct 2023 00:24:00 +0000 Subject: [PATCH 17/30] different ctx --- enterprise/coderd/workspacebuilds_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/enterprise/coderd/workspacebuilds_test.go b/enterprise/coderd/workspacebuilds_test.go index 5039a1cf8d9ad..709673b0ebde5 100644 --- a/enterprise/coderd/workspacebuilds_test.go +++ b/enterprise/coderd/workspacebuilds_test.go @@ -1,7 +1,6 @@ package coderd_test import ( - "context" "net/http" "testing" @@ -121,7 +120,7 @@ func TestWorkspaceBuild(t *testing.T) { for _, c := range cases { t.Run(c.Name, func(t *testing.T) { - _, err = c.Client.CreateWorkspace(context.Background(), owner.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{ + _, err = c.Client.CreateWorkspace(ctx, owner.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{ TemplateVersionID: oldVersion.ID, Name: "abc123", AutomaticUpdates: codersdk.AutomaticUpdatesNever, From 6a34bc3bfc51b8aeca64edfbcf886a2f741a738d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 17 Oct 2023 01:23:27 +0000 Subject: [PATCH 18/30] pr comments --- coderd/autobuild/lifecycle_executor_test.go | 54 +++++++++++++++++++++ coderd/database/dbauthz/dbauthz_test.go | 54 +++++++++++++++++++++ coderd/templates.go | 1 + coderd/wsbuilder/wsbuilder.go | 2 +- codersdk/organizations.go | 4 ++ enterprise/coderd/templates_test.go | 14 +++++- 6 files changed, 126 insertions(+), 3 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index fd37166ea86db..34e6e286fdda9 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -783,6 +783,60 @@ func TestExecutorAutostopTemplateDisabled(t *testing.T) { assert.Len(t, stats.Transitions, 0) } +func TestExecutorRequireActiveVersion(t *testing.T) { + t.Parallel() + + // Test that an AGPL TemplateScheduleStore properly disables + // functionality. + t.Run("Unused", func(t *testing.T) { + t.Parallel() + + var ( + sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *") + ticker = make(chan time.Time) + statCh = make(chan autobuild.Stats) + + ownerClient = coderdtest.New(t, &coderdtest.Options{ + AutobuildTicker: ticker, + IncludeProvisionerDaemon: true, + AutobuildStats: statCh, + TemplateScheduleStore: schedule.NewAGPLTemplateScheduleStore(), + }) + ) + owner := coderdtest.CreateFirstUser(t, ownerClient) + + // Create an active and inactive template version. We'll + // build a regular member's workspace using a non-active + // template version and assert that the field is not abided + // since there is no enterprise license. + activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, activeVersion.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.RequireActiveVersion = true + ctr.VersionID = activeVersion.ID + }) + inactiveVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) + memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + ws := coderdtest.CreateWorkspace(t, memberClient, owner.OrganizationID, uuid.Nil, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.TemplateVersionID = inactiveVersion.ID + cwr.AutostartSchedule = ptr.Ref(sched.String()) + }) + _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, ownerClient, ws.LatestBuild.ID) + ws = coderdtest.MustTransitionWorkspace(t, memberClient, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { + req.TemplateVersionID = inactiveVersion.ID + }) + require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) + ticker <- sched.Next(ws.LatestBuild.CreatedAt) + stats := <-statCh + require.Len(t, stats.Transitions, 1) + + ws = coderdtest.MustWorkspace(t, memberClient, ws.ID) + require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) + }) +} + // TestExecutorFailedWorkspace test AGPL functionality which mainly // ensures that autostop actions as a result of a failed workspace // build do not trigger. diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 4ca20ab88872a..4d5f0d7a65443 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -21,6 +21,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/util/slice" + "github.com/coder/coder/v2/testutil" ) func TestAsNoActor(t *testing.T) { @@ -1223,6 +1224,59 @@ func (s *MethodTestSuite) TestWorkspace() { Reason: database.BuildReasonInitiator, }).Asserts(w.WorkspaceBuildRBAC(database.WorkspaceTransitionStart), rbac.ActionUpdate) })) + s.Run("Start/RequireActiveVersion/VersionMismatch/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { + t := dbgen.Template(s.T(), db, database.Template{}) + + ctx := testutil.Context(s.T(), testutil.WaitShort) + err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ + ID: t.ID, + RequireActiveVersion: true, + }) + require.NoError(s.T(), err) + + v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{ + TemplateID: uuid.NullUUID{UUID: t.ID}, + }) + w := dbgen.Workspace(s.T(), db, database.Workspace{ + TemplateID: t.ID, + }) + check.Args(database.InsertWorkspaceBuildParams{ + WorkspaceID: w.ID, + Transition: database.WorkspaceTransitionStart, + Reason: database.BuildReasonInitiator, + TemplateVersionID: v.ID, + }).Asserts( + w.WorkspaceBuildRBAC(database.WorkspaceTransitionStart), rbac.ActionUpdate, + t, rbac.ActionUpdate, + ) + })) + s.Run("Start/RequireActiveVersion/VersionsMatch/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { + v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{}) + t := dbgen.Template(s.T(), db, database.Template{ + ActiveVersionID: v.ID, + }) + + ctx := testutil.Context(s.T(), testutil.WaitShort) + err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ + ID: t.ID, + RequireActiveVersion: true, + }) + require.NoError(s.T(), err) + + w := dbgen.Workspace(s.T(), db, database.Workspace{ + TemplateID: t.ID, + }) + // Assert that we do not check for template update permissions + // if versions match. + check.Args(database.InsertWorkspaceBuildParams{ + WorkspaceID: w.ID, + Transition: database.WorkspaceTransitionStart, + Reason: database.BuildReasonInitiator, + TemplateVersionID: v.ID, + }).Asserts( + w.WorkspaceBuildRBAC(database.WorkspaceTransitionStart), rbac.ActionUpdate, + ) + })) s.Run("Delete/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { w := dbgen.Workspace(s.T(), db, database.Workspace{}) check.Args(database.InsertWorkspaceBuildParams{ diff --git a/coderd/templates.go b/coderd/templates.go index f6d71359486d0..45e66a62c0d8e 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -370,6 +370,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque FailureTTL: failureTTL, TimeTilDormant: dormantTTL, TimeTilDormantAutoDelete: dormantAutoDeletionTTL, + RequireActiveVersion: createTemplate.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("set template schedule options: %s", err) diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 2987615b2a3ac..34907888d3655 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -218,7 +218,7 @@ func (b *Builder) Build( return err }) if err != nil { - return nil, nil, xerrors.Errorf("too many errors; last error: %w", err) + return nil, nil, xerrors.Errorf("build tx: %w", err) } return workspaceBuild, provisionerJob, nil } diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 6c10bc2c91abe..cc206180f81ae 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -124,6 +124,10 @@ type CreateTemplateRequest struct { // and must be explicitly granted to users or groups in the permissions settings // of the template. DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"` + + // RequireActiveVersion mandates that workspaces are built with the active + // template version. + RequireActiveVersion bool `json:"require_active_version"` } // CreateWorkspaceRequest provides options for creating a new workspace. diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 67acc2e686738..34f41491e1152 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -588,14 +588,24 @@ func TestTemplates(t *testing.T) { }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.RequireActiveVersion = true + }) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - require.False(t, template.RequireActiveVersion) + require.True(t, template.RequireActiveVersion) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + // Update the field and assert it persists. updatedTemplate, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ + RequireActiveVersion: false, + }) + require.NoError(t, err) + require.False(t, updatedTemplate.RequireActiveVersion) + + // Flip it back to ensure we aren't hardcoding to a default value. + updatedTemplate, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ RequireActiveVersion: true, }) require.NoError(t, err) From da82c79e7871c50ba85c5ae930792b9fb3cdc96c Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 17 Oct 2023 02:51:13 +0000 Subject: [PATCH 19/30] fix a bug --- enterprise/coderd/schedule/template.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index 808f3635dd4f5..3197f7096844e 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -117,7 +117,8 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S int64(opts.TimeTilDormant) == tpl.TimeTilDormant && int64(opts.TimeTilDormantAutoDelete) == tpl.TimeTilDormantAutoDelete && opts.UserAutostartEnabled == tpl.AllowUserAutostart && - opts.UserAutostopEnabled == tpl.AllowUserAutostop { + opts.UserAutostopEnabled == tpl.AllowUserAutostop && + opts.RequireActiveVersion == tpl.RequireActiveVersion { // Avoid updating the UpdatedAt timestamp if nothing will be changed. return tpl, nil } From 8853b9559d83c41619a3bf163c277e2d1b639ade Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 17 Oct 2023 02:52:16 +0000 Subject: [PATCH 20/30] make gen --- coderd/apidoc/docs.go | 4 ++++ coderd/apidoc/swagger.json | 4 ++++ docs/api/schemas.md | 2 ++ docs/api/templates.md | 1 + site/src/api/typesGenerated.ts | 1 + 5 files changed, 12 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 64e0bbcf120d7..d3ff455e18324 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7788,6 +7788,10 @@ const docTemplate = `{ "description": "Name is the name of the template.", "type": "string" }, + "require_active_version": { + "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", + "type": "boolean" + }, "template_version_id": { "description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.", "type": "string", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 7397426af3d8d..3988cbe4a6d31 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6945,6 +6945,10 @@ "description": "Name is the name of the template.", "type": "string" }, + "require_active_version": { + "description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.", + "type": "boolean" + }, "template_version_id": { "description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.", "type": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index cf3b3663198e8..da6715839647a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1584,6 +1584,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "icon": "string", "max_ttl_ms": 0, "name": "string", + "require_active_version": true, "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1" } ``` @@ -1607,6 +1608,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | | `max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured | | `name` | string | true | | Name is the name of the template. | +| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | | `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. | | This is required on creation to enable a user-flow of validating a template works. There is no reason the data-model cannot support empty templates, but it doesn't make sense for users. | diff --git a/docs/api/templates.md b/docs/api/templates.md index 26aeb30fac88c..08de540e2a7f2 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -161,6 +161,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "icon": "string", "max_ttl_ms": 0, "name": "string", + "require_active_version": true, "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1" } ``` diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b534194568c2b..c31b752b4eebc 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -216,6 +216,7 @@ export interface CreateTemplateRequest { readonly dormant_ttl_ms?: number; readonly delete_ttl_ms?: number; readonly disable_everyone_group_access: boolean; + readonly require_active_version: boolean; } // From codersdk/templateversions.go From 6ec940c53bf6b13716096e2dd2f91eba6ccfdf73 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 00:50:00 +0000 Subject: [PATCH 21/30] add access control implementation --- coderd/coderd.go | 9 ++++ coderd/coderdtest/coderdtest.go | 7 ++- coderd/database/dbauthz/accesscontrol.go | 63 ++++++++++++++++++++++++ coderd/database/dbauthz/dbauthz.go | 24 +++++++-- coderd/database/dbauthz/dbauthz_test.go | 22 ++++++--- coderd/database/dbauthz/setup_test.go | 4 +- coderd/database/dbfake/dbfake.go | 21 +++++++- coderd/database/dbmetrics/dbmetrics.go | 7 +++ coderd/database/dbmock/dbmock.go | 14 ++++++ coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 24 +++++++-- coderd/database/queries/templates.sql | 12 ++++- coderd/schedule/template.go | 1 - codersdk/deployment.go | 2 + enterprise/coderd/coderd.go | 9 ++++ enterprise/coderd/schedule/template.go | 1 - 16 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 coderd/database/dbauthz/accesscontrol.go diff --git a/coderd/coderd.go b/coderd/coderd.go index f301265cc5ad7..57eafa6015eb1 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -208,11 +208,17 @@ func New(options *Options) *API { if options.Authorizer == nil { options.Authorizer = rbac.NewCachingAuthorizer(options.PrometheusRegistry) } + + acs := &atomic.Pointer[dbauthz.AccessControlStore]{} + var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + acs.Store(&tacs) options.Database = dbauthz.New( options.Database, options.Authorizer, options.Logger.Named("authz_querier"), + acs, ) + experiments := ReadExperiments( options.Logger, options.DeploymentValues.Experiments.Value(), ) @@ -1007,6 +1013,9 @@ type API struct { UserQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore] // DERPMapper mutates the DERPMap to include workspace proxies. DERPMapper atomic.Pointer[func(derpMap *tailcfg.DERPMap) *tailcfg.DERPMap] + // AccessControlStore is a pointer to an atomic pointer since it is + // passed to dbauthz. + AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore] HTTPAuth *HTTPAuthorizer diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 721bd982cab5f..6ff3963ce3711 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -218,7 +218,12 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can if options.Database == nil { options.Database, options.Pubsub = dbtestutil.NewDB(t) - options.Database = dbauthz.New(options.Database, options.Authorizer, options.Logger.Leveled(slog.LevelDebug)) + + acs := &atomic.Pointer[dbauthz.AccessControlStore]{} + var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + acs.Store(&tacs) + + options.Database = dbauthz.New(options.Database, options.Authorizer, options.Logger.Leveled(slog.LevelDebug), acs) } // Some routes expect a deployment ID, so just make sure one exists. diff --git a/coderd/database/dbauthz/accesscontrol.go b/coderd/database/dbauthz/accesscontrol.go new file mode 100644 index 0000000000000..e40874430c039 --- /dev/null +++ b/coderd/database/dbauthz/accesscontrol.go @@ -0,0 +1,63 @@ +package dbauthz + +import ( + "context" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/database" +) + +// AccessControlStore fetches access control-related configuration +// that is used when determining whether an actor is authorized +// to interact with an RBAC object. +type AccessControlStore interface { + GetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID) (TemplateAccessControl, error) + SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error +} + +type TemplateAccessControl struct { + RequireActiveVersion bool +} + +// AGPLTemplateAccessControlStore always returns the defaults for access control +// settings. +type AGPLTemplateAccessControlStore struct{} + +var _ AccessControlStore = AGPLTemplateAccessControlStore{} + +func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(context.Context, database.Store, uuid.UUID) (TemplateAccessControl, error) { + return TemplateAccessControl{ + RequireActiveVersion: false, + }, nil +} + +func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, TemplateAccessControl) error { + return nil +} + +// TODO (JonA): This is kind of a no-no since enterprise shouldn't leak into +// the AGPL implementation. +type EnterpriseTemplateAccessControlStore struct{} + +func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID) (TemplateAccessControl, error) { + t, err := store.GetTemplateByID(ctx, id) + if err != nil { + return TemplateAccessControl{}, xerrors.Errorf("get template: %w", err) + } + return TemplateAccessControl{ + RequireActiveVersion: t.RequireActiveVersion, + }, nil +} + +func (EnterpriseTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error { + err := store.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ + ID: id, + RequireActiveVersion: opts.RequireActiveVersion, + }) + if err != nil { + return xerrors.Errorf("update template access control: %w", err) + } + return nil +} diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index cdea7f0a744c0..94b3d4321bc2f 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "sync/atomic" "time" "github.com/google/uuid" @@ -101,9 +102,10 @@ type querier struct { db database.Store auth rbac.Authorizer log slog.Logger + acs *atomic.Pointer[AccessControlStore] } -func New(db database.Store, authorizer rbac.Authorizer, logger slog.Logger) database.Store { +func New(db database.Store, authorizer rbac.Authorizer, logger slog.Logger, acs *atomic.Pointer[AccessControlStore]) database.Store { // If the underlying db store is already a querier, return it. // Do not double wrap. if slices.Contains(db.Wrappers(), wrapname) { @@ -113,6 +115,7 @@ func New(db database.Store, authorizer rbac.Authorizer, logger slog.Logger) data db: db, auth: authorizer, log: logger, + acs: acs, } } @@ -507,7 +510,7 @@ func (q *querier) Ping(ctx context.Context) (time.Duration, error) { func (q *querier) InTx(function func(querier database.Store) error, txOpts *sql.TxOptions) error { return q.db.InTx(func(tx database.Store) error { // Wrap the transaction store in a querier. - wrapped := New(tx, q.auth, q.log) + wrapped := New(tx, q.auth, q.log, q.acs) return function(wrapped) }, txOpts) } @@ -2214,17 +2217,20 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW // If we're starting a workspace we need to check the template. if arg.Transition == database.WorkspaceTransitionStart { - // Fetch the template. We have to ensure that the request - // abides by the update policy of the templates. t, err := q.db.GetTemplateByID(ctx, w.TemplateID) if err != nil { return xerrors.Errorf("get template by id: %w", err) } + accessControl, err := (*q.acs.Load()).GetTemplateAccessControl(ctx, q.db, w.TemplateID) + if err != nil { + return xerrors.Errorf("get template access control by id: %w", err) + } + // If the template requires the active version we need to check if // the user is a template admin. If they aren't and are attempting // to use a non-active version then we must fail the request. - if t.RequireActiveVersion { + if accessControl.RequireActiveVersion { if arg.TemplateVersionID != t.ActiveVersionID { if err = q.authorizeContext(ctx, rbac.ActionUpdate, t); err != nil { return xerrors.Errorf("cannot use non-active version: %w", err) @@ -2463,6 +2469,14 @@ func (q *querier) UpdateTemplateACLByID(ctx context.Context, arg database.Update return fetchAndExec(q.log, q.auth, rbac.ActionCreate, fetch, q.db.UpdateTemplateACLByID)(ctx, arg) } +func (q *querier) UpdateTemplateAccessControlByID(ctx context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { + fetch := func(ctx context.Context, arg database.UpdateTemplateAccessControlByIDParams) (database.Template, error) { + return q.db.GetTemplateByID(ctx, arg.ID) + } + return update(q.log, q.auth, fetch, q.db.UpdateTemplateAccessControlByID)(ctx, arg) + +} + func (q *querier) UpdateTemplateActiveVersionByID(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error { fetch := func(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) (database.Template, error) { return q.db.GetTemplateByID(ctx, arg.ID) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 4d5f0d7a65443..1839f35095533 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "reflect" + "sync/atomic" "testing" "time" @@ -62,7 +63,7 @@ func TestAsNoActor(t *testing.T) { func TestPing(t *testing.T) { t.Parallel() - q := dbauthz.New(dbfake.New(), &coderdtest.RecordingAuthorizer{}, slog.Make()) + q := dbauthz.New(dbfake.New(), &coderdtest.RecordingAuthorizer{}, slog.Make(), accessControlStorePointer()) _, err := q.Ping(context.Background()) require.NoError(t, err, "must not error") } @@ -74,7 +75,7 @@ func TestInTX(t *testing.T) { db := dbfake.New() q := dbauthz.New(db, &coderdtest.RecordingAuthorizer{ Wrapped: &coderdtest.FakeAuthorizer{AlwaysReturn: xerrors.New("custom error")}, - }, slog.Make()) + }, slog.Make(), accessControlStorePointer()) actor := rbac.Subject{ ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleOwner()}, @@ -110,8 +111,8 @@ func TestNew(t *testing.T) { // Double wrap should not cause an actual double wrap. So only 1 rbac call // should be made. - az := dbauthz.New(db, rec, slog.Make()) - az = dbauthz.New(az, rec, slog.Make()) + az := dbauthz.New(db, rec, slog.Make(), accessControlStorePointer()) + az = dbauthz.New(az, rec, slog.Make(), accessControlStorePointer()) w, err := az.GetWorkspaceByID(ctx, exp.ID) require.NoError(t, err, "must not error") @@ -128,7 +129,7 @@ func TestDBAuthzRecursive(t *testing.T) { t.Parallel() q := dbauthz.New(dbfake.New(), &coderdtest.RecordingAuthorizer{ Wrapped: &coderdtest.FakeAuthorizer{AlwaysReturn: nil}, - }, slog.Make()) + }, slog.Make(), accessControlStorePointer()) actor := rbac.Subject{ ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleOwner()}, @@ -1228,7 +1229,7 @@ func (s *MethodTestSuite) TestWorkspace() { t := dbgen.Template(s.T(), db, database.Template{}) ctx := testutil.Context(s.T(), testutil.WaitShort) - err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ + err := db.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ ID: t.ID, RequireActiveVersion: true, }) @@ -1257,7 +1258,7 @@ func (s *MethodTestSuite) TestWorkspace() { }) ctx := testutil.Context(s.T(), testutil.WaitShort) - err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ + err := db.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ ID: t.ID, RequireActiveVersion: true, }) @@ -1670,3 +1671,10 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, rbac.ActionCreate) })) } + +func accessControlStorePointer() *atomic.Pointer[dbauthz.AccessControlStore] { + acs := &atomic.Pointer[dbauthz.AccessControlStore]{} + var tacs dbauthz.AccessControlStore = dbauthz.EnterpriseTemplateAccessControlStore{} + acs.Store(&tacs) + return acs +} diff --git a/coderd/database/dbauthz/setup_test.go b/coderd/database/dbauthz/setup_test.go index 9efcf5ef9418e..3dec592505999 100644 --- a/coderd/database/dbauthz/setup_test.go +++ b/coderd/database/dbauthz/setup_test.go @@ -59,7 +59,7 @@ func (s *MethodTestSuite) SetupSuite() { mockStore := dbmock.NewMockStore(ctrl) // We intentionally set no expectations apart from this. mockStore.EXPECT().Wrappers().Return([]string{}).AnyTimes() - az := dbauthz.New(mockStore, nil, slog.Make()) + az := dbauthz.New(mockStore, nil, slog.Make(), accessControlStorePointer()) // Take the underlying type of the interface. azt := reflect.TypeOf(az).Elem() s.methodAccounting = make(map[string]int) @@ -110,7 +110,7 @@ func (s *MethodTestSuite) Subtest(testCaseF func(db database.Store, check *expec rec := &coderdtest.RecordingAuthorizer{ Wrapped: fakeAuthorizer, } - az := dbauthz.New(db, rec, slog.Make()) + az := dbauthz.New(db, rec, slog.Make(), accessControlStorePointer()) actor := rbac.Subject{ ID: uuid.NewString(), Roles: rbac.RoleNames{rbac.RoleOwner()}, diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 02aabdde179d9..1b70fbb851710 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -5642,6 +5642,26 @@ func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.Upda return sql.ErrNoRows } +func (q *FakeQuerier) UpdateTemplateAccessControlByID(ctx context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { + if err := validateDatabaseType(arg); err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for idx, tpl := range q.templates { + if tpl.ID != arg.ID { + continue + } + tpl.RequireActiveVersion = arg.RequireActiveVersion + q.templates[idx] = tpl + return nil + } + + return sql.ErrNoRows +} + func (q *FakeQuerier) UpdateTemplateActiveVersionByID(_ context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error { if err := validateDatabaseType(arg); err != nil { return err @@ -5729,7 +5749,6 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database tpl.FailureTTL = arg.FailureTTL tpl.TimeTilDormant = arg.TimeTilDormant tpl.TimeTilDormantAutoDelete = arg.TimeTilDormantAutoDelete - tpl.RequireActiveVersion = arg.RequireActiveVersion q.templates[idx] = tpl return nil } diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index ece7020139b0f..ed3ff65b18378 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1523,6 +1523,13 @@ func (m metricsStore) UpdateTemplateACLByID(ctx context.Context, arg database.Up return err } +func (m metricsStore) UpdateTemplateAccessControlByID(ctx context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { + start := time.Now() + r0 := m.s.UpdateTemplateAccessControlByID(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateTemplateAccessControlByID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpdateTemplateActiveVersionByID(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error { start := time.Now() err := m.s.UpdateTemplateActiveVersionByID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 31614be3ae919..491937c6aeb6d 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3213,6 +3213,20 @@ func (mr *MockStoreMockRecorder) UpdateTemplateACLByID(arg0, arg1 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateACLByID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateACLByID), arg0, arg1) } +// UpdateTemplateAccessControlByID mocks base method. +func (m *MockStore) UpdateTemplateAccessControlByID(arg0 context.Context, arg1 database.UpdateTemplateAccessControlByIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTemplateAccessControlByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateTemplateAccessControlByID indicates an expected call of UpdateTemplateAccessControlByID. +func (mr *MockStoreMockRecorder) UpdateTemplateAccessControlByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateAccessControlByID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateAccessControlByID), arg0, arg1) +} + // UpdateTemplateActiveVersionByID mocks base method. func (m *MockStore) UpdateTemplateActiveVersionByID(arg0 context.Context, arg1 database.UpdateTemplateActiveVersionByIDParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 99503ba40e3d6..bb2e95d17c309 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -301,6 +301,7 @@ type sqlcQuerier interface { UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error) UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) error + UpdateTemplateAccessControlByID(ctx context.Context, arg UpdateTemplateAccessControlByIDParams) error UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 6ec19ee624acb..d4978e5d141e8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5058,6 +5058,25 @@ func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTempla return err } +const updateTemplateAccessControlByID = `-- name: UpdateTemplateAccessControlByID :exec +UPDATE + templates +SET + require_active_version = $2 +WHERE + id = $1 +` + +type UpdateTemplateAccessControlByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` +} + +func (q *sqlQuerier) UpdateTemplateAccessControlByID(ctx context.Context, arg UpdateTemplateAccessControlByIDParams) error { + _, err := q.db.ExecContext(ctx, updateTemplateAccessControlByID, arg.ID, arg.RequireActiveVersion) + return err +} + const updateTemplateActiveVersionByID = `-- name: UpdateTemplateActiveVersionByID :exec UPDATE templates @@ -5151,8 +5170,7 @@ SET autostart_block_days_of_week = $9, failure_ttl = $10, time_til_dormant = $11, - time_til_dormant_autodelete = $12, - require_active_version = $13 + time_til_dormant_autodelete = $12 WHERE id = $1 ` @@ -5170,7 +5188,6 @@ type UpdateTemplateScheduleByIDParams struct { FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"` TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"` TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"` - RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` } func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error { @@ -5187,7 +5204,6 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT arg.FailureTTL, arg.TimeTilDormant, arg.TimeTilDormantAutoDelete, - arg.RequireActiveVersion, ) return err } diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index d63e8cf7634bc..c5bc72d7911d6 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -123,8 +123,7 @@ SET autostart_block_days_of_week = $9, failure_ttl = $10, time_til_dormant = $11, - time_til_dormant_autodelete = $12, - require_active_version = $13 + time_til_dormant_autodelete = $12 WHERE id = $1 ; @@ -170,3 +169,12 @@ SELECT 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 +WHERE + id = $1 +; diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 911b93edbb4e5..0a7790c5d2d85 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -233,7 +233,6 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp FailureTTL: tpl.FailureTTL, TimeTilDormant: tpl.TimeTilDormant, TimeTilDormantAutoDelete: tpl.TimeTilDormantAutoDelete, - RequireActiveVersion: tpl.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 195915b052ea4..a6ba24f2a7f99 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -50,6 +50,7 @@ const ( FeatureExternalTokenEncryption FeatureName = "external_token_encryption" FeatureTemplateAutostopRequirement FeatureName = "template_autostop_requirement" FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions" + FeatureAccessControl FeatureName = "access_control" ) // FeatureNames must be kept in-sync with the Feature enum above. @@ -70,6 +71,7 @@ var FeatureNames = []FeatureName{ FeatureExternalTokenEncryption, FeatureTemplateAutostopRequirement, FeatureWorkspaceBatchActions, + FeatureAccessControl, } // Humanize returns the feature name in a human-readable format. diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index b49890589a6c3..543e5f9729499 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -482,6 +482,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { codersdk.FeatureTemplateAutostopRequirement: api.AGPL.Experiments.Enabled(codersdk.ExperimentTemplateAutostopRequirement) && api.DefaultQuietHoursSchedule != "", codersdk.FeatureWorkspaceProxy: true, codersdk.FeatureUserRoleManagement: true, + codersdk.FeatureAccessControl: true, }) if err != nil { return err @@ -654,6 +655,14 @@ func (api *API) updateEntitlements(ctx context.Context) error { } } + if initial, changed, enabled := featureChanged(codersdk.FeatureAccessControl); shouldUpdate(initial, changed, enabled) { + var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + if enabled { + acs = dbauthz.EnterpriseTemplateAccessControlStore{} + } + api.AGPL.AccessControlStore.Store(&acs) + } + // External token encryption is soft-enforced featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption] featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0 diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index 3197f7096844e..d18a90054f173 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -153,7 +153,6 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S FailureTTL: int64(opts.FailureTTL), TimeTilDormant: int64(opts.TimeTilDormant), TimeTilDormantAutoDelete: int64(opts.TimeTilDormantAutoDelete), - RequireActiveVersion: opts.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("update template schedule: %w", err) From cd504820e6b3a42506d5d041e656382ce0810771 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 01:31:39 +0000 Subject: [PATCH 22/30] fix some tests --- coderd/coderd.go | 2 ++ coderd/coderdtest/coderdtest.go | 6 ------ coderd/templates.go | 18 ++++++++++++++++++ enterprise/coderd/templates_test.go | 2 +- enterprise/coderd/workspacebuilds_test.go | 6 +++--- enterprise/coderd/workspaces_test.go | 2 +- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 57eafa6015eb1..b733b8545cf86 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -212,6 +212,7 @@ func New(options *Options) *API { acs := &atomic.Pointer[dbauthz.AccessControlStore]{} var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} acs.Store(&tacs) + options.Database = dbauthz.New( options.Database, options.Authorizer, @@ -375,6 +376,7 @@ func New(options *Options) *API { Auditor: atomic.Pointer[audit.Auditor]{}, TemplateScheduleStore: options.TemplateScheduleStore, UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore, + AccessControlStore: acs, Experiments: experiments, healthCheckGroup: &singleflight.Group[string, *healthcheck.Report]{}, Acquirer: provisionerdserver.NewAcquirer( diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 6ff3963ce3711..25827ec3f205e 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -218,12 +218,6 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can if options.Database == nil { options.Database, options.Pubsub = dbtestutil.NewDB(t) - - acs := &atomic.Pointer[dbauthz.AccessControlStore]{} - var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} - acs.Store(&tacs) - - options.Database = dbauthz.New(options.Database, options.Authorizer, options.Logger.Leveled(slog.LevelDebug), acs) } // Some routes expect a deployment ID, so just make sure one exists. diff --git a/coderd/templates.go b/coderd/templates.go index db17a72aa52d7..eb57521f159f8 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -352,6 +352,15 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return xerrors.Errorf("get template by id: %s", err) } + if createTemplate.RequireActiveVersion { + err = (*api.AccessControlStore.Load()).SetTemplateAccessControl(ctx, tx, id, dbauthz.TemplateAccessControl{ + RequireActiveVersion: createTemplate.RequireActiveVersion, + }) + if err != nil { + return xerrors.Errorf("set template access control: %w", err) + } + } + dbTemplate, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, dbTemplate, schedule.TemplateScheduleOptions{ UserAutostartEnabled: allowUserAutostart, UserAutostopEnabled: allowUserAutostop, @@ -645,6 +654,15 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("fetch updated template metadata: %w", err) } + if updated.RequireActiveVersion != req.RequireActiveVersion { + err = (*api.AccessControlStore.Load()).SetTemplateAccessControl(ctx, tx, template.ID, dbauthz.TemplateAccessControl{ + RequireActiveVersion: req.RequireActiveVersion, + }) + if err != nil { + return xerrors.Errorf("set template access control: %w", err) + } + } + defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 34f41491e1152..6b51e706e2d81 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -582,7 +582,7 @@ func TestTemplates(t *testing.T) { }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ - codersdk.FeatureAdvancedTemplateScheduling: 1, + codersdk.FeatureAccessControl: 1, }, }, }) diff --git a/enterprise/coderd/workspacebuilds_test.go b/enterprise/coderd/workspacebuilds_test.go index 709673b0ebde5..615991a65dec9 100644 --- a/enterprise/coderd/workspacebuilds_test.go +++ b/enterprise/coderd/workspacebuilds_test.go @@ -11,7 +11,6 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" - "github.com/coder/coder/v2/enterprise/coderd/schedule" "github.com/coder/coder/v2/testutil" ) @@ -24,12 +23,12 @@ func TestWorkspaceBuild(t *testing.T) { ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ IncludeProvisionerDaemon: true, - TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ - codersdk.FeatureAdvancedTemplateScheduling: 1, + codersdk.FeatureAccessControl: 1, codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAdvancedTemplateScheduling: 1, }, }, }) @@ -45,6 +44,7 @@ func TestWorkspaceBuild(t *testing.T) { RequireActiveVersion: true, }) require.NoError(t, err) + require.True(t, template.RequireActiveVersion) // Create a new version that we will promote. activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 55a53512e5c76..a80107d379e74 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -752,7 +752,7 @@ func TestWorkspaceAutobuild(t *testing.T) { TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), }, LicenseOptions: &coderdenttest.LicenseOptions{ - Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1}, + Features: license.Features{codersdk.FeatureAccessControl: 1}, }, }) From 9ee6898914210ee6126f61bc6ede3808c7f83acd Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 01:42:26 +0000 Subject: [PATCH 23/30] make gen --- coderd/database/dbauthz/dbauthz.go | 1 - coderd/database/dbfake/dbfake.go | 2 +- site/src/api/typesGenerated.ts | 2 ++ site/src/pages/CreateTemplatePage/utils.ts | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 94b3d4321bc2f..a09a95d23e0ba 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2474,7 +2474,6 @@ func (q *querier) UpdateTemplateAccessControlByID(ctx context.Context, arg datab return q.db.GetTemplateByID(ctx, arg.ID) } return update(q.log, q.auth, fetch, q.db.UpdateTemplateAccessControlByID)(ctx, arg) - } func (q *querier) UpdateTemplateActiveVersionByID(ctx context.Context, arg database.UpdateTemplateActiveVersionByIDParams) error { diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 1b70fbb851710..fe6f5a43c71b3 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -5642,7 +5642,7 @@ func (q *FakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.Upda return sql.ErrNoRows } -func (q *FakeQuerier) UpdateTemplateAccessControlByID(ctx context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { +func (q *FakeQuerier) UpdateTemplateAccessControlByID(_ context.Context, arg database.UpdateTemplateAccessControlByIDParams) error { if err := validateDatabaseType(arg); err != nil { return err } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index c31b752b4eebc..af608fd577322 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1713,6 +1713,7 @@ export const Experiments: Experiment[] = [ // From codersdk/deployment.go export type FeatureName = + | "access_control" | "advanced_template_scheduling" | "appearance" | "audit_log" @@ -1729,6 +1730,7 @@ export type FeatureName = | "workspace_batch_actions" | "workspace_proxy"; export const FeatureNames: FeatureName[] = [ + "access_control", "advanced_template_scheduling", "appearance", "audit_log", diff --git a/site/src/pages/CreateTemplatePage/utils.ts b/site/src/pages/CreateTemplatePage/utils.ts index 2e525eae7a29e..fdeb1046d3869 100644 --- a/site/src/pages/CreateTemplatePage/utils.ts +++ b/site/src/pages/CreateTemplatePage/utils.ts @@ -37,6 +37,7 @@ export const newTemplate = (formData: CreateTemplateData) => { autostart_requirement: { days_of_week: autostart_requirement_days_of_week, }, + require_active_version: false, }; }; From ef112de223eb8f759ea833792ff880678d4201bb Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 06:02:05 +0000 Subject: [PATCH 24/30] move enterprise access control impl into own pkg --- coderd/database/dbauthz/accesscontrol.go | 30 ++-------------------- coderd/database/dbauthz/dbauthz.go | 2 +- coderd/database/dbauthz/dbauthz_test.go | 10 -------- coderd/database/dbauthz/setup_test.go | 20 +++++++++++++++ enterprise/coderd/coderd.go | 7 ++--- enterprise/coderd/dbauthz/accesscontrol.go | 30 ++++++++++++++++++++++ 6 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 enterprise/coderd/dbauthz/accesscontrol.go diff --git a/coderd/database/dbauthz/accesscontrol.go b/coderd/database/dbauthz/accesscontrol.go index e40874430c039..42a3b84a4bbd6 100644 --- a/coderd/database/dbauthz/accesscontrol.go +++ b/coderd/database/dbauthz/accesscontrol.go @@ -4,7 +4,6 @@ import ( "context" "github.com/google/uuid" - "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" ) @@ -13,7 +12,7 @@ import ( // that is used when determining whether an actor is authorized // to interact with an RBAC object. type AccessControlStore interface { - GetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID) (TemplateAccessControl, error) + GetTemplateAccessControl(t database.Template) (TemplateAccessControl, error) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error } @@ -27,7 +26,7 @@ type AGPLTemplateAccessControlStore struct{} var _ AccessControlStore = AGPLTemplateAccessControlStore{} -func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(context.Context, database.Store, uuid.UUID) (TemplateAccessControl, error) { +func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) (TemplateAccessControl, error) { return TemplateAccessControl{ RequireActiveVersion: false, }, nil @@ -36,28 +35,3 @@ func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(context.Context, func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, TemplateAccessControl) error { return nil } - -// TODO (JonA): This is kind of a no-no since enterprise shouldn't leak into -// the AGPL implementation. -type EnterpriseTemplateAccessControlStore struct{} - -func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID) (TemplateAccessControl, error) { - t, err := store.GetTemplateByID(ctx, id) - if err != nil { - return TemplateAccessControl{}, xerrors.Errorf("get template: %w", err) - } - return TemplateAccessControl{ - RequireActiveVersion: t.RequireActiveVersion, - }, nil -} - -func (EnterpriseTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error { - err := store.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ - ID: id, - RequireActiveVersion: opts.RequireActiveVersion, - }) - if err != nil { - return xerrors.Errorf("update template access control: %w", err) - } - return nil -} diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a09a95d23e0ba..a9bbeb2889c5e 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2222,7 +2222,7 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW return xerrors.Errorf("get template by id: %w", err) } - accessControl, err := (*q.acs.Load()).GetTemplateAccessControl(ctx, q.db, w.TemplateID) + accessControl, err := (*q.acs.Load()).GetTemplateAccessControl(t) if err != nil { return xerrors.Errorf("get template access control by id: %w", err) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 1839f35095533..a5847839b2478 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -5,7 +5,6 @@ import ( "database/sql" "encoding/json" "reflect" - "sync/atomic" "testing" "time" @@ -1227,14 +1226,12 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("Start/RequireActiveVersion/VersionMismatch/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { t := dbgen.Template(s.T(), db, database.Template{}) - ctx := testutil.Context(s.T(), testutil.WaitShort) err := db.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ ID: t.ID, RequireActiveVersion: true, }) require.NoError(s.T(), err) - v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{ TemplateID: uuid.NullUUID{UUID: t.ID}, }) @@ -1671,10 +1668,3 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, rbac.ActionCreate) })) } - -func accessControlStorePointer() *atomic.Pointer[dbauthz.AccessControlStore] { - acs := &atomic.Pointer[dbauthz.AccessControlStore]{} - var tacs dbauthz.AccessControlStore = dbauthz.EnterpriseTemplateAccessControlStore{} - acs.Store(&tacs) - return acs -} diff --git a/coderd/database/dbauthz/setup_test.go b/coderd/database/dbauthz/setup_test.go index 3dec592505999..246355c920694 100644 --- a/coderd/database/dbauthz/setup_test.go +++ b/coderd/database/dbauthz/setup_test.go @@ -6,6 +6,7 @@ import ( "reflect" "sort" "strings" + "sync/atomic" "testing" "github.com/golang/mock/gomock" @@ -398,3 +399,22 @@ func (emptyPreparedAuthorized) Authorize(_ context.Context, _ rbac.Object) error func (emptyPreparedAuthorized) CompileToSQL(_ context.Context, _ regosql.ConvertConfig) (string, error) { return "", nil } + +func accessControlStorePointer() *atomic.Pointer[dbauthz.AccessControlStore] { + acs := &atomic.Pointer[dbauthz.AccessControlStore]{} + var tacs dbauthz.AccessControlStore = fakeAccessControlStore{} + acs.Store(&tacs) + return acs +} + +type fakeAccessControlStore struct{} + +func (fakeAccessControlStore) GetTemplateAccessControl(t database.Template) (dbauthz.TemplateAccessControl, error) { + return dbauthz.TemplateAccessControl{ + RequireActiveVersion: t.RequireActiveVersion, + }, nil +} + +func (fakeAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, dbauthz.TemplateAccessControl) error { + panic("not implemented") +} diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 543e5f9729499..55defaf9b964d 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -24,12 +24,13 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/coderd" agplaudit "github.com/coder/coder/v2/coderd/audit" - "github.com/coder/coder/v2/coderd/database/dbauthz" + agpldbauthz "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/rbac" agplschedule "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/dbauthz" "github.com/coder/coder/v2/enterprise/coderd/license" "github.com/coder/coder/v2/enterprise/coderd/proxyhealth" "github.com/coder/coder/v2/enterprise/coderd/schedule" @@ -113,7 +114,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { api.AGPL.SiteHandler.RegionsFetcher = func(ctx context.Context) (any, error) { // If the user can read the workspace proxy resource, return that. // If not, always default to the regions. - actor, ok := dbauthz.ActorFromContext(ctx) + actor, ok := agpldbauthz.ActorFromContext(ctx) if ok && api.Authorizer.Authorize(ctx, actor, rbac.ActionRead, rbac.ResourceWorkspaceProxy) == nil { return api.fetchWorkspaceProxies(ctx) } @@ -656,7 +657,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { } if initial, changed, enabled := featureChanged(codersdk.FeatureAccessControl); shouldUpdate(initial, changed, enabled) { - var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + var acs agpldbauthz.AccessControlStore = agpldbauthz.AGPLTemplateAccessControlStore{} if enabled { acs = dbauthz.EnterpriseTemplateAccessControlStore{} } diff --git a/enterprise/coderd/dbauthz/accesscontrol.go b/enterprise/coderd/dbauthz/accesscontrol.go new file mode 100644 index 0000000000000..48d2f85f9a9d3 --- /dev/null +++ b/enterprise/coderd/dbauthz/accesscontrol.go @@ -0,0 +1,30 @@ +package dbauthz + +import ( + "context" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/database" + agpldbz "github.com/coder/coder/v2/coderd/database/dbauthz" +) + +type EnterpriseTemplateAccessControlStore struct{} + +func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) (agpldbz.TemplateAccessControl, error) { + return agpldbz.TemplateAccessControl{ + RequireActiveVersion: t.RequireActiveVersion, + }, nil +} + +func (EnterpriseTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts agpldbz.TemplateAccessControl) error { + err := store.UpdateTemplateAccessControlByID(ctx, database.UpdateTemplateAccessControlByIDParams{ + ID: id, + RequireActiveVersion: opts.RequireActiveVersion, + }) + if err != nil { + return xerrors.Errorf("update template access control: %w", err) + } + return nil +} From 8a4e0bf9de0ccb6d2e833a24731e0ac65b75820f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 07:02:02 +0000 Subject: [PATCH 25/30] pass atomic pointer to autobuild --- cli/server.go | 2 +- cli/templates.go | 3 ++- cli/templateversionarchive.go | 3 ++- cli/templateversions.go | 3 ++- coderd/autobuild/lifecycle_executor.go | 14 +++++++++++--- coderd/coderd.go | 13 ++++++++----- coderd/coderdtest/coderdtest.go | 6 ++++++ coderd/database/dbauthz/accesscontrol.go | 6 +++--- coderd/database/dbauthz/dbauthz.go | 5 +---- coderd/database/dbauthz/setup_test.go | 4 ++-- coderd/schedule/template.go | 4 ---- coderd/templates.go | 13 ++++++------- enterprise/coderd/dbauthz/accesscontrol.go | 4 ++-- enterprise/coderd/schedule/template.go | 4 +--- 14 files changed, 47 insertions(+), 37 deletions(-) diff --git a/cli/server.go b/cli/server.go index 9f33ced438f84..21a6254c57669 100644 --- a/cli/server.go +++ b/cli/server.go @@ -938,7 +938,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. autobuildTicker := time.NewTicker(vals.AutobuildPollInterval.Value()) defer autobuildTicker.Stop() autobuildExecutor := autobuild.NewExecutor( - ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, logger, autobuildTicker.C) + ctx, options.Database, options.Pubsub, coderAPI.TemplateScheduleStore, &coderAPI.Auditor, coderAPI.AccessControlStore, logger, autobuildTicker.C) autobuildExecutor.Run() hangDetectorTicker := time.NewTicker(vals.JobHangDetectorInterval.Value()) diff --git a/cli/templates.go b/cli/templates.go index d7cd02a467ef2..391ac7700870a 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -3,10 +3,11 @@ package cli import ( "time" - "github.com/coder/pretty" "github.com/google/uuid" "golang.org/x/xerrors" + "github.com/coder/pretty" + "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" diff --git a/cli/templateversionarchive.go b/cli/templateversionarchive.go index 9ffcb5f50aa6a..d334bdb83fb4b 100644 --- a/cli/templateversionarchive.go +++ b/cli/templateversionarchive.go @@ -6,9 +6,10 @@ import ( "strings" "time" - "github.com/coder/pretty" "golang.org/x/xerrors" + "github.com/coder/pretty" + "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" diff --git a/cli/templateversions.go b/cli/templateversions.go index cc3b31277a0d9..4ccba09b63ba8 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -5,10 +5,11 @@ import ( "strings" "time" - "github.com/coder/pretty" "github.com/google/uuid" "golang.org/x/xerrors" + "github.com/coder/pretty" + "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index bc12f5ba31a27..addcd35819ed0 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -32,6 +32,7 @@ type Executor struct { db database.Store ps pubsub.Pubsub templateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore] + accessControlStore *atomic.Pointer[dbauthz.AccessControlStore] auditor *atomic.Pointer[audit.Auditor] log slog.Logger tick <-chan time.Time @@ -46,7 +47,7 @@ type Stats struct { } // New returns a new wsactions executor. -func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], log slog.Logger, tick <-chan time.Time) *Executor { +func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], acs *atomic.Pointer[dbauthz.AccessControlStore], log slog.Logger, tick <-chan time.Time) *Executor { le := &Executor{ //nolint:gocritic // Autostart has a limited set of permissions. ctx: dbauthz.AsAutostart(ctx), @@ -56,6 +57,7 @@ func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, tss * tick: tick, log: log.Named("autobuild"), auditor: auditor, + accessControlStore: acs, } return le } @@ -159,6 +161,12 @@ func (e *Executor) runOnce(t time.Time) Stats { return nil } + template, err := tx.GetTemplateByID(e.ctx, ws.TemplateID) + if err != nil { + log.Warn(e.ctx, "get template by id", slog.Error(err)) + } + accessControl := (*(e.accessControlStore.Load())).GetTemplateAccessControl(template) + latestJob, err := tx.GetProvisionerJobByID(e.ctx, latestBuild.JobID) if err != nil { log.Warn(e.ctx, "get last provisioner job for workspace %q: %w", slog.Error(err)) @@ -179,7 +187,7 @@ func (e *Executor) runOnce(t time.Time) Stats { Reason(reason) log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition)) if nextTransition == database.WorkspaceTransitionStart && - useActiveVersion(templateSchedule, ws) { + useActiveVersion(accessControl, ws) { log.Debug(e.ctx, "autostarting with active version") builder = builder.ActiveVersion() } @@ -470,6 +478,6 @@ func auditBuild(ctx context.Context, log slog.Logger, auditor audit.Auditor, par }) } -func useActiveVersion(opts schedule.TemplateScheduleOptions, ws database.Workspace) bool { +func useActiveVersion(opts dbauthz.TemplateAccessControl, ws database.Workspace) bool { return opts.RequireActiveVersion || ws.AutomaticUpdates == database.AutomaticUpdatesAlways } diff --git a/coderd/coderd.go b/coderd/coderd.go index b733b8545cf86..f8730ac8d3a37 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -131,6 +131,7 @@ type Options struct { SetUserSiteRoles func(ctx context.Context, logger slog.Logger, tx database.Store, userID uuid.UUID, roles []string) error TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore] UserQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore] + AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore] // AppSecurityKey is the crypto key used to sign and encrypt tokens related to // workspace applications. It consists of both a signing and encryption key. AppSecurityKey workspaceapps.SecurityKey @@ -209,15 +210,17 @@ func New(options *Options) *API { options.Authorizer = rbac.NewCachingAuthorizer(options.PrometheusRegistry) } - acs := &atomic.Pointer[dbauthz.AccessControlStore]{} - var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} - acs.Store(&tacs) + if options.AccessControlStore == nil { + options.AccessControlStore = &atomic.Pointer[dbauthz.AccessControlStore]{} + var tacs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + options.AccessControlStore.Store(&tacs) + } options.Database = dbauthz.New( options.Database, options.Authorizer, options.Logger.Named("authz_querier"), - acs, + options.AccessControlStore, ) experiments := ReadExperiments( @@ -376,7 +379,7 @@ func New(options *Options) *API { Auditor: atomic.Pointer[audit.Auditor]{}, TemplateScheduleStore: options.TemplateScheduleStore, UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore, - AccessControlStore: acs, + AccessControlStore: options.AccessControlStore, Experiments: experiments, healthCheckGroup: &singleflight.Group[string, *healthcheck.Report]{}, Acquirer: provisionerdserver.NewAcquirer( diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 25827ec3f205e..e1efcdcdfad3a 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -259,6 +259,10 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can t.Cleanup(closeBatcher) } + var accessControlStore = &atomic.Pointer[dbauthz.AccessControlStore]{} + var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} + accessControlStore.Store(&acs) + var templateScheduleStore atomic.Pointer[schedule.TemplateScheduleStore] if options.TemplateScheduleStore == nil { options.TemplateScheduleStore = schedule.NewAGPLTemplateScheduleStore() @@ -278,6 +282,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can options.Pubsub, &templateScheduleStore, &auditor, + accessControlStore, *options.Logger, options.AutobuildTicker, ).WithStatsChannel(options.AutobuildStats) @@ -415,6 +420,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can Authorizer: options.Authorizer, Telemetry: telemetry.NewNoop(), TemplateScheduleStore: &templateScheduleStore, + AccessControlStore: accessControlStore, TLSCertificates: options.TLSCertificates, TrialGenerator: options.TrialGenerator, TailnetCoordinator: options.Coordinator, diff --git a/coderd/database/dbauthz/accesscontrol.go b/coderd/database/dbauthz/accesscontrol.go index 42a3b84a4bbd6..92417ff4114ba 100644 --- a/coderd/database/dbauthz/accesscontrol.go +++ b/coderd/database/dbauthz/accesscontrol.go @@ -12,7 +12,7 @@ import ( // that is used when determining whether an actor is authorized // to interact with an RBAC object. type AccessControlStore interface { - GetTemplateAccessControl(t database.Template) (TemplateAccessControl, error) + GetTemplateAccessControl(t database.Template) TemplateAccessControl SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts TemplateAccessControl) error } @@ -26,10 +26,10 @@ type AGPLTemplateAccessControlStore struct{} var _ AccessControlStore = AGPLTemplateAccessControlStore{} -func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) (TemplateAccessControl, error) { +func (AGPLTemplateAccessControlStore) GetTemplateAccessControl(database.Template) TemplateAccessControl { return TemplateAccessControl{ RequireActiveVersion: false, - }, nil + } } func (AGPLTemplateAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, TemplateAccessControl) error { diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a9bbeb2889c5e..796c68a5031f8 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2222,10 +2222,7 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW return xerrors.Errorf("get template by id: %w", err) } - accessControl, err := (*q.acs.Load()).GetTemplateAccessControl(t) - if err != nil { - return xerrors.Errorf("get template access control by id: %w", err) - } + accessControl := (*q.acs.Load()).GetTemplateAccessControl(t) // If the template requires the active version we need to check if // the user is a template admin. If they aren't and are attempting diff --git a/coderd/database/dbauthz/setup_test.go b/coderd/database/dbauthz/setup_test.go index 246355c920694..968b882f2d263 100644 --- a/coderd/database/dbauthz/setup_test.go +++ b/coderd/database/dbauthz/setup_test.go @@ -409,10 +409,10 @@ func accessControlStorePointer() *atomic.Pointer[dbauthz.AccessControlStore] { type fakeAccessControlStore struct{} -func (fakeAccessControlStore) GetTemplateAccessControl(t database.Template) (dbauthz.TemplateAccessControl, error) { +func (fakeAccessControlStore) GetTemplateAccessControl(t database.Template) dbauthz.TemplateAccessControl { return dbauthz.TemplateAccessControl{ RequireActiveVersion: t.RequireActiveVersion, - }, nil + } } func (fakeAccessControlStore) SetTemplateAccessControl(context.Context, database.Store, uuid.UUID, dbauthz.TemplateAccessControl) error { diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 0a7790c5d2d85..fc274d9a7d8ba 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -151,9 +151,6 @@ type TemplateScheduleOptions struct { // workspaces whose dormant_at field violates the new template time_til_dormant_autodelete // threshold. UpdateWorkspaceDormantAt bool `json:"update_workspace_dormant_at"` - // RequireActiveVersion requires that a starting a workspace uses the active - // version for a template. - RequireActiveVersion bool `json:"require_active_version"` } // TemplateScheduleStore provides an interface for retrieving template @@ -203,7 +200,6 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te FailureTTL: 0, TimeTilDormant: 0, TimeTilDormantAutoDelete: 0, - RequireActiveVersion: false, }, nil } diff --git a/coderd/templates.go b/coderd/templates.go index eb57521f159f8..cd1bc1b355997 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -347,11 +347,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return xerrors.Errorf("insert template: %s", err) } - dbTemplate, err = tx.GetTemplateByID(ctx, id) - if err != nil { - return xerrors.Errorf("get template by id: %s", err) - } - if createTemplate.RequireActiveVersion { err = (*api.AccessControlStore.Load()).SetTemplateAccessControl(ctx, tx, id, dbauthz.TemplateAccessControl{ RequireActiveVersion: createTemplate.RequireActiveVersion, @@ -361,6 +356,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } } + dbTemplate, err = tx.GetTemplateByID(ctx, id) + if err != nil { + return xerrors.Errorf("get template by id: %s", err) + } + dbTemplate, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, dbTemplate, schedule.TemplateScheduleOptions{ UserAutostartEnabled: allowUserAutostart, UserAutostopEnabled: allowUserAutostop, @@ -379,7 +379,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque FailureTTL: failureTTL, TimeTilDormant: dormantTTL, TimeTilDormantAutoDelete: dormantAutoDeletionTTL, - RequireActiveVersion: createTemplate.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("set template schedule options: %s", err) @@ -661,6 +660,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { if err != nil { return xerrors.Errorf("set template access control: %w", err) } + updated.RequireActiveVersion = req.RequireActiveVersion } defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond @@ -700,7 +700,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { TimeTilDormantAutoDelete: timeTilDormantAutoDelete, UpdateWorkspaceLastUsedAt: req.UpdateWorkspaceLastUsedAt, UpdateWorkspaceDormantAt: req.UpdateWorkspaceDormantAt, - RequireActiveVersion: req.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("set template schedule options: %w", err) diff --git a/enterprise/coderd/dbauthz/accesscontrol.go b/enterprise/coderd/dbauthz/accesscontrol.go index 48d2f85f9a9d3..454f416ab8736 100644 --- a/enterprise/coderd/dbauthz/accesscontrol.go +++ b/enterprise/coderd/dbauthz/accesscontrol.go @@ -12,10 +12,10 @@ import ( type EnterpriseTemplateAccessControlStore struct{} -func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) (agpldbz.TemplateAccessControl, error) { +func (EnterpriseTemplateAccessControlStore) GetTemplateAccessControl(t database.Template) agpldbz.TemplateAccessControl { return agpldbz.TemplateAccessControl{ RequireActiveVersion: t.RequireActiveVersion, - }, nil + } } func (EnterpriseTemplateAccessControlStore) SetTemplateAccessControl(ctx context.Context, store database.Store, id uuid.UUID, opts agpldbz.TemplateAccessControl) error { diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index d18a90054f173..26b0c78c6bfc9 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -92,7 +92,6 @@ func (s *EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.S FailureTTL: time.Duration(tpl.FailureTTL), TimeTilDormant: time.Duration(tpl.TimeTilDormant), TimeTilDormantAutoDelete: time.Duration(tpl.TimeTilDormantAutoDelete), - RequireActiveVersion: tpl.RequireActiveVersion, }, nil } @@ -117,8 +116,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S int64(opts.TimeTilDormant) == tpl.TimeTilDormant && int64(opts.TimeTilDormantAutoDelete) == tpl.TimeTilDormantAutoDelete && opts.UserAutostartEnabled == tpl.AllowUserAutostart && - opts.UserAutostopEnabled == tpl.AllowUserAutostop && - opts.RequireActiveVersion == tpl.RequireActiveVersion { + opts.UserAutostopEnabled == tpl.AllowUserAutostop { // Avoid updating the UpdatedAt timestamp if nothing will be changed. return tpl, nil } From 693d3a69d2044c6c43182f0418a862bdac20c8b9 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 07:02:59 +0000 Subject: [PATCH 26/30] make fmt --- coderd/coderdtest/coderdtest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index e1efcdcdfad3a..1cab73f519e8d 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -259,7 +259,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can t.Cleanup(closeBatcher) } - var accessControlStore = &atomic.Pointer[dbauthz.AccessControlStore]{} + accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{} var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{} accessControlStore.Store(&acs) From 3f4ec2e17418ccd26c362b2246498ed870b31182 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 07:12:30 +0000 Subject: [PATCH 27/30] hm --- coderd/templates.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/coderd/templates.go b/coderd/templates.go index cd1bc1b355997..1c2b6bde57cf9 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -648,19 +648,18 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("update template metadata: %w", err) } - updated, err = tx.GetTemplateByID(ctx, template.ID) - if err != nil { - return xerrors.Errorf("fetch updated template metadata: %w", err) - } - - if updated.RequireActiveVersion != req.RequireActiveVersion { + if template.RequireActiveVersion != req.RequireActiveVersion { err = (*api.AccessControlStore.Load()).SetTemplateAccessControl(ctx, tx, template.ID, dbauthz.TemplateAccessControl{ RequireActiveVersion: req.RequireActiveVersion, }) if err != nil { return xerrors.Errorf("set template access control: %w", err) } - updated.RequireActiveVersion = req.RequireActiveVersion + } + + updated, err = tx.GetTemplateByID(ctx, template.ID) + if err != nil { + return xerrors.Errorf("fetch updated template metadata: %w", err) } defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond From 9e00957f9b983e3ed91a4fe9935c7cd756213a1d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 21:11:25 +0000 Subject: [PATCH 28/30] nits --- coderd/coderdtest/coderdtest.go | 3 --- coderd/database/dbfake/dbfake.go | 3 +-- coderd/templates.go | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 1cab73f519e8d..6310a48d04e2e 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -931,9 +931,6 @@ func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID require.NoError(t, err, "fetch workspace template") req := codersdk.CreateWorkspaceBuildRequest{ - // TODO (JonA): I get this is for convenience but we should probably - // change this. Tripped me up why my test was passing when it shouldn't - // have. TemplateVersionID: template.ActiveVersionID, Transition: codersdk.WorkspaceTransition(to), } diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index fe6f5a43c71b3..2c04532547f13 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -5654,8 +5654,7 @@ func (q *FakeQuerier) UpdateTemplateAccessControlByID(_ context.Context, arg dat if tpl.ID != arg.ID { continue } - tpl.RequireActiveVersion = arg.RequireActiveVersion - q.templates[idx] = tpl + q.templates[idx].RequireActiveVersion = arg.RequireActiveVersion return nil } diff --git a/coderd/templates.go b/coderd/templates.go index 1c2b6bde57cf9..c1f3bc97a01c3 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -677,8 +677,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { inactivityTTL != time.Duration(template.TimeTilDormant) || timeTilDormantAutoDelete != time.Duration(template.TimeTilDormantAutoDelete) || req.AllowUserAutostart != template.AllowUserAutostart || - req.AllowUserAutostop != template.AllowUserAutostop || - req.RequireActiveVersion != template.RequireActiveVersion { + req.AllowUserAutostop != template.AllowUserAutostop { updated, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, updated, schedule.TemplateScheduleOptions{ // Some of these values are enterprise-only, but the // TemplateScheduleStore will handle avoiding setting them if From a497b714708a0e2b76737f16420dd8a7b2eacdba Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 21:33:39 +0000 Subject: [PATCH 29/30] fix duplicate import error --- cli/templates.go | 2 -- cli/templateversionarchive.go | 2 -- cli/templateversions.go | 2 -- 3 files changed, 6 deletions(-) diff --git a/cli/templates.go b/cli/templates.go index 2dec46d226b5c..4f5b4f8f36d0b 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -6,8 +6,6 @@ import ( "github.com/google/uuid" "golang.org/x/xerrors" - "github.com/coder/pretty" - "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" diff --git a/cli/templateversionarchive.go b/cli/templateversionarchive.go index 740de869ff9c2..63c9d8a3de212 100644 --- a/cli/templateversionarchive.go +++ b/cli/templateversionarchive.go @@ -8,8 +8,6 @@ import ( "golang.org/x/xerrors" - "github.com/coder/pretty" - "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" diff --git a/cli/templateversions.go b/cli/templateversions.go index 5b99a46310996..a27d6a6d65af3 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -8,8 +8,6 @@ import ( "github.com/google/uuid" "golang.org/x/xerrors" - "github.com/coder/pretty" - "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" From f336ab1655b3c05783e2464756a5e5c9ee8e5031 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 18 Oct 2023 21:38:05 +0000 Subject: [PATCH 30/30] unnest test --- coderd/autobuild/lifecycle_executor_test.go | 88 ++++++++++----------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 34e6e286fdda9..81bbf80603898 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -783,58 +783,54 @@ func TestExecutorAutostopTemplateDisabled(t *testing.T) { assert.Len(t, stats.Transitions, 0) } +// Test that an AGPL AccessControlStore properly disables +// functionality. func TestExecutorRequireActiveVersion(t *testing.T) { t.Parallel() - // Test that an AGPL TemplateScheduleStore properly disables - // functionality. - t.Run("Unused", func(t *testing.T) { - t.Parallel() - - var ( - sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *") - ticker = make(chan time.Time) - statCh = make(chan autobuild.Stats) + var ( + sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *") + ticker = make(chan time.Time) + statCh = make(chan autobuild.Stats) - ownerClient = coderdtest.New(t, &coderdtest.Options{ - AutobuildTicker: ticker, - IncludeProvisionerDaemon: true, - AutobuildStats: statCh, - TemplateScheduleStore: schedule.NewAGPLTemplateScheduleStore(), - }) - ) - owner := coderdtest.CreateFirstUser(t, ownerClient) - - // Create an active and inactive template version. We'll - // build a regular member's workspace using a non-active - // template version and assert that the field is not abided - // since there is no enterprise license. - activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, activeVersion.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.RequireActiveVersion = true - ctr.VersionID = activeVersion.ID - }) - inactiveVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { - ctvr.TemplateID = template.ID - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) - memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - ws := coderdtest.CreateWorkspace(t, memberClient, owner.OrganizationID, uuid.Nil, func(cwr *codersdk.CreateWorkspaceRequest) { - cwr.TemplateVersionID = inactiveVersion.ID - cwr.AutostartSchedule = ptr.Ref(sched.String()) - }) - _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, ownerClient, ws.LatestBuild.ID) - ws = coderdtest.MustTransitionWorkspace(t, memberClient, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { - req.TemplateVersionID = inactiveVersion.ID + ownerClient = coderdtest.New(t, &coderdtest.Options{ + AutobuildTicker: ticker, + IncludeProvisionerDaemon: true, + AutobuildStats: statCh, + TemplateScheduleStore: schedule.NewAGPLTemplateScheduleStore(), }) - require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) - ticker <- sched.Next(ws.LatestBuild.CreatedAt) - stats := <-statCh - require.Len(t, stats.Transitions, 1) - - ws = coderdtest.MustWorkspace(t, memberClient, ws.ID) - require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) + ) + owner := coderdtest.CreateFirstUser(t, ownerClient) + + // Create an active and inactive template version. We'll + // build a regular member's workspace using a non-active + // template version and assert that the field is not abided + // since there is no enterprise license. + activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, activeVersion.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.RequireActiveVersion = true + ctr.VersionID = activeVersion.ID + }) + inactiveVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = template.ID }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) + memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + ws := coderdtest.CreateWorkspace(t, memberClient, owner.OrganizationID, uuid.Nil, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.TemplateVersionID = inactiveVersion.ID + cwr.AutostartSchedule = ptr.Ref(sched.String()) + }) + _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, ownerClient, ws.LatestBuild.ID) + ws = coderdtest.MustTransitionWorkspace(t, memberClient, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { + req.TemplateVersionID = inactiveVersion.ID + }) + require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) + ticker <- sched.Next(ws.LatestBuild.CreatedAt) + stats := <-statCh + require.Len(t, stats.Transitions, 1) + + ws = coderdtest.MustWorkspace(t, memberClient, ws.ID) + require.Equal(t, inactiveVersion.ID, ws.LatestBuild.TemplateVersionID) } // TestExecutorFailedWorkspace test AGPL functionality which mainly