From 359d188779df86ee97ccfb36bb0c167ea28f16ea Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Mon, 22 Jan 2024 10:21:44 +0000 Subject: [PATCH 1/7] feat: add template activity_bump property --- cli/templateedit.go | 23 ++++++--- cli/templateedit_test.go | 1 + .../coder_templates_edit_--help.golden | 5 ++ coderd/agentapi/activitybump_test.go | 16 ++++++ coderd/apidoc/docs.go | 7 +++ coderd/apidoc/swagger.json | 7 +++ coderd/database/dbmem/dbmem.go | 9 ++-- coderd/database/dump.sql | 4 +- ...6_template_activity_bump_duration.down.sql | 19 +++++++ ...186_template_activity_bump_duration.up.sql | 19 +++++++ coderd/database/modelqueries.go | 1 + coderd/database/models.go | 6 ++- coderd/database/queries.sql.go | 51 +++++++++++-------- coderd/database/queries/activitybump.sql | 20 +++++--- coderd/database/queries/templates.sql | 17 ++++--- coderd/schedule/template.go | 15 ++++-- coderd/templates.go | 23 +++++++-- coderd/templates_test.go | 16 +++++- codersdk/organizations.go | 4 ++ codersdk/templates.go | 5 ++ docs/admin/audit-logs.md | 28 +++++----- docs/api/schemas.md | 4 ++ docs/api/templates.md | 7 +++ docs/cli/templates_edit.md | 8 +++ enterprise/audit/table.go | 1 + enterprise/coderd/schedule/template.go | 3 ++ site/src/api/typesGenerated.ts | 3 ++ 27 files changed, 250 insertions(+), 72 deletions(-) create mode 100644 coderd/database/migrations/000186_template_activity_bump_duration.down.sql create mode 100644 coderd/database/migrations/000186_template_activity_bump_duration.up.sql diff --git a/cli/templateedit.go b/cli/templateedit.go index 6df67f10101d8..c7ac3b430b897 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -23,6 +23,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { description string icon string defaultTTL time.Duration + activityBump time.Duration maxTTL time.Duration autostopRequirementDaysOfWeek []string autostopRequirementWeeks int64 @@ -108,6 +109,10 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { defaultTTL = time.Duration(template.DefaultTTLMillis) * time.Millisecond } + if !userSetOption(inv, "activity-bump") { + activityBump = time.Duration(template.ActivityBumpMillis) * time.Millisecond + } + if !userSetOption(inv, "allow-user-autostop") { allowUserAutostop = template.AllowUserAutostop } @@ -168,12 +173,13 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { } req := codersdk.UpdateTemplateMeta{ - Name: name, - DisplayName: displayName, - Description: description, - Icon: icon, - DefaultTTLMillis: defaultTTL.Milliseconds(), - MaxTTLMillis: maxTTL.Milliseconds(), + Name: name, + DisplayName: displayName, + Description: description, + Icon: icon, + DefaultTTLMillis: defaultTTL.Milliseconds(), + ActivityBumpMillis: activityBump.Milliseconds(), + MaxTTLMillis: maxTTL.Milliseconds(), AutostopRequirement: &codersdk.TemplateAutostopRequirement{ DaysOfWeek: autostopRequirementDaysOfWeek, Weeks: autostopRequirementWeeks, @@ -233,6 +239,11 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { Description: "Edit the template default time before shutdown - workspaces created from this template default to this value. Maps to \"Default autostop\" in the UI.", Value: clibase.DurationOf(&defaultTTL), }, + { + Flag: "activity-bump", + Description: "Edit the template activity bump - workspaces created from this template will have their shutdown time bumped by this value when activity is detected. Maps to \"Activity bump\" in the UI.", + Value: clibase.DurationOf(&activityBump), + }, { Flag: "max-ttl", Description: "Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting, regardless of user activity. This is an enterprise-only feature. Maps to \"Max lifetime\" in the UI.", diff --git a/cli/templateedit_test.go b/cli/templateedit_test.go index 1c9c78b4e4de7..d7cfb01e9be1a 100644 --- a/cli/templateedit_test.go +++ b/cli/templateedit_test.go @@ -93,6 +93,7 @@ func TestTemplateEdit(t *testing.T) { "--description", template.Description, "--icon", template.Icon, "--default-ttl", (time.Duration(template.DefaultTTLMillis) * time.Millisecond).String(), + "--activity-bump", (time.Duration(template.ActivityBumpMillis) * time.Millisecond).String(), "--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(template.AllowUserCancelWorkspaceJobs), } inv, root := clitest.New(t, cmdArgs...) diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden index 52ef47d363326..1f6ce3c4d405e 100644 --- a/cli/testdata/coder_templates_edit_--help.golden +++ b/cli/testdata/coder_templates_edit_--help.golden @@ -6,6 +6,11 @@ USAGE: Edit the metadata of a template by name. OPTIONS: + --activity-bump duration + Edit the template activity bump - workspaces created from this + template will have their shutdown time bumped by this value when + activity is detected. Maps to "Activity bump" in the UI. + --allow-user-autostart bool (default: true) Allow users to configure autostart for workspaces on this template. This can only be disabled in enterprise. diff --git a/coderd/agentapi/activitybump_test.go b/coderd/agentapi/activitybump_test.go index 340be4b61d04c..200795e47dd56 100644 --- a/coderd/agentapi/activitybump_test.go +++ b/coderd/agentapi/activitybump_test.go @@ -41,6 +41,7 @@ func Test_ActivityBumpWorkspace(t *testing.T) { maxDeadlineOffset *time.Duration workspaceTTL time.Duration templateTTL time.Duration + templateActivityBump time.Duration templateDisallowsUserAutostop bool expectedBump time.Duration // If the tests get queued, we need to be able to set the next autostart @@ -137,6 +138,16 @@ func Test_ActivityBumpWorkspace(t *testing.T) { expectedBump: 10*time.Hour + (time.Minute * 30), nextAutostart: func(now time.Time) time.Time { return now.Add(time.Minute * 30) }, }, + { + // Custom activity bump duration specified on the template. + name: "TemplateCustomActivityBump", + transition: database.WorkspaceTransitionStart, + jobCompletedAt: sql.NullTime{Valid: true, Time: dbtime.Now().Add(-30 * time.Minute)}, + buildDeadlineOffset: ptr.Ref(-30 * time.Minute), + workspaceTTL: 8 * time.Hour, + templateActivityBump: 5 * time.Hour, // instead of default 1h + expectedBump: 5 * time.Hour, + }, } { tt := tt for _, tz := range timezones { @@ -186,11 +197,16 @@ func Test_ActivityBumpWorkspace(t *testing.T) { buildID = uuid.New() ) + activityBump := 1 * time.Hour + if tt.templateActivityBump != 0 { + activityBump = tt.templateActivityBump + } require.NoError(t, db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ ID: template.ID, UpdatedAt: dbtime.Now(), AllowUserAutostop: !tt.templateDisallowsUserAutostop, DefaultTTL: int64(tt.templateTTL), + ActivityBump: int64(activityBump), }), "unexpected error updating template schedule") var buildNumber int32 = 1 diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 06ed3e19dfe1c..9c27bd21374b8 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8343,6 +8343,10 @@ const docTemplate = `{ "template_version_id" ], "properties": { + "activity_bump_ms": { + "description": "ActivityBumpMillis allows optionally specifying the activity bump\nduration for all workspaces created from this template. Defaults to 1h\nbut can be set to 0 to disable activity bumping.", + "type": "integer" + }, "allow_user_autostart": { "description": "AllowUserAutostart allows users to set a schedule for autostarting their\nworkspace. By default this is true. This can only be disabled when using\nan enterprise license.", "type": "boolean" @@ -10705,6 +10709,9 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, + "activity_bump_ms": { + "type": "integer" + }, "allow_user_autostart": { "description": "AllowUserAutostart and AllowUserAutostop are enterprise-only. Their\nvalues are only used if your license is entitled to use the advanced\ntemplate scheduling feature.", "type": "boolean" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8982d4a4a781f..af54c766cd4ca 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7430,6 +7430,10 @@ "type": "object", "required": ["name", "template_version_id"], "properties": { + "activity_bump_ms": { + "description": "ActivityBumpMillis allows optionally specifying the activity bump\nduration for all workspaces created from this template. Defaults to 1h\nbut can be set to 0 to disable activity bumping.", + "type": "integer" + }, "allow_user_autostart": { "description": "AllowUserAutostart allows users to set a schedule for autostarting their\nworkspace. By default this is true. This can only be disabled when using\nan enterprise license.", "type": "boolean" @@ -9661,6 +9665,9 @@ "type": "string", "format": "uuid" }, + "activity_bump_ms": { + "type": "integer" + }, "allow_user_autostart": { "description": "AllowUserAutostart and AllowUserAutostop are enterprise-only. Their\nvalues are only used if your license is entitled to use the advanced\ntemplate scheduling feature.", "type": "boolean" diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 0800fb5dd0a54..9ca57867bf69a 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -839,10 +839,11 @@ func (q *FakeQuerier) ActivityBumpWorkspace(ctx context.Context, arg database.Ac if err != nil { return err } + activityBump := time.Duration(template.ActivityBump) var ttlDur time.Duration - if now.Add(time.Hour).After(arg.NextAutostart) && arg.NextAutostart.After(now) { - // Extend to TTL + if now.Add(activityBump).After(arg.NextAutostart) && arg.NextAutostart.After(now) { + // Extend to TTL (NOT activity bump) add := arg.NextAutostart.Sub(now) if workspace.Ttl.Valid && template.AllowUserAutostop { add += time.Duration(workspace.Ttl.Int64) @@ -851,7 +852,8 @@ func (q *FakeQuerier) ActivityBumpWorkspace(ctx context.Context, arg database.Ac } ttlDur = add } else { - ttlDur = time.Hour + // Otherwise, default to regular activity bump duration. + ttlDur = activityBump } // Only bump if 5% of the deadline has passed. @@ -6423,6 +6425,7 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database tpl.AllowUserAutostop = arg.AllowUserAutostop tpl.UpdatedAt = dbtime.Now() tpl.DefaultTTL = arg.DefaultTTL + tpl.ActivityBump = arg.ActivityBump tpl.UseMaxTtl = arg.UseMaxTtl tpl.MaxTTL = arg.MaxTTL tpl.AutostopRequirementDaysOfWeek = arg.AutostopRequirementDaysOfWeek diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f9d1e4311b2b2..08f6e0baaf9b2 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -840,7 +840,8 @@ CREATE TABLE templates ( autostart_block_days_of_week smallint DEFAULT 0 NOT NULL, require_active_version boolean DEFAULT false NOT NULL, deprecated text DEFAULT ''::text NOT NULL, - use_max_ttl boolean DEFAULT false NOT NULL + use_max_ttl boolean DEFAULT false NOT NULL, + activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -890,6 +891,7 @@ CREATE VIEW template_with_users AS templates.require_active_version, templates.deprecated, templates.use_max_ttl, + templates.activity_bump, 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/000186_template_activity_bump_duration.down.sql b/coderd/database/migrations/000186_template_activity_bump_duration.down.sql new file mode 100644 index 0000000000000..ad47e6fea6b17 --- /dev/null +++ b/coderd/database/migrations/000186_template_activity_bump_duration.down.sql @@ -0,0 +1,19 @@ +DROP VIEW template_with_users; + +ALTER TABLE templates DROP COLUMN activity_bump; + +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.'; diff --git a/coderd/database/migrations/000186_template_activity_bump_duration.up.sql b/coderd/database/migrations/000186_template_activity_bump_duration.up.sql new file mode 100644 index 0000000000000..f08280b7c2212 --- /dev/null +++ b/coderd/database/migrations/000186_template_activity_bump_duration.up.sql @@ -0,0 +1,19 @@ +ALTER TABLE templates ADD COLUMN activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL; -- 1 hour + +DROP VIEW template_with_users; + +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.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 7443f1231a848..a8fd522f8cf13 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -90,6 +90,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.RequireActiveVersion, &i.Deprecated, &i.UseMaxTtl, + &i.ActivityBump, &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { diff --git a/coderd/database/models.go b/coderd/database/models.go index 5308f88b35a79..3bb64666a5eb4 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1994,6 +1994,7 @@ type Template struct { RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` Deprecated string `db:"deprecated" json:"deprecated"` UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"` + ActivityBump int64 `db:"activity_bump" json:"activity_bump"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` } @@ -2034,8 +2035,9 @@ type TemplateTable struct { AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"` RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"` // If set to a non empty string, the template will no longer be able to be used. The message will be displayed to the user. - Deprecated string `db:"deprecated" json:"deprecated"` - UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"` + Deprecated string `db:"deprecated" json:"deprecated"` + UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"` + ActivityBump int64 `db:"activity_bump" json:"activity_bump"` } // 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 4c4bfc6012e7b..07a0b53463b81 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -26,11 +26,15 @@ WITH latest AS ( ( CASE -- If the extension would push us over the next_autostart - -- interval, then extend the deadline by the full ttl from - -- the autostart time. This will essentially be as if the - -- workspace auto started at the given time and the original - -- TTL was applied. - WHEN NOW() + ('60 minutes')::interval > $1 :: timestamptz + -- interval, then extend the deadline by the full TTL (NOT + -- activity bump) from the autostart time. This will essentially + -- be as if the workspace auto started at the given time and the + -- original TTL was applied. + -- + -- Sadly we can't define ` + "`" + `activity_bump_interval` + "`" + ` above since + -- it won't be available for this CASE statement, so we have to + -- copy the cast twice. + WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > $1 :: timestamptz -- If the autostart is behind now(), then the -- autostart schedule is either the 0 time and not provided, -- or it was the autostart in the past, which is no longer @@ -38,16 +42,16 @@ WITH latest AS ( -- that is a mistake by the caller. AND $1 > NOW() THEN - -- Extend to the autostart, then add the TTL + -- Extend to the autostart, then add the activity bump (($1 :: timestamptz) - NOW()) + CASE WHEN templates.allow_user_autostop THEN (workspaces.ttl / 1000 / 1000 / 1000 || ' seconds')::interval ELSE (templates.default_ttl / 1000 / 1000 / 1000 || ' seconds')::interval END - -- Default to 60 minutes. + -- Default to the activity bump duration. ELSE - ('60 minutes')::interval + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval END ) AS ttl_interval FROM workspace_builds @@ -5651,7 +5655,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, 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, deprecated, use_max_ttl, 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, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, activity_bump, created_by_avatar_url, created_by_username FROM template_with_users WHERE @@ -5692,6 +5696,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.RequireActiveVersion, &i.Deprecated, &i.UseMaxTtl, + &i.ActivityBump, &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -5700,7 +5705,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, 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, deprecated, use_max_ttl, 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, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, activity_bump, created_by_avatar_url, created_by_username FROM template_with_users AS templates WHERE @@ -5749,6 +5754,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.RequireActiveVersion, &i.Deprecated, &i.UseMaxTtl, + &i.ActivityBump, &i.CreatedByAvatarURL, &i.CreatedByUsername, ) @@ -5756,7 +5762,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many -SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, 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, deprecated, use_max_ttl, 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, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, activity_bump, created_by_avatar_url, created_by_username FROM template_with_users AS templates ORDER BY (name, id) ASC ` @@ -5798,6 +5804,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.RequireActiveVersion, &i.Deprecated, &i.UseMaxTtl, + &i.ActivityBump, &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -5816,7 +5823,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, 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, deprecated, use_max_ttl, 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, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, activity_bump, created_by_avatar_url, created_by_username FROM template_with_users AS templates WHERE @@ -5908,6 +5915,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.RequireActiveVersion, &i.Deprecated, &i.UseMaxTtl, + &i.ActivityBump, &i.CreatedByAvatarURL, &i.CreatedByUsername, ); err != nil { @@ -6115,14 +6123,15 @@ SET allow_user_autostart = $3, allow_user_autostop = $4, default_ttl = $5, - use_max_ttl = $6, - max_ttl = $7, - autostop_requirement_days_of_week = $8, - autostop_requirement_weeks = $9, - autostart_block_days_of_week = $10, - failure_ttl = $11, - time_til_dormant = $12, - time_til_dormant_autodelete = $13 + activity_bump = $6, + use_max_ttl = $7, + max_ttl = $8, + autostop_requirement_days_of_week = $9, + autostop_requirement_weeks = $10, + autostart_block_days_of_week = $11, + failure_ttl = $12, + time_til_dormant = $13, + time_til_dormant_autodelete = $14 WHERE id = $1 ` @@ -6133,6 +6142,7 @@ type UpdateTemplateScheduleByIDParams struct { AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"` AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"` DefaultTTL int64 `db:"default_ttl" json:"default_ttl"` + ActivityBump int64 `db:"activity_bump" json:"activity_bump"` UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"` MaxTTL int64 `db:"max_ttl" json:"max_ttl"` AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"` @@ -6150,6 +6160,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT arg.AllowUserAutostart, arg.AllowUserAutostop, arg.DefaultTTL, + arg.ActivityBump, arg.UseMaxTtl, arg.MaxTTL, arg.AutostopRequirementDaysOfWeek, diff --git a/coderd/database/queries/activitybump.sql b/coderd/database/queries/activitybump.sql index f35628c686369..e729543b79d77 100644 --- a/coderd/database/queries/activitybump.sql +++ b/coderd/database/queries/activitybump.sql @@ -15,11 +15,15 @@ WITH latest AS ( ( CASE -- If the extension would push us over the next_autostart - -- interval, then extend the deadline by the full ttl from - -- the autostart time. This will essentially be as if the - -- workspace auto started at the given time and the original - -- TTL was applied. - WHEN NOW() + ('60 minutes')::interval > @next_autostart :: timestamptz + -- interval, then extend the deadline by the full TTL (NOT + -- activity bump) from the autostart time. This will essentially + -- be as if the workspace auto started at the given time and the + -- original TTL was applied. + -- + -- Sadly we can't define `activity_bump_interval` above since + -- it won't be available for this CASE statement, so we have to + -- copy the cast twice. + WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > @next_autostart :: timestamptz -- If the autostart is behind now(), then the -- autostart schedule is either the 0 time and not provided, -- or it was the autostart in the past, which is no longer @@ -27,16 +31,16 @@ WITH latest AS ( -- that is a mistake by the caller. AND @next_autostart > NOW() THEN - -- Extend to the autostart, then add the TTL + -- Extend to the autostart, then add the activity bump ((@next_autostart :: timestamptz) - NOW()) + CASE WHEN templates.allow_user_autostop THEN (workspaces.ttl / 1000 / 1000 / 1000 || ' seconds')::interval ELSE (templates.default_ttl / 1000 / 1000 / 1000 || ' seconds')::interval END - -- Default to 60 minutes. + -- Default to the activity bump duration. ELSE - ('60 minutes')::interval + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval END ) AS ttl_interval FROM workspace_builds diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index ca031bb0bd839..ef540e37eb457 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -129,14 +129,15 @@ SET allow_user_autostart = $3, allow_user_autostop = $4, default_ttl = $5, - use_max_ttl = $6, - max_ttl = $7, - autostop_requirement_days_of_week = $8, - autostop_requirement_weeks = $9, - autostart_block_days_of_week = $10, - failure_ttl = $11, - time_til_dormant = $12, - time_til_dormant_autodelete = $13 + activity_bump = $6, + use_max_ttl = $7, + max_ttl = $8, + autostop_requirement_days_of_week = $9, + autostop_requirement_weeks = $10, + autostart_block_days_of_week = $11, + failure_ttl = $12, + time_til_dormant = $13, + time_til_dormant_autodelete = $14 WHERE id = $1 ; diff --git a/coderd/schedule/template.go b/coderd/schedule/template.go index 4872b89ef9d83..b4fa3efd04d12 100644 --- a/coderd/schedule/template.go +++ b/coderd/schedule/template.go @@ -117,7 +117,10 @@ type TemplateScheduleOptions struct { UserAutostartEnabled bool `json:"user_autostart_enabled"` UserAutostopEnabled bool `json:"user_autostop_enabled"` DefaultTTL time.Duration `json:"default_ttl"` - MaxTTL time.Duration `json:"max_ttl"` + // ActivityBump dictates the duration to bump the workspace's deadline by if + // Coder detects activity from the user. A value of 0 means no bumping. + ActivityBump time.Duration `json:"activity_bump"` + MaxTTL time.Duration `json:"max_ttl"` // UseMaxTTL dictates whether the max_ttl should be used instead of // autostop_requirement for this template. This is governed by the template // and licensing. @@ -181,6 +184,7 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te UserAutostartEnabled: true, UserAutostopEnabled: true, DefaultTTL: time.Duration(tpl.DefaultTTL), + ActivityBump: time.Duration(tpl.ActivityBump), // Disregard the values in the database, since AutostopRequirement, // FailureTTL, TimeTilDormant, and TimeTilDormantAutoDelete are enterprise features. UseMaxTTL: false, @@ -205,7 +209,7 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp ctx, span := tracing.StartSpan(ctx) defer span.End() - if int64(opts.DefaultTTL) == tpl.DefaultTTL { + if int64(opts.DefaultTTL) == tpl.DefaultTTL && int64(opts.ActivityBump) == tpl.ActivityBump { // Avoid updating the UpdatedAt timestamp if nothing will be changed. return tpl, nil } @@ -213,9 +217,10 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp var template database.Template err := db.InTx(func(db database.Store) error { err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{ - ID: tpl.ID, - UpdatedAt: dbtime.Now(), - DefaultTTL: int64(opts.DefaultTTL), + ID: tpl.ID, + UpdatedAt: dbtime.Now(), + DefaultTTL: int64(opts.DefaultTTL), + ActivityBump: int64(opts.ActivityBump), // Don't allow changing these settings, but keep the value in the DB (to // avoid clearing settings if the license has an issue). UseMaxTtl: tpl.UseMaxTtl, diff --git a/coderd/templates.go b/coderd/templates.go index 78f918fe18180..8ad62a695a93f 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -224,6 +224,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque var ( defaultTTL time.Duration + activityBump = time.Hour // default maxTTL time.Duration autostopRequirementDaysOfWeek []string autostartRequirementDaysOfWeek []string @@ -235,6 +236,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque if createTemplate.DefaultTTLMillis != nil { defaultTTL = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond } + if createTemplate.ActivityBumpMillis != nil { + activityBump = time.Duration(*createTemplate.ActivityBumpMillis) * time.Millisecond + } if createTemplate.AutostopRequirement != nil { autostopRequirementDaysOfWeek = createTemplate.AutostopRequirement.DaysOfWeek autostopRequirementWeeks = createTemplate.AutostopRequirement.Weeks @@ -263,6 +267,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque if defaultTTL < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) } + if activityBump < 0 { + validErrs = append(validErrs, codersdk.ValidationError{Field: "activity_bump_ms", Detail: "Must be a positive integer."}) + } if maxTTL < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."}) } @@ -313,7 +320,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque var ( dbTemplate database.Template - template codersdk.Template allowUserCancelWorkspaceJobs = ptr.NilToDefault(createTemplate.AllowUserCancelWorkspaceJobs, false) allowUserAutostart = ptr.NilToDefault(createTemplate.AllowUserAutostart, true) @@ -368,6 +374,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque UserAutostopEnabled: allowUserAutostop, UseMaxTTL: maxTTL > 0, DefaultTTL: defaultTTL, + ActivityBump: activityBump, MaxTTL: maxTTL, // Some of these values are enterprise-only, but the // TemplateScheduleStore will handle avoiding setting them if @@ -409,7 +416,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } templateVersionAudit.New = newTemplateVersion - template = api.convertTemplate(dbTemplate) return nil }, nil) if err != nil { @@ -425,7 +431,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque TemplateVersions: []telemetry.TemplateVersion{telemetry.ConvertTemplateVersion(templateVersion)}, }) - httpapi.Write(ctx, rw, http.StatusCreated, template) + httpapi.Write(ctx, rw, http.StatusCreated, api.convertTemplate(dbTemplate)) } // @Summary Get templates by organization @@ -565,6 +571,9 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { if req.DefaultTTLMillis < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) } + if req.ActivityBumpMillis < 0 { + validErrs = append(validErrs, codersdk.ValidationError{Field: "activity_bump_ms", Detail: "Must be a positive integer."}) + } if req.MaxTTLMillis < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."}) } @@ -648,6 +657,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { req.AllowUserAutostop == template.AllowUserAutostop && req.AllowUserCancelWorkspaceJobs == template.AllowUserCancelWorkspaceJobs && req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() && + req.ActivityBumpMillis == time.Duration(template.ActivityBump).Milliseconds() && useMaxTTL == scheduleOpts.UseMaxTTL && req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() && autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek && @@ -703,12 +713,14 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond + activityBump := time.Duration(req.ActivityBumpMillis) * time.Millisecond maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond inactivityTTL := time.Duration(req.TimeTilDormantMillis) * time.Millisecond timeTilDormantAutoDelete := time.Duration(req.TimeTilDormantAutoDeleteMillis) * time.Millisecond if defaultTTL != time.Duration(template.DefaultTTL) || + activityBump != time.Duration(template.ActivityBump) || useMaxTTL != scheduleOpts.UseMaxTTL || maxTTL != time.Duration(template.MaxTTL) || autostopRequirementDaysOfWeekParsed != scheduleOpts.AutostopRequirement.DaysOfWeek || @@ -726,6 +738,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { UserAutostartEnabled: req.AllowUserAutostart, UserAutostopEnabled: req.AllowUserAutostop, DefaultTTL: defaultTTL, + ActivityBump: activityBump, UseMaxTTL: useMaxTTL, MaxTTL: maxTTL, AutostopRequirement: schedule.TemplateAutostopRequirement{ @@ -861,7 +874,7 @@ func (api *API) convertTemplate( autostopRequirementWeeks = 1 } - return codersdk.Template{ + x := codersdk.Template{ ID: template.ID, CreatedAt: template.CreatedAt, UpdatedAt: template.UpdatedAt, @@ -875,6 +888,7 @@ func (api *API) convertTemplate( Description: template.Description, Icon: template.Icon, DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(), + ActivityBumpMillis: time.Duration(template.ActivityBump).Milliseconds(), UseMaxTTL: template.UseMaxTtl, MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(), CreatedByID: template.CreatedBy, @@ -897,4 +911,5 @@ func (api *API) convertTemplate( Deprecated: templateAccessControl.IsDeprecated(), DeprecationMessage: templateAccessControl.Deprecated, } + return x } diff --git a/coderd/templates_test.go b/coderd/templates_test.go index fc3e8d07c3c97..86701f335e83d 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -57,7 +57,10 @@ func TestPostTemplateByOrganization(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil) - expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.ActivityBumpMillis = ptr.Ref((3 * time.Hour).Milliseconds()) + }) + assert.Equal(t, (3 * time.Hour).Milliseconds(), expected.ActivityBumpMillis) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -67,6 +70,7 @@ func TestPostTemplateByOrganization(t *testing.T) { assert.Equal(t, expected.Name, got.Name) assert.Equal(t, expected.Description, got.Description) + assert.Equal(t, expected.ActivityBumpMillis, got.ActivityBumpMillis) require.Len(t, auditor.AuditLogs(), 3) assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action) @@ -268,6 +272,7 @@ func TestPostTemplateByOrganization(t *testing.T) { AllowUserAutostart: options.UserAutostartEnabled, AllowUserAutostop: options.UserAutostopEnabled, DefaultTTL: int64(options.DefaultTTL), + ActivityBump: int64(options.ActivityBump), UseMaxTtl: options.UseMaxTTL, MaxTTL: int64(options.MaxTTL), AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek), @@ -320,6 +325,7 @@ func TestPostTemplateByOrganization(t *testing.T) { AllowUserAutostart: options.UserAutostartEnabled, AllowUserAutostop: options.UserAutostopEnabled, DefaultTTL: int64(options.DefaultTTL), + ActivityBump: int64(options.ActivityBump), UseMaxTtl: options.UseMaxTTL, MaxTTL: int64(options.MaxTTL), AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek), @@ -508,6 +514,7 @@ func TestPatchTemplateMeta(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) + assert.Equal(t, (1 * time.Hour).Milliseconds(), template.ActivityBumpMillis) req := codersdk.UpdateTemplateMeta{ Name: "new-template-name", @@ -515,6 +522,7 @@ func TestPatchTemplateMeta(t *testing.T) { Description: "lorem ipsum dolor sit amet et cetera", Icon: "/icon/new-icon.png", DefaultTTLMillis: 12 * time.Hour.Milliseconds(), + ActivityBumpMillis: 3 * time.Hour.Milliseconds(), AllowUserCancelWorkspaceJobs: false, } // It is unfortunate we need to sleep, but the test can fail if the @@ -532,6 +540,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, req.Description, updated.Description) assert.Equal(t, req.Icon, updated.Icon) assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis) + assert.Equal(t, req.ActivityBumpMillis, updated.ActivityBumpMillis) assert.False(t, req.AllowUserCancelWorkspaceJobs) // Extra paranoid: did it _really_ happen? @@ -543,6 +552,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, req.Description, updated.Description) assert.Equal(t, req.Icon, updated.Icon) assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis) + assert.Equal(t, req.ActivityBumpMillis, updated.ActivityBumpMillis) assert.False(t, req.AllowUserCancelWorkspaceJobs) require.Len(t, auditor.AuditLogs(), 5) @@ -707,6 +717,7 @@ func TestPatchTemplateMeta(t *testing.T) { AllowUserAutostart: options.UserAutostartEnabled, AllowUserAutostop: options.UserAutostopEnabled, DefaultTTL: int64(options.DefaultTTL), + ActivityBump: int64(options.ActivityBump), MaxTTL: int64(options.MaxTTL), UseMaxTtl: options.UseMaxTTL, AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek), @@ -1015,6 +1026,7 @@ func TestPatchTemplateMeta(t *testing.T) { Description: template.Description, Icon: template.Icon, DefaultTTLMillis: template.DefaultTTLMillis, + ActivityBumpMillis: template.ActivityBumpMillis, AutostopRequirement: nil, AllowUserAutostart: template.AllowUserAutostart, AllowUserAutostop: template.AllowUserAutostop, @@ -1105,6 +1117,7 @@ func TestPatchTemplateMeta(t *testing.T) { AllowUserAutostart: options.UserAutostartEnabled, AllowUserAutostop: options.UserAutostopEnabled, DefaultTTL: int64(options.DefaultTTL), + ActivityBump: int64(options.ActivityBump), UseMaxTtl: options.UseMaxTTL, MaxTTL: int64(options.MaxTTL), AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek), @@ -1177,6 +1190,7 @@ func TestPatchTemplateMeta(t *testing.T) { AllowUserAutostart: options.UserAutostartEnabled, AllowUserAutostop: options.UserAutostopEnabled, DefaultTTL: int64(options.DefaultTTL), + ActivityBump: int64(options.ActivityBump), UseMaxTtl: options.UseMaxTTL, MaxTTL: int64(options.MaxTTL), AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek), diff --git a/codersdk/organizations.go b/codersdk/organizations.go index e26b406cc49fe..6a629160207e8 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -84,6 +84,10 @@ type CreateTemplateRequest struct { // DefaultTTLMillis allows optionally specifying the default TTL // for all workspaces created from this template. DefaultTTLMillis *int64 `json:"default_ttl_ms,omitempty"` + // ActivityBumpMillis allows optionally specifying the activity bump + // duration for all workspaces created from this template. Defaults to 1h + // but can be set to 0 to disable activity bumping. + ActivityBumpMillis *int64 `json:"activity_bump_ms,omitempty"` // TODO(@dean): remove max_ttl once autostop_requirement is matured // Only one of MaxTTLMillis or AutostopRequirement can be specified. MaxTTLMillis *int64 `json:"max_ttl_ms,omitempty"` diff --git a/codersdk/templates.go b/codersdk/templates.go index 1be4d931ad7a2..ebb1b95dbfca1 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -31,6 +31,7 @@ type Template struct { DeprecationMessage string `json:"deprecation_message"` Icon string `json:"icon"` DefaultTTLMillis int64 `json:"default_ttl_ms"` + ActivityBumpMillis int64 `json:"activity_bump_ms"` // UseMaxTTL picks whether to use the deprecated max TTL for the template or // the new autostop requirement. UseMaxTTL bool `json:"use_max_ttl"` @@ -208,6 +209,10 @@ type UpdateTemplateMeta struct { Description string `json:"description,omitempty"` Icon string `json:"icon,omitempty"` DefaultTTLMillis int64 `json:"default_ttl_ms,omitempty"` + // ActivityBumpMillis allows optionally specifying the activity bump + // duration for all workspaces created from this template. Defaults to 1h + // but can be set to 0 to disable activity bumping. + ActivityBumpMillis int64 `json:"activity_bump_ms,omitempty"` // TODO(@dean): remove max_ttl once autostop_requirement is matured // Only one of MaxTTLMillis or AutostopRequirement can be specified. MaxTTLMillis int64 `json:"max_ttl_ms,omitempty"` diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index c09c829f3b765..f93b160a458c1 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -8,20 +8,20 @@ We track the following resources: -| 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
| -| HealthSettings
|
FieldTracked
dismissed_healthcheckstrue
idfalse
| -| 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
deprecatedtrue
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
use_max_ttltrue
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
nametrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
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
versiontrue
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
| +| HealthSettings
|
FieldTracked
dismissed_healthcheckstrue
idfalse
| +| License
create, delete |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
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
deprecatedtrue
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
use_max_ttltrue
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
nametrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
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
versiontrue
wildcard_hostnametrue
| diff --git a/docs/api/schemas.md b/docs/api/schemas.md index a51b3bcdfd3df..41fd0a6be8ce2 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1618,6 +1618,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -1647,6 +1648,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `activity_bump_ms` | integer | false | | Activity bump ms allows optionally specifying the activity bump duration for all workspaces created from this template. Defaults to 1h but can be set to 0 to disable activity bumping. | | `allow_user_autostart` | boolean | false | | Allow user autostart allows users to set a schedule for autostarting their workspace. By default this is true. This can only be disabled when using an enterprise license. | | `allow_user_autostop` | boolean | false | | Allow user autostop allows users to set a custom workspace TTL to use in place of the template's DefaultTTL field. By default this is true. If false, the DefaultTTL will always be used. This can only be disabled when using an enterprise license. | | `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. \*bool as the default value is "true". | @@ -4585,6 +4587,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -4634,6 +4637,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | ---------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `active_user_count` | integer | false | | Active user count is set to -1 when loading. | | `active_version_id` | string | false | | | +| `activity_bump_ms` | integer | 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 | | | diff --git a/docs/api/templates.md b/docs/api/templates.md index 63420f76a0d35..3b34ca1d67982 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -28,6 +28,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -87,6 +88,7 @@ Status Code **200** | `[array item]` | array | false | | | | `» active_user_count` | integer | false | | Active user count is set to -1 when loading. | | `» active_version_id` | string(uuid) | false | | | +| `» activity_bump_ms` | integer | 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 | | | @@ -147,6 +149,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa ```json { + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -187,6 +190,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -326,6 +330,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -641,6 +646,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, @@ -763,6 +769,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ { "active_user_count": 0, "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, "allow_user_autostart": true, "allow_user_autostop": true, "allow_user_cancel_workspace_jobs": true, diff --git a/docs/cli/templates_edit.md b/docs/cli/templates_edit.md index ff73c2828eb83..1dbcaa6376241 100644 --- a/docs/cli/templates_edit.md +++ b/docs/cli/templates_edit.md @@ -12,6 +12,14 @@ coder templates edit [flags]