feat: change template max_ttl to default_ttl (#4843)

This commit is contained in:
Garrett Delfosse
2022-11-09 14:36:25 -05:00
committed by GitHub
parent ffc24dcbe0
commit d277e28427
26 changed files with 317 additions and 517 deletions

View File

@ -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

View File

@ -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,

View File

@ -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";

View File

@ -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.';

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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,
}
}

View File

@ -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())
ctr.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds())
})
req := codersdk.UpdateTemplateMeta{
MaxTTLMillis: -1,
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: Must be a positive integer")
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)
})
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())
})
req := codersdk.UpdateTemplateMeta{
MaxTTLMillis: 365 * 24 * time.Hour.Milliseconds(),
}
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")
// 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) {

View File

@ -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,

View File

@ -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{