diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 458cb1c2015b0..1f8833d0c957a 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -24,11 +24,10 @@ import ( func templateCreate() *cobra.Command { var ( - directory string - provisioner string - parameterFile string - maxTTL time.Duration - minAutostartInterval time.Duration + directory string + provisioner string + parameterFile string + defaultTTL time.Duration ) cmd := &cobra.Command{ Use: "create [name]", @@ -108,10 +107,9 @@ func templateCreate() *cobra.Command { } createReq := codersdk.CreateTemplateRequest{ - Name: templateName, - VersionID: job.ID, - MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()), - MinAutostartIntervalMillis: ptr.Ref(minAutostartInterval.Milliseconds()), + Name: templateName, + VersionID: job.ID, + DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()), } _, err = client.CreateTemplate(cmd.Context(), organization.ID, createReq) @@ -133,8 +131,7 @@ func templateCreate() *cobra.Command { cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") cmd.Flags().StringVarP(&provisioner, "test.provisioner", "", "terraform", "Customize the provisioner backend") cmd.Flags().StringVarP(¶meterFile, "parameter-file", "", "", "Specify a file path with parameter values.") - cmd.Flags().DurationVarP(&maxTTL, "max-ttl", "", 24*time.Hour, "Specify a maximum TTL for workspaces created from this template.") - cmd.Flags().DurationVarP(&minAutostartInterval, "min-autostart-interval", "", time.Hour, "Specify a minimum autostart interval for workspaces created from this template.") + cmd.Flags().DurationVarP(&defaultTTL, "default-ttl", "", 24*time.Hour, "Specify a default TTL for workspaces created from this template.") // This is for testing! err := cmd.Flags().MarkHidden("test.provisioner") if err != nil { diff --git a/cli/templatecreate_test.go b/cli/templatecreate_test.go index ccffb45fc7114..98af8158241d3 100644 --- a/cli/templatecreate_test.go +++ b/cli/templatecreate_test.go @@ -52,8 +52,7 @@ func TestTemplateCreate(t *testing.T) { "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), - "--max-ttl", "24h", - "--min-autostart-interval", "2h", + "--default-ttl", "24h", } cmd, root := clitest.New(t, args...) clitest.SetupConfig(t, client, root) diff --git a/cli/templateedit.go b/cli/templateedit.go index e0e4cf57a7196..867cb41d208a7 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -13,11 +13,10 @@ import ( func templateEdit() *cobra.Command { var ( - name string - description string - icon string - maxTTL time.Duration - minAutostartInterval time.Duration + name string + description string + icon string + defaultTTL time.Duration ) cmd := &cobra.Command{ @@ -40,11 +39,10 @@ func templateEdit() *cobra.Command { // NOTE: coderd will ignore empty fields. req := codersdk.UpdateTemplateMeta{ - Name: name, - Description: description, - Icon: icon, - MaxTTLMillis: maxTTL.Milliseconds(), - MinAutostartIntervalMillis: minAutostartInterval.Milliseconds(), + Name: name, + Description: description, + Icon: icon, + DefaultTTLMillis: defaultTTL.Milliseconds(), } _, err = client.UpdateTemplateMeta(cmd.Context(), template.ID, req) @@ -59,8 +57,7 @@ func templateEdit() *cobra.Command { cmd.Flags().StringVarP(&name, "name", "", "", "Edit the template name") cmd.Flags().StringVarP(&description, "description", "", "", "Edit the template description") cmd.Flags().StringVarP(&icon, "icon", "", "", "Edit the template icon path") - cmd.Flags().DurationVarP(&maxTTL, "max-ttl", "", 0, "Edit the template maximum time before shutdown - workspaces created from this template cannot stay running longer than this.") - cmd.Flags().DurationVarP(&minAutostartInterval, "min-autostart-interval", "", 0, "Edit the template minimum autostart interval - workspaces created from this template must wait at least this long between autostarts.") + cmd.Flags().DurationVarP(&defaultTTL, "default-ttl", "", 0, "Edit the template default time before shutdown - workspaces created from this template to this value.") cliui.AllowSkipPrompt(cmd) return cmd diff --git a/cli/templateedit_test.go b/cli/templateedit_test.go index 61437764021f6..fbfc77d26fdf5 100644 --- a/cli/templateedit_test.go +++ b/cli/templateedit_test.go @@ -26,16 +26,14 @@ func TestTemplateEdit(t *testing.T) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.Description = "original description" ctr.Icon = "/icons/default-icon.png" - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) // Test the cli command. name := "new-template-name" desc := "lorem ipsum dolor sit amet et cetera" icon := "/icons/new-icon.png" - maxTTL := 12 * time.Hour - minAutostartInterval := time.Minute + defaultTTL := 12 * time.Hour cmdArgs := []string{ "templates", "edit", @@ -43,8 +41,7 @@ func TestTemplateEdit(t *testing.T) { "--name", name, "--description", desc, "--icon", icon, - "--max-ttl", maxTTL.String(), - "--min-autostart-interval", minAutostartInterval.String(), + "--default-ttl", defaultTTL.String(), } cmd, root := clitest.New(t, cmdArgs...) clitest.SetupConfig(t, client, root) @@ -59,8 +56,7 @@ func TestTemplateEdit(t *testing.T) { assert.Equal(t, name, updated.Name) assert.Equal(t, desc, updated.Description) assert.Equal(t, icon, updated.Icon) - assert.Equal(t, maxTTL.Milliseconds(), updated.MaxTTLMillis) - assert.Equal(t, minAutostartInterval.Milliseconds(), updated.MinAutostartIntervalMillis) + assert.Equal(t, defaultTTL.Milliseconds(), updated.DefaultTTLMillis) }) t.Run("NotModified", func(t *testing.T) { @@ -72,8 +68,7 @@ func TestTemplateEdit(t *testing.T) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.Description = "original description" ctr.Icon = "/icons/default-icon.png" - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) // Test the cli command. @@ -84,8 +79,7 @@ func TestTemplateEdit(t *testing.T) { "--name", template.Name, "--description", template.Description, "--icon", template.Icon, - "--max-ttl", (time.Duration(template.MaxTTLMillis) * time.Millisecond).String(), - "--min-autostart-interval", (time.Duration(template.MinAutostartIntervalMillis) * time.Millisecond).String(), + "--default-ttl", (time.Duration(template.DefaultTTLMillis) * time.Millisecond).String(), } cmd, root := clitest.New(t, cmdArgs...) clitest.SetupConfig(t, client, root) @@ -100,7 +94,6 @@ func TestTemplateEdit(t *testing.T) { assert.Equal(t, template.Name, updated.Name) assert.Equal(t, template.Description, updated.Description) assert.Equal(t, template.Icon, updated.Icon) - assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) - assert.Equal(t, template.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis) + assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis) }) } diff --git a/cli/templates.go b/cli/templates.go index 4c85606d70c6f..7f8aa7e861792 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -50,15 +50,14 @@ func templates() *cobra.Command { } type templateTableRow struct { - Name string `table:"name"` - CreatedAt string `table:"created at"` - LastUpdated string `table:"last updated"` - OrganizationID uuid.UUID `table:"organization id"` - Provisioner codersdk.ProvisionerType `table:"provisioner"` - ActiveVersionID uuid.UUID `table:"active version id"` - UsedBy string `table:"used by"` - MaxTTL time.Duration `table:"max ttl"` - MinAutostartInterval time.Duration `table:"min autostart"` + Name string `table:"name"` + CreatedAt string `table:"created at"` + LastUpdated string `table:"last updated"` + OrganizationID uuid.UUID `table:"organization id"` + Provisioner codersdk.ProvisionerType `table:"provisioner"` + ActiveVersionID uuid.UUID `table:"active version id"` + UsedBy string `table:"used by"` + DefaultTTL time.Duration `table:"default ttl"` } // displayTemplates will return a table displaying all templates passed in. @@ -68,15 +67,14 @@ func displayTemplates(filterColumns []string, templates ...codersdk.Template) (s rows := make([]templateTableRow, len(templates)) for i, template := range templates { rows[i] = templateTableRow{ - Name: template.Name, - CreatedAt: template.CreatedAt.Format("January 2, 2006"), - LastUpdated: template.UpdatedAt.Format("January 2, 2006"), - OrganizationID: template.OrganizationID, - Provisioner: template.Provisioner, - ActiveVersionID: template.ActiveVersionID, - UsedBy: cliui.Styles.Fuchsia.Render(formatActiveDevelopers(template.ActiveUserCount)), - MaxTTL: (time.Duration(template.MaxTTLMillis) * time.Millisecond), - MinAutostartInterval: (time.Duration(template.MinAutostartIntervalMillis) * time.Millisecond), + Name: template.Name, + CreatedAt: template.CreatedAt.Format("January 2, 2006"), + LastUpdated: template.UpdatedAt.Format("January 2, 2006"), + OrganizationID: template.OrganizationID, + Provisioner: template.Provisioner, + ActiveVersionID: template.ActiveVersionID, + UsedBy: cliui.Styles.Fuchsia.Render(formatActiveDevelopers(template.ActiveUserCount)), + DefaultTTL: (time.Duration(template.DefaultTTLMillis) * time.Millisecond), } } diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index e2b9f09f3c37f..91525b5eb09af 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1455,8 +1455,7 @@ func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd tpl.Name = arg.Name tpl.Description = arg.Description tpl.Icon = arg.Icon - tpl.MaxTtl = arg.MaxTtl - tpl.MinAutostartInterval = arg.MinAutostartInterval + tpl.DefaultTtl = arg.DefaultTtl q.templates[idx] = tpl return tpl, nil } @@ -2227,25 +2226,20 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl q.mutex.Lock() defer q.mutex.Unlock() - if arg.MinAutostartInterval == 0 { - arg.MinAutostartInterval = int64(time.Hour) - } - //nolint:gosimple template := database.Template{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - UpdatedAt: arg.UpdatedAt, - OrganizationID: arg.OrganizationID, - Name: arg.Name, - Provisioner: arg.Provisioner, - ActiveVersionID: arg.ActiveVersionID, - Description: arg.Description, - MaxTtl: arg.MaxTtl, - MinAutostartInterval: arg.MinAutostartInterval, - CreatedBy: arg.CreatedBy, - UserACL: arg.UserACL, - GroupACL: arg.GroupACL, + ID: arg.ID, + CreatedAt: arg.CreatedAt, + UpdatedAt: arg.UpdatedAt, + OrganizationID: arg.OrganizationID, + Name: arg.Name, + Provisioner: arg.Provisioner, + ActiveVersionID: arg.ActiveVersionID, + Description: arg.Description, + DefaultTtl: arg.DefaultTtl, + CreatedBy: arg.CreatedBy, + UserACL: arg.UserACL, + GroupACL: arg.GroupACL, } q.templates = append(q.templates, template) return template, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index a32ea8af471f1..2b369cf167fe3 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -349,14 +349,15 @@ CREATE TABLE templates ( provisioner provisioner_type NOT NULL, active_version_id uuid NOT NULL, description character varying(128) DEFAULT ''::character varying NOT NULL, - max_ttl bigint DEFAULT '604800000000000'::bigint NOT NULL, - min_autostart_interval bigint DEFAULT '3600000000000'::bigint NOT NULL, + default_ttl bigint DEFAULT '604800000000000'::bigint NOT NULL, created_by uuid NOT NULL, icon character varying(256) DEFAULT ''::character varying NOT NULL, user_acl jsonb DEFAULT '{}'::jsonb NOT NULL, group_acl jsonb DEFAULT '{}'::jsonb NOT NULL ); +COMMENT ON COLUMN templates.default_ttl IS 'The default duration for auto-stop for workspaces created from this template.'; + CREATE TABLE user_links ( user_id uuid NOT NULL, login_type login_type NOT NULL, diff --git a/coderd/database/migrations/000073_remove_min_autostart.down.sql b/coderd/database/migrations/000073_remove_min_autostart.down.sql new file mode 100644 index 0000000000000..0b7b72c9fc974 --- /dev/null +++ b/coderd/database/migrations/000073_remove_min_autostart.down.sql @@ -0,0 +1,5 @@ +-- add "slug" min_autostart_interval to "templates" table +ALTER TABLE "templates" ADD COLUMN "min_autostart_interval" int DEFAULT 0; + +-- rename "default_ttl" to "max_ttl" on "templates" table +ALTER TABLE "templates" RENAME COLUMN "default_ttl" TO "max_ttl"; diff --git a/coderd/database/migrations/000073_remove_min_autostart.up.sql b/coderd/database/migrations/000073_remove_min_autostart.up.sql new file mode 100644 index 0000000000000..cc13b5f1746e6 --- /dev/null +++ b/coderd/database/migrations/000073_remove_min_autostart.up.sql @@ -0,0 +1,6 @@ +-- drop "min_autostart_interval" column from "templates" table +ALTER TABLE "templates" DROP COLUMN "min_autostart_interval"; + +-- rename "max_ttl" to "default_ttl" on "templates" table +ALTER TABLE "templates" RENAME COLUMN "max_ttl" TO "default_ttl"; +COMMENT ON COLUMN templates.default_ttl IS 'The default duration for auto-stop for workspaces created from this template.'; diff --git a/coderd/database/models.go b/coderd/database/models.go index b3da222afa827..f457dede07eec 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -574,21 +574,21 @@ type SiteConfig struct { } type Template struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` - Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` - ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` - Description string `db:"description" json:"description"` - MaxTtl int64 `db:"max_ttl" json:"max_ttl"` - MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"` - CreatedBy uuid.UUID `db:"created_by" json:"created_by"` - Icon string `db:"icon" json:"icon"` - UserACL TemplateACL `db:"user_acl" json:"user_acl"` - GroupACL TemplateACL `db:"group_acl" json:"group_acl"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` + ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` + Description string `db:"description" json:"description"` + // The default duration for auto-stop for workspaces created from this template. + DefaultTtl int64 `db:"default_ttl" json:"default_ttl"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` + Icon string `db:"icon" json:"icon"` + UserACL TemplateACL `db:"user_acl" json:"user_acl"` + GroupACL TemplateACL `db:"group_acl" json:"group_acl"` } type TemplateVersion struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7c1bf4bfcd34d..074bbb8a7eac3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3019,7 +3019,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, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl FROM templates WHERE @@ -3041,8 +3041,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3053,7 +3052,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, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl FROM templates WHERE @@ -3083,8 +3082,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3094,7 +3092,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, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl FROM 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 FROM templates ORDER BY (name, id) ASC ` @@ -3117,8 +3115,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3139,7 +3136,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, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl FROM templates WHERE @@ -3197,8 +3194,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3228,32 +3224,30 @@ INSERT INTO provisioner, active_version_id, description, - max_ttl, - min_autostart_interval, + default_ttl, created_by, icon, user_acl, group_acl ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl ` type InsertTemplateParams struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - Name string `db:"name" json:"name"` - Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` - ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` - Description string `db:"description" json:"description"` - MaxTtl int64 `db:"max_ttl" json:"max_ttl"` - MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"` - CreatedBy uuid.UUID `db:"created_by" json:"created_by"` - Icon string `db:"icon" json:"icon"` - UserACL TemplateACL `db:"user_acl" json:"user_acl"` - GroupACL TemplateACL `db:"group_acl" json:"group_acl"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + Name string `db:"name" json:"name"` + Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` + ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` + Description string `db:"description" json:"description"` + DefaultTtl int64 `db:"default_ttl" json:"default_ttl"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` + Icon string `db:"icon" json:"icon"` + UserACL TemplateACL `db:"user_acl" json:"user_acl"` + GroupACL TemplateACL `db:"group_acl" json:"group_acl"` } func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error) { @@ -3266,8 +3260,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam arg.Provisioner, arg.ActiveVersionID, arg.Description, - arg.MaxTtl, - arg.MinAutostartInterval, + arg.DefaultTtl, arg.CreatedBy, arg.Icon, arg.UserACL, @@ -3284,8 +3277,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3303,7 +3295,7 @@ SET WHERE id = $3 RETURNING - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl ` type UpdateTemplateACLByIDParams struct { @@ -3325,8 +3317,7 @@ func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTempla &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, @@ -3383,24 +3374,22 @@ UPDATE SET updated_at = $2, description = $3, - max_ttl = $4, - min_autostart_interval = $5, - name = $6, - icon = $7 + default_ttl = $4, + name = $5, + icon = $6 WHERE id = $1 RETURNING - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl ` type UpdateTemplateMetaByIDParams struct { - ID uuid.UUID `db:"id" json:"id"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - Description string `db:"description" json:"description"` - MaxTtl int64 `db:"max_ttl" json:"max_ttl"` - MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"` - Name string `db:"name" json:"name"` - Icon string `db:"icon" json:"icon"` + ID uuid.UUID `db:"id" json:"id"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + Description string `db:"description" json:"description"` + DefaultTtl int64 `db:"default_ttl" json:"default_ttl"` + Name string `db:"name" json:"name"` + Icon string `db:"icon" json:"icon"` } func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) (Template, error) { @@ -3408,8 +3397,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl arg.ID, arg.UpdatedAt, arg.Description, - arg.MaxTtl, - arg.MinAutostartInterval, + arg.DefaultTtl, arg.Name, arg.Icon, ) @@ -3424,8 +3412,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl &i.Provisioner, &i.ActiveVersionID, &i.Description, - &i.MaxTtl, - &i.MinAutostartInterval, + &i.DefaultTtl, &i.CreatedBy, &i.Icon, &i.UserACL, diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index 6eb73af288b81..7063b87075c6f 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -65,15 +65,14 @@ INSERT INTO provisioner, active_version_id, description, - max_ttl, - min_autostart_interval, + default_ttl, created_by, icon, user_acl, group_acl ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *; -- name: UpdateTemplateActiveVersionByID :exec UPDATE @@ -99,10 +98,9 @@ UPDATE SET updated_at = $2, description = $3, - max_ttl = $4, - min_autostart_interval = $5, - name = $6, - icon = $7 + default_ttl = $4, + name = $5, + icon = $6 WHERE id = $1 RETURNING diff --git a/coderd/templates.go b/coderd/templates.go index 163464db39b12..8becfd31c04df 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -22,15 +22,9 @@ import ( "github.com/coder/coder/coderd/httpmw" "github.com/coder/coder/coderd/rbac" "github.com/coder/coder/coderd/telemetry" - "github.com/coder/coder/coderd/util/ptr" "github.com/coder/coder/codersdk" ) -var ( - maxTTLDefault = 24 * 7 * time.Hour - minAutostartIntervalDefault = time.Hour -) - // Auto-importable templates. These can be auto-imported after the first user // has been created. type AutoImportTemplate string @@ -212,52 +206,36 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return } - maxTTL := maxTTLDefault - if createTemplate.MaxTTLMillis != nil { - maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond + var ttl time.Duration + if createTemplate.DefaultTTLMillis != nil { + ttl = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond } - if maxTTL < 0 { + if ttl < 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid create template request.", Validations: []codersdk.ValidationError{ - {Field: "max_ttl_ms", Detail: "Must be a positive integer."}, + {Field: "default_ttl_ms", Detail: "Must be a positive integer."}, }, }) return } - if maxTTL > maxTTLDefault { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid create template request.", - Validations: []codersdk.ValidationError{ - {Field: "max_ttl_ms", Detail: "Cannot be greater than " + maxTTLDefault.String()}, - }, - }) - return - } - - minAutostartInterval := minAutostartIntervalDefault - if !ptr.NilOrZero(createTemplate.MinAutostartIntervalMillis) { - minAutostartInterval = time.Duration(*createTemplate.MinAutostartIntervalMillis) * time.Millisecond - } - var dbTemplate database.Template var template codersdk.Template err = api.Database.InTx(func(tx database.Store) error { now := database.Now() dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{ - ID: uuid.New(), - CreatedAt: now, - UpdatedAt: now, - OrganizationID: organization.ID, - Name: createTemplate.Name, - Provisioner: importJob.Provisioner, - ActiveVersionID: templateVersion.ID, - Description: createTemplate.Description, - MaxTtl: int64(maxTTL), - MinAutostartInterval: int64(minAutostartInterval), - CreatedBy: apiKey.UserID, - UserACL: database.TemplateACL{}, + ID: uuid.New(), + CreatedAt: now, + UpdatedAt: now, + OrganizationID: organization.ID, + Name: createTemplate.Name, + Provisioner: importJob.Provisioner, + ActiveVersionID: templateVersion.ID, + Description: createTemplate.Description, + DefaultTtl: int64(ttl), + CreatedBy: apiKey.UserID, + UserACL: database.TemplateACL{}, GroupACL: database.TemplateACL{ organization.ID.String(): []rbac.Action{rbac.ActionRead}, }, @@ -464,14 +442,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } var validErrs []codersdk.ValidationError - if req.MaxTTLMillis < 0 { - validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."}) - } - if req.MinAutostartIntervalMillis < 0 { - validErrs = append(validErrs, codersdk.ValidationError{Field: "min_autostart_interval_ms", Detail: "Must be a positive integer."}) - } - if req.MaxTTLMillis > maxTTLDefault.Milliseconds() { - validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Cannot be greater than " + maxTTLDefault.String()}) + if req.DefaultTTLMillis < 0 { + validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) } if len(validErrs) > 0 { @@ -501,8 +473,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { if req.Name == template.Name && req.Description == template.Description && req.Icon == template.Icon && - req.MaxTTLMillis == time.Duration(template.MaxTtl).Milliseconds() && - req.MinAutostartIntervalMillis == time.Duration(template.MinAutostartInterval).Milliseconds() { + req.DefaultTTLMillis == time.Duration(template.DefaultTtl).Milliseconds() { return nil } @@ -510,8 +481,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { name := req.Name desc := req.Description icon := req.Icon - maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond - minAutostartInterval := time.Duration(req.MinAutostartIntervalMillis) * time.Millisecond + maxTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond if name == "" { name = template.Name @@ -519,18 +489,14 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { if desc == "" { desc = template.Description } - if minAutostartInterval == 0 { - minAutostartInterval = time.Duration(template.MinAutostartInterval) - } updated, err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{ - ID: template.ID, - UpdatedAt: database.Now(), - Name: name, - Description: desc, - Icon: icon, - MaxTtl: int64(maxTTL), - MinAutostartInterval: int64(minAutostartInterval), + ID: template.ID, + UpdatedAt: database.Now(), + Name: name, + Description: desc, + Icon: icon, + DefaultTtl: int64(maxTTL), }) if err != nil { return err @@ -666,18 +632,17 @@ func (api *API) autoImportTemplate(ctx context.Context, opts autoImportTemplateO // Create template template, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{ - ID: uuid.New(), - CreatedAt: now, - UpdatedAt: now, - OrganizationID: opts.orgID, - Name: opts.name, - Provisioner: job.Provisioner, - ActiveVersionID: templateVersion.ID, - Description: "This template was auto-imported by Coder.", - MaxTtl: int64(maxTTLDefault), - MinAutostartInterval: int64(minAutostartIntervalDefault), - CreatedBy: opts.userID, - UserACL: database.TemplateACL{}, + ID: uuid.New(), + CreatedAt: now, + UpdatedAt: now, + OrganizationID: opts.orgID, + Name: opts.name, + Provisioner: job.Provisioner, + ActiveVersionID: templateVersion.ID, + Description: "This template was auto-imported by Coder.", + DefaultTtl: 0, + CreatedBy: opts.userID, + UserACL: database.TemplateACL{}, GroupACL: database.TemplateACL{ opts.orgID.String(): []rbac.Action{rbac.ActionRead}, }, @@ -768,21 +733,20 @@ func (api *API) convertTemplate( buildTimeStats := api.metricsCache.TemplateBuildTimeStats(template.ID) return codersdk.Template{ - ID: template.ID, - CreatedAt: template.CreatedAt, - UpdatedAt: template.UpdatedAt, - OrganizationID: template.OrganizationID, - Name: template.Name, - Provisioner: codersdk.ProvisionerType(template.Provisioner), - ActiveVersionID: template.ActiveVersionID, - WorkspaceOwnerCount: workspaceOwnerCount, - ActiveUserCount: activeCount, - BuildTimeStats: buildTimeStats, - Description: template.Description, - Icon: template.Icon, - MaxTTLMillis: time.Duration(template.MaxTtl).Milliseconds(), - MinAutostartIntervalMillis: time.Duration(template.MinAutostartInterval).Milliseconds(), - CreatedByID: template.CreatedBy, - CreatedByName: createdByName, + ID: template.ID, + CreatedAt: template.CreatedAt, + UpdatedAt: template.UpdatedAt, + OrganizationID: template.OrganizationID, + Name: template.Name, + Provisioner: codersdk.ProvisionerType(template.Provisioner), + ActiveVersionID: template.ActiveVersionID, + WorkspaceOwnerCount: workspaceOwnerCount, + ActiveUserCount: activeCount, + BuildTimeStats: buildTimeStats, + Description: template.Description, + Icon: template.Icon, + DefaultTTLMillis: time.Duration(template.DefaultTtl).Milliseconds(), + CreatedByID: template.CreatedBy, + CreatedByName: createdByName, } } diff --git a/coderd/templates_test.go b/coderd/templates_test.go index bb602d0b05406..df047993b5061 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -131,34 +131,14 @@ func TestPostTemplateByOrganization(t *testing.T) { defer cancel() _, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{ - Name: "testing", - VersionID: version.ID, - MaxTTLMillis: ptr.Ref(int64(-1)), + Name: "testing", + VersionID: version.ID, + DefaultTTLMillis: ptr.Ref(int64(-1)), }) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) - require.Contains(t, err.Error(), "max_ttl_ms: Must be a positive integer") - }) - - t.Run("MaxTTLTooHigh", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, nil) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - _, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{ - Name: "testing", - VersionID: version.ID, - MaxTTLMillis: ptr.Ref(365 * 24 * time.Hour.Milliseconds()), - }) - var apiErr *codersdk.Error - require.ErrorAs(t, err, &apiErr) - require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) - require.Contains(t, err.Error(), "max_ttl_ms: Cannot be greater than") + require.Contains(t, err.Error(), "default_ttl_ms: Must be a positive integer") }) t.Run("NoMaxTTL", func(t *testing.T) { @@ -171,12 +151,12 @@ func TestPostTemplateByOrganization(t *testing.T) { defer cancel() got, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{ - Name: "testing", - VersionID: version.ID, - MaxTTLMillis: ptr.Ref(int64(0)), + Name: "testing", + VersionID: version.ID, + DefaultTTLMillis: ptr.Ref(int64(0)), }) require.NoError(t, err) - require.Zero(t, got.MaxTTLMillis) + require.Zero(t, got.DefaultTTLMillis) }) t.Run("Unauthorized", func(t *testing.T) { @@ -306,15 +286,13 @@ func TestPatchTemplateMeta(t *testing.T) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.Description = "original description" ctr.Icon = "/icons/original-icon.png" - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) req := codersdk.UpdateTemplateMeta{ - Name: "new-template-name", - Description: "lorem ipsum dolor sit amet et cetera", - Icon: "/icons/new-icon.png", - MaxTTLMillis: 12 * time.Hour.Milliseconds(), - MinAutostartIntervalMillis: time.Minute.Milliseconds(), + Name: "new-template-name", + Description: "lorem ipsum dolor sit amet et cetera", + Icon: "/icons/new-icon.png", + DefaultTTLMillis: 12 * time.Hour.Milliseconds(), } // It is unfortunate we need to sleep, but the test can fail if the // updatedAt is too close together. @@ -329,8 +307,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, req.Name, updated.Name) assert.Equal(t, req.Description, updated.Description) assert.Equal(t, req.Icon, updated.Icon) - assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis) - assert.Equal(t, req.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis) + assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis) // Extra paranoid: did it _really_ happen? updated, err = client.Template(ctx, template.ID) @@ -339,8 +316,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, req.Name, updated.Name) assert.Equal(t, req.Description, updated.Description) assert.Equal(t, req.Icon, updated.Icon) - assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis) - assert.Equal(t, req.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis) + assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis) require.Len(t, auditor.AuditLogs, 4) assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[3].Action) @@ -353,10 +329,10 @@ func TestPatchTemplateMeta(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) req := codersdk.UpdateTemplateMeta{ - MaxTTLMillis: 0, + DefaultTTLMillis: 0, } // We're too fast! Sleep so we can be sure that updatedAt is greater @@ -372,7 +348,7 @@ func TestPatchTemplateMeta(t *testing.T) { updated, err := client.Template(ctx, template.ID) require.NoError(t, err) assert.Greater(t, updated.UpdatedAt, template.UpdatedAt) - assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis) + assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis) }) t.Run("MaxTTLTooLow", func(t *testing.T) { @@ -382,49 +358,23 @@ func TestPatchTemplateMeta(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - }) - req := codersdk.UpdateTemplateMeta{ - MaxTTLMillis: -1, - } - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - _, err := client.UpdateTemplateMeta(ctx, template.ID, req) - require.ErrorContains(t, err, "max_ttl_ms: Must be a positive integer") - - // Ensure no update occurred - updated, err := client.Template(ctx, template.ID) - require.NoError(t, err) - assert.Equal(t, updated.UpdatedAt, template.UpdatedAt) - assert.Equal(t, updated.MaxTTLMillis, template.MaxTTLMillis) - }) - - t.Run("MaxTTLTooHigh", func(t *testing.T) { - t.Parallel() - - client := coderdtest.New(t, nil) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) req := codersdk.UpdateTemplateMeta{ - MaxTTLMillis: 365 * 24 * time.Hour.Milliseconds(), + DefaultTTLMillis: -1, } ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() _, err := client.UpdateTemplateMeta(ctx, template.ID, req) - require.ErrorContains(t, err, "max_ttl_ms: Cannot be greater than") + require.ErrorContains(t, err, "default_ttl_ms: Must be a positive integer") // Ensure no update occurred updated, err := client.Template(ctx, template.ID) require.NoError(t, err) assert.Equal(t, updated.UpdatedAt, template.UpdatedAt) - assert.Equal(t, updated.MaxTTLMillis, template.MaxTTLMillis) + assert.Equal(t, updated.DefaultTTLMillis, template.DefaultTTLMillis) }) t.Run("NotModified", func(t *testing.T) { @@ -436,19 +386,17 @@ func TestPatchTemplateMeta(t *testing.T) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.Description = "original description" ctr.Icon = "/icons/original-icon.png" - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() req := codersdk.UpdateTemplateMeta{ - Name: template.Name, - Description: template.Description, - Icon: template.Icon, - MaxTTLMillis: template.MaxTTLMillis, - MinAutostartIntervalMillis: template.MinAutostartIntervalMillis, + Name: template.Name, + Description: template.Description, + Icon: template.Icon, + DefaultTTLMillis: template.DefaultTTLMillis, } _, err := client.UpdateTemplateMeta(ctx, template.ID, req) require.ErrorContains(t, err, "not modified") @@ -458,8 +406,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, template.Name, updated.Name) assert.Equal(t, template.Description, updated.Description) assert.Equal(t, template.Icon, updated.Icon) - assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) - assert.Equal(t, template.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis) + assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis) }) t.Run("Invalid", func(t *testing.T) { @@ -470,24 +417,21 @@ func TestPatchTemplateMeta(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.Description = "original description" - ctr.MaxTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) + ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds()) }) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() req := codersdk.UpdateTemplateMeta{ - MaxTTLMillis: -int64(time.Hour), - MinAutostartIntervalMillis: -int64(time.Hour), + DefaultTTLMillis: -int64(time.Hour), } _, err := client.UpdateTemplateMeta(ctx, template.ID, req) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Contains(t, apiErr.Message, "Invalid request") - require.Len(t, apiErr.Validations, 2) - assert.Equal(t, apiErr.Validations[0].Field, "max_ttl_ms") - assert.Equal(t, apiErr.Validations[1].Field, "min_autostart_interval_ms") + require.Len(t, apiErr.Validations, 1) + assert.Equal(t, apiErr.Validations[0].Field, "default_ttl_ms") updated, err := client.Template(ctx, template.ID) require.NoError(t, err) @@ -495,8 +439,7 @@ func TestPatchTemplateMeta(t *testing.T) { assert.Equal(t, template.Name, updated.Name) assert.Equal(t, template.Description, updated.Description) assert.Equal(t, template.Icon, updated.Icon) - assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) - assert.Equal(t, template.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis) + assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis) }) t.Run("RemoveIcon", func(t *testing.T) { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 38d53107363dd..0002c65178cc6 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -37,11 +37,10 @@ var ( ttlMin = time.Minute //nolint:revive // min here means 'minimum' not 'minutes' ttlMax = 7 * 24 * time.Hour - errTTLMin = xerrors.New("time until shutdown must be at least one minute") - errTTLMax = xerrors.New("time until shutdown must be less than 7 days") - errDeadlineTooSoon = xerrors.New("new deadline must be at least 30 minutes in the future") - errDeadlineBeforeStart = xerrors.New("new deadline must be before workspace start time") - errDeadlineOverTemplateMax = xerrors.New("new deadline is greater than template allows") + errTTLMin = xerrors.New("time until shutdown must be at least one minute") + errTTLMax = xerrors.New("time until shutdown must be less than 7 days") + errDeadlineTooSoon = xerrors.New("new deadline must be at least 30 minutes in the future") + errDeadlineBeforeStart = xerrors.New("new deadline must be before workspace start time") ) func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { @@ -333,7 +332,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return } - dbAutostartSchedule, err := validWorkspaceSchedule(createWorkspace.AutostartSchedule, time.Duration(template.MinAutostartInterval)) + dbAutostartSchedule, err := validWorkspaceSchedule(createWorkspace.AutostartSchedule) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid Autostart Schedule.", @@ -342,7 +341,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return } - dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, time.Duration(template.MaxTtl)) + dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, template.DefaultTtl) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid Workspace Time to Shutdown.", @@ -666,16 +665,7 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { return } - template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID) - if err != nil { - api.Logger.Error(ctx, "fetch workspace template", slog.F("workspace_id", workspace.ID), slog.F("template_id", workspace.TemplateID), slog.Error(err)) - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Error fetching workspace template.", - }) - return - } - - dbSched, err := validWorkspaceSchedule(req.Schedule, time.Duration(template.MinAutostartInterval)) + dbSched, err := validWorkspaceSchedule(req.Schedule) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid autostart schedule.", @@ -739,7 +729,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("fetch workspace template: %w", err) } - dbTTL, err = validWorkspaceTTLMillis(req.TTLMillis, time.Duration(template.MaxTtl)) + dbTTL, err = validWorkspaceTTLMillis(req.TTLMillis, template.DefaultTtl) if err != nil { return codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()} } @@ -793,13 +783,6 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { resp := codersdk.Response{} err := api.Database.InTx(func(s database.Store) error { - template, err := s.GetTemplateByID(ctx, workspace.TemplateID) - if err != nil { - code = http.StatusInternalServerError - resp.Message = "Error fetching workspace template!" - return xerrors.Errorf("get workspace template: %w", err) - } - build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) if err != nil { code = http.StatusInternalServerError @@ -833,7 +816,7 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { } newDeadline := req.Deadline.UTC() - if err := validWorkspaceDeadline(job.CompletedAt.Time, newDeadline, time.Duration(template.MaxTtl)); err != nil { + if err := validWorkspaceDeadline(job.CompletedAt.Time, newDeadline); err != nil { // NOTE(Cian): Putting the error in the Message field on request from the FE folks. // Normally, we would put the validation error in Validations, but this endpoint is // not tied to a form or specific named user input on the FE. @@ -1104,9 +1087,16 @@ func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 { return &millis } -func validWorkspaceTTLMillis(millis *int64, max time.Duration) (sql.NullInt64, error) { +func validWorkspaceTTLMillis(millis *int64, def int64) (sql.NullInt64, error) { if ptr.NilOrZero(millis) { - return sql.NullInt64{}, nil + if def == 0 { + return sql.NullInt64{}, nil + } + + return sql.NullInt64{ + Int64: def, + Valid: true, + }, nil } dur := time.Duration(*millis) * time.Millisecond @@ -1119,18 +1109,13 @@ func validWorkspaceTTLMillis(millis *int64, max time.Duration) (sql.NullInt64, e return sql.NullInt64{}, errTTLMax } - // template level - if max > 0 && truncated > max { - return sql.NullInt64{}, xerrors.Errorf("time until shutdown must be below template maximum %s", max.String()) - } - return sql.NullInt64{ Valid: true, Int64: int64(truncated), }, nil } -func validWorkspaceDeadline(startedAt, newDeadline time.Time, max time.Duration) error { +func validWorkspaceDeadline(startedAt, newDeadline time.Time) error { soon := time.Now().Add(29 * time.Minute) if newDeadline.Before(soon) { return errDeadlineTooSoon @@ -1141,28 +1126,19 @@ func validWorkspaceDeadline(startedAt, newDeadline time.Time, max time.Duration) return errDeadlineBeforeStart } - delta := newDeadline.Sub(startedAt) - if delta > max { - return errDeadlineOverTemplateMax - } - return nil } -func validWorkspaceSchedule(s *string, min time.Duration) (sql.NullString, error) { +func validWorkspaceSchedule(s *string) (sql.NullString, error) { if ptr.NilOrEmpty(s) { return sql.NullString{}, nil } - sched, err := schedule.Weekly(*s) + _, err := schedule.Weekly(*s) if err != nil { return sql.NullString{}, err } - if schedMin := sched.Min(); schedMin < min { - return sql.NullString{}, xerrors.Errorf("Minimum autostart interval %s below template minimum %s", schedMin, min) - } - return sql.NullString{ Valid: true, String: *s, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index ca92537da0e27..bc8a6b05664cb 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -239,10 +239,10 @@ func TestPostWorkspacesByOrganization(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref(int64(0)) + ctr.DefaultTTLMillis = ptr.Ref(int64(0)) }) // Given: the template has no max TTL set - require.Zero(t, template.MaxTTLMillis) + require.Zero(t, template.DefaultTTLMillis) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) // When: we create a workspace with autostop not enabled @@ -260,15 +260,15 @@ func TestPostWorkspacesByOrganization(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) templateTTL := 24 * time.Hour.Milliseconds() template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref(templateTTL) + ctr.DefaultTTLMillis = ptr.Ref(templateTTL) }) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { cwr.TTLMillis = nil // ensure that no default TTL is set }) // TTL should be set by the template - require.Equal(t, template.MaxTTLMillis, templateTTL) - require.Equal(t, template.MaxTTLMillis, template.MaxTTLMillis, workspace.TTLMillis) + require.Equal(t, template.DefaultTTLMillis, templateTTL) + require.Equal(t, template.DefaultTTLMillis, template.DefaultTTLMillis, workspace.TTLMillis) }) t.Run("InvalidTTL", func(t *testing.T) { @@ -298,58 +298,38 @@ func TestPostWorkspacesByOrganization(t *testing.T) { require.Equal(t, apiErr.Validations[0].Field, "ttl_ms") require.Equal(t, "time until shutdown must be at least one minute", apiErr.Validations[0].Detail) }) - - t.Run("AboveMax", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - req := codersdk.CreateWorkspaceRequest{ - TemplateID: template.ID, - Name: "testing", - TTLMillis: ptr.Ref(template.MaxTTLMillis + time.Minute.Milliseconds()), - } - _, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req) - require.Error(t, err) - var apiErr *codersdk.Error - require.ErrorAs(t, err, &apiErr) - require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) - require.Len(t, apiErr.Validations, 1) - require.Equal(t, apiErr.Validations[0].Field, "ttl_ms") - require.Equal(t, "time until shutdown must be less than 7 days", apiErr.Validations[0].Detail) - }) }) - t.Run("InvalidAutostart", func(t *testing.T) { + t.Run("TemplateDefaultTTL", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + exp := 24 * time.Hour.Milliseconds() + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.DefaultTTLMillis = &exp + }) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + // no TTL provided should use template default req := codersdk.CreateWorkspaceRequest{ - TemplateID: template.ID, - Name: "testing", - AutostartSchedule: ptr.Ref("CRON_TZ=US/Central * * * * *"), + TemplateID: template.ID, + Name: "testing", } - _, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req) - require.Error(t, err) - var apiErr *codersdk.Error - require.ErrorAs(t, err, &apiErr) - require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) - require.Len(t, apiErr.Validations, 1) - require.Equal(t, apiErr.Validations[0].Field, "schedule") - require.Equal(t, apiErr.Validations[0].Detail, "Minimum autostart interval 1m0s below template minimum 1h0m0s") + ws, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req) + require.NoError(t, err) + require.EqualValues(t, exp, *ws.TTLMillis) + + // TTL provided should override template default + req.Name = "testing2" + exp = 1 * time.Hour.Milliseconds() + req.TTLMillis = &exp + ws, err = client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req) + require.NoError(t, err) + require.EqualValues(t, exp, *ws.TTLMillis) }) } @@ -1187,23 +1167,6 @@ func TestWorkspaceUpdateTTL(t *testing.T) { ttlMillis: ptr.Ref((24*7*time.Hour + time.Minute).Milliseconds()), expectedError: "time until shutdown must be less than 7 days", }, - { - name: "above template maximum ttl", - ttlMillis: ptr.Ref((12 * time.Hour).Milliseconds()), - expectedError: "ttl_ms: time until shutdown must be below template maximum 8h0m0s", - modifyTemplate: func(ctr *codersdk.CreateTemplateRequest) { ctr.MaxTTLMillis = ptr.Ref((8 * time.Hour).Milliseconds()) }, - }, - { - name: "no template maximum ttl", - ttlMillis: ptr.Ref((7 * 24 * time.Hour).Milliseconds()), - modifyTemplate: func(ctr *codersdk.CreateTemplateRequest) { ctr.MaxTTLMillis = ptr.Ref(int64(0)) }, - }, - { - name: "above maximum ttl even with no template max", - ttlMillis: ptr.Ref((365 * 24 * time.Hour).Milliseconds()), - expectedError: "ttl_ms: time until shutdown must be less than 7 days", - modifyTemplate: func(ctr *codersdk.CreateTemplateRequest) { ctr.MaxTTLMillis = ptr.Ref(int64(0)) }, - }, } for _, testCase := range testCases { @@ -1322,14 +1285,6 @@ func TestWorkspaceExtend(t *testing.T) { }) require.ErrorContains(t, err, "unexpected status code 400: Cannot extend workspace: new deadline must be at least 30 minutes in the future", "setting a deadline less than 30 minutes in the future should fail") - // And with a deadline greater than the template max_ttl should also fail - deadlineExceedsMaxTTL := time.Now().Add(time.Duration(template.MaxTTLMillis) * time.Millisecond).Add(time.Minute) - err = client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{ - Deadline: deadlineExceedsMaxTTL, - }) - - require.ErrorContains(t, err, "unexpected status code 400: Cannot extend workspace: new deadline is greater than template allows", "setting a deadline greater than that allowed by the template should fail") - // Updating with a deadline 30 minutes in the future should succeed deadlineJustSoonEnough := time.Now().Add(30 * time.Minute) err = client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{ diff --git a/codersdk/organizations.go b/codersdk/organizations.go index de5e42122ce28..1cb0ca5f975e2 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -66,14 +66,9 @@ type CreateTemplateRequest struct { VersionID uuid.UUID `json:"template_version_id" validate:"required"` ParameterValues []CreateParameterRequest `json:"parameter_values,omitempty"` - // MaxTTLMillis allows optionally specifying the maximum allowable TTL + // DefaultTTLMillis allows optionally specifying the default TTL // for all workspaces created from this template. - MaxTTLMillis *int64 `json:"max_ttl_ms,omitempty"` - - // MinAutostartIntervalMillis allows optionally specifying the minimum - // allowable duration between autostarts for all workspaces created from - // this template. - MinAutostartIntervalMillis *int64 `json:"min_autostart_interval_ms,omitempty"` + DefaultTTLMillis *int64 `json:"default_ttl_ms,omitempty"` } // CreateWorkspaceRequest provides options for creating a new workspace. diff --git a/codersdk/templates.go b/codersdk/templates.go index 22e707050aed3..05e845240be8f 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -23,14 +23,13 @@ type Template struct { ActiveVersionID uuid.UUID `json:"active_version_id"` WorkspaceOwnerCount uint32 `json:"workspace_owner_count"` // ActiveUserCount is set to -1 when loading. - ActiveUserCount int `json:"active_user_count"` - BuildTimeStats TemplateBuildTimeStats `json:"build_time_stats"` - Description string `json:"description"` - Icon string `json:"icon"` - MaxTTLMillis int64 `json:"max_ttl_ms"` - MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"` - CreatedByID uuid.UUID `json:"created_by_id"` - CreatedByName string `json:"created_by_name"` + ActiveUserCount int `json:"active_user_count"` + BuildTimeStats TemplateBuildTimeStats `json:"build_time_stats"` + Description string `json:"description"` + Icon string `json:"icon"` + DefaultTTLMillis int64 `json:"default_ttl_ms"` + CreatedByID uuid.UUID `json:"created_by_id"` + CreatedByName string `json:"created_by_name"` } type TemplateBuildTimeStats struct { @@ -72,11 +71,10 @@ type UpdateTemplateACL struct { } type UpdateTemplateMeta struct { - Name string `json:"name,omitempty" validate:"omitempty,username"` - Description string `json:"description,omitempty"` - Icon string `json:"icon,omitempty"` - MaxTTLMillis int64 `json:"max_ttl_ms,omitempty"` - MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms,omitempty"` + Name string `json:"name,omitempty" validate:"omitempty,username"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + DefaultTTLMillis int64 `json:"default_ttl_ms,omitempty"` } // Template returns a single template. diff --git a/enterprise/audit/diff_internal_test.go b/enterprise/audit/diff_internal_test.go index d5ee1f0e369c5..226fc44b4131c 100644 --- a/enterprise/audit/diff_internal_test.go +++ b/enterprise/audit/diff_internal_test.go @@ -242,26 +242,24 @@ func Test_diff(t *testing.T) { name: "Create", left: audit.Empty[database.Template](), right: database.Template{ - ID: uuid.UUID{1}, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - OrganizationID: uuid.UUID{2}, - Deleted: false, - Name: "rust", - Provisioner: database.ProvisionerTypeTerraform, - ActiveVersionID: uuid.UUID{3}, - MaxTtl: int64(time.Hour), - MinAutostartInterval: int64(time.Minute), - CreatedBy: uuid.UUID{4}, + ID: uuid.UUID{1}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + OrganizationID: uuid.UUID{2}, + Deleted: false, + Name: "rust", + Provisioner: database.ProvisionerTypeTerraform, + ActiveVersionID: uuid.UUID{3}, + DefaultTtl: int64(time.Hour), + CreatedBy: uuid.UUID{4}, }, exp: audit.Map{ - "id": audit.OldNew{Old: "", New: uuid.UUID{1}.String()}, - "name": audit.OldNew{Old: "", New: "rust"}, - "provisioner": audit.OldNew{Old: database.ProvisionerType(""), New: database.ProvisionerTypeTerraform}, - "active_version_id": audit.OldNew{Old: "", New: uuid.UUID{3}.String()}, - "max_ttl": audit.OldNew{Old: int64(0), New: int64(time.Hour)}, - "min_autostart_interval": audit.OldNew{Old: int64(0), New: int64(time.Minute)}, - "created_by": audit.OldNew{Old: "", New: uuid.UUID{4}.String()}, + "id": audit.OldNew{Old: "", New: uuid.UUID{1}.String()}, + "name": audit.OldNew{Old: "", New: "rust"}, + "provisioner": audit.OldNew{Old: database.ProvisionerType(""), New: database.ProvisionerTypeTerraform}, + "active_version_id": audit.OldNew{Old: "", New: uuid.UUID{3}.String()}, + "default_ttl": audit.OldNew{Old: int64(0), New: int64(time.Hour)}, + "created_by": audit.OldNew{Old: "", New: uuid.UUID{4}.String()}, }, }, }) diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 2578ae7437844..72774ced4e142 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -58,7 +58,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{ "active_version_id": ActionTrack, "description": ActionTrack, "icon": ActionTrack, - "max_ttl": ActionTrack, + "default_ttl": ActionTrack, "min_autostart_interval": ActionTrack, "created_by": ActionTrack, "is_private": ActionTrack, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b38ed2ea6c040..bb8d4fa38c16f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -179,8 +179,7 @@ export interface CreateTemplateRequest { readonly icon?: string readonly template_version_id: string readonly parameter_values?: CreateParameterRequest[] - readonly max_ttl_ms?: number - readonly min_autostart_interval_ms?: number + readonly default_ttl_ms?: number } // From codersdk/templateversions.go @@ -620,8 +619,7 @@ export interface Template { readonly build_time_stats: TemplateBuildTimeStats readonly description: string readonly icon: string - readonly max_ttl_ms: number - readonly min_autostart_interval_ms: number + readonly default_ttl_ms: number readonly created_by_id: string readonly created_by_name: string } @@ -700,8 +698,7 @@ export interface UpdateTemplateMeta { readonly name?: string readonly description?: string readonly icon?: string - readonly max_ttl_ms?: number - readonly min_autostart_interval_ms?: number + readonly default_ttl_ms?: number } // From codersdk/users.go diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx index 7ef9f758fc498..2b30f6fb803dc 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx @@ -19,7 +19,7 @@ import * as Yup from "yup" export const Language = { nameLabel: "Name", descriptionLabel: "Description", - maxTtlLabel: "Auto-stop limit", + defaultTtlLabel: "Auto-stop default", iconLabel: "Icon", formAriaLabel: "Template settings form", selectEmoji: "Select emoji", @@ -28,7 +28,7 @@ export const Language = { descriptionMaxError: "Please enter a description that is less than or equal to 128 characters.", ttlHelperText: (ttl: number): string => - `Workspaces created from this template may not remain running longer than ${ttl} hours.`, + `Workspaces created from this template will default to stopping after ${ttl} hours.`, } const MAX_DESCRIPTION_CHAR_LIMIT = 128 @@ -41,7 +41,7 @@ export const validationSchema = Yup.object({ MAX_DESCRIPTION_CHAR_LIMIT, Language.descriptionMaxError, ), - max_ttl_ms: Yup.number() + default_ttl_ms: Yup.number() .integer() .min(0) .max(24 * MAX_TTL_DAYS /* 7 days in hours */, Language.ttlMaxError), @@ -72,7 +72,7 @@ export const TemplateSettingsForm: FC = ({ name: template.name, description: template.description, // on display, convert from ms => hours - max_ttl_ms: template.max_ttl_ms / MS_HOUR_CONVERSION, + default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION, icon: template.icon, }, validationSchema, @@ -80,8 +80,8 @@ export const TemplateSettingsForm: FC = ({ // on submit, convert from hours => ms onSubmit({ ...formData, - max_ttl_ms: formData.max_ttl_ms - ? formData.max_ttl_ms * MS_HOUR_CONVERSION + default_ttl_ms: formData.default_ttl_ms + ? formData.default_ttl_ms * MS_HOUR_CONVERSION : undefined, }) }, @@ -176,20 +176,20 @@ export const TemplateSettingsForm: FC = ({ - {/* If a value for max_ttl_ms has been entered and + {/* If a value for default_ttl_ms has been entered and there are no validation errors for that field, display helper text. We do not use the MUI helper-text prop because it overrides the validation error */} - {form.values.max_ttl_ms && !form.errors.max_ttl_ms && ( + {form.values.default_ttl_ms && !form.errors.default_ttl_ms && ( - {Language.ttlHelperText(form.values.max_ttl_ms)} + {Language.ttlHelperText(form.values.default_ttl_ms)} )} diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsPage.test.tsx index f6db0bde004d9..95aed343c0d01 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsPage.test.tsx @@ -26,15 +26,15 @@ const validFormValues = { name: "Name", description: "A description", icon: "A string", - max_ttl_ms: 1, + default_ttl_ms: 1, } const fillAndSubmitForm = async ({ name, description, - max_ttl_ms, + default_ttl_ms, icon, -}: Omit, "min_autostart_interval_ms">) => { +}: Required) => { const nameField = await screen.findByLabelText(FormLanguage.nameLabel) await userEvent.clear(nameField) await userEvent.type(nameField, name) @@ -49,9 +49,9 @@ const fillAndSubmitForm = async ({ await userEvent.clear(iconField) await userEvent.type(iconField, icon) - const maxTtlField = await screen.findByLabelText(FormLanguage.maxTtlLabel) + const maxTtlField = await screen.findByLabelText(FormLanguage.defaultTtlLabel) await userEvent.clear(maxTtlField) - await userEvent.type(maxTtlField, max_ttl_ms.toString()) + await userEvent.type(maxTtlField, default_ttl_ms.toString()) const submitButton = await screen.findByText( FooterFormLanguage.defaultSubmitLabel, @@ -87,7 +87,7 @@ describe("TemplateSettingsPage", () => { }) await fillAndSubmitForm(validFormValues) - expect(screen.getByDisplayValue(1)).toBeInTheDocument() // the max_ttl_ms + expect(screen.getByDisplayValue(1)).toBeInTheDocument() // the default_ttl_ms await waitFor(() => expect(API.updateTemplateMeta).toBeCalledTimes(1)) await waitFor(() => @@ -95,7 +95,7 @@ describe("TemplateSettingsPage", () => { "test-template", expect.objectContaining({ ...validFormValues, - max_ttl_ms: 3600000, // the max_ttl_ms to ms + default_ttl_ms: 3600000, // the default_ttl_ms to ms }), ), ) @@ -104,7 +104,7 @@ describe("TemplateSettingsPage", () => { it("allows a ttl of 7 days", () => { const values: UpdateTemplateMeta = { ...validFormValues, - max_ttl_ms: 24 * 7, + default_ttl_ms: 24 * 7, } const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrowError() @@ -113,7 +113,7 @@ describe("TemplateSettingsPage", () => { it("allows ttl of 0", () => { const values: UpdateTemplateMeta = { ...validFormValues, - max_ttl_ms: 0, + default_ttl_ms: 0, } const validate = () => validationSchema.validateSync(values) expect(validate).not.toThrowError() @@ -122,7 +122,7 @@ describe("TemplateSettingsPage", () => { it("disallows a ttl of 7 days + 1 hour", () => { const values: UpdateTemplateMeta = { ...validFormValues, - max_ttl_ms: 24 * 7 + 1, + default_ttl_ms: 24 * 7 + 1, } const validate = () => validationSchema.validateSync(values) expect(validate).toThrowError(FormLanguage.ttlMaxError) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 343f2dd414c12..69361d68b9222 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -198,8 +198,7 @@ export const MockTemplate: TypesGen.Template = { delete_ms: 3000, }, description: "This is a test description.", - max_ttl_ms: 24 * 60 * 60 * 1000, - min_autostart_interval_ms: 60 * 60 * 1000, + default_ttl_ms: 24 * 60 * 60 * 1000, created_by_id: "test-creator-id", created_by_name: "test_creator", icon: "/icon/code.svg", diff --git a/site/src/util/schedule.test.ts b/site/src/util/schedule.test.ts index 9cd3dd1b50177..452727624a4e6 100644 --- a/site/src/util/schedule.test.ts +++ b/site/src/util/schedule.test.ts @@ -52,7 +52,7 @@ describe("maxDeadline", () => { it("should be never be greater than global max deadline", () => { const template: Template = { ...Mocks.MockTemplate, - max_ttl_ms: 25 * 60 * 60 * 1000, + default_ttl_ms: 25 * 60 * 60 * 1000, } // Then: deadlineMinusDisabled should be falsy @@ -65,7 +65,7 @@ describe("maxDeadline", () => { it("should be never be greater than global max deadline", () => { const template: Template = { ...Mocks.MockTemplate, - max_ttl_ms: 4 * 60 * 60 * 1000, + default_ttl_ms: 4 * 60 * 60 * 1000, } // Then: deadlineMinusDisabled should be falsy @@ -95,7 +95,7 @@ describe("canExtendDeadline", () => { it("should be falsy if the deadline is more than the template max_ttl", () => { const tooFarAhead = dayjs().add( - dayjs.duration(Mocks.MockTemplate.max_ttl_ms, "milliseconds"), + dayjs.duration(Mocks.MockTemplate.default_ttl_ms, "milliseconds"), ) expect( canExtendDeadline(tooFarAhead, Mocks.MockWorkspace, Mocks.MockTemplate), @@ -104,7 +104,7 @@ describe("canExtendDeadline", () => { it("should be truth if the deadline is within the template max_ttl", () => { const okDeadline = dayjs().add( - dayjs.duration(Mocks.MockTemplate.max_ttl_ms / 2, "milliseconds"), + dayjs.duration(Mocks.MockTemplate.default_ttl_ms / 2, "milliseconds"), ) expect( canExtendDeadline(okDeadline, Mocks.MockWorkspace, Mocks.MockTemplate), diff --git a/site/src/util/schedule.ts b/site/src/util/schedule.ts index c96ba02daa47f..4070f8101e388 100644 --- a/site/src/util/schedule.ts +++ b/site/src/util/schedule.ts @@ -139,7 +139,7 @@ export function getMaxDeadline( } const startedAt = dayjs(ws.latest_build.updated_at) const maxTemplateDeadline = startedAt.add( - dayjs.duration(tpl.max_ttl_ms, "milliseconds"), + dayjs.duration(tpl.default_ttl_ms, "milliseconds"), ) const maxGlobalDeadline = startedAt.add(deadlineExtensionMax) return dayjs.min(maxTemplateDeadline, maxGlobalDeadline)