Skip to content

Commit a5b7723

Browse files
committed
working API and dashboard
1 parent 71e9fe8 commit a5b7723

21 files changed

+181
-29
lines changed

coderd/apidoc/docs.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbfake/databasefake.go

-1
Original file line numberDiff line numberDiff line change
@@ -2615,7 +2615,6 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
26152615
Provisioner: arg.Provisioner,
26162616
ActiveVersionID: arg.ActiveVersionID,
26172617
Description: arg.Description,
2618-
DefaultTTL: arg.DefaultTTL,
26192618
CreatedBy: arg.CreatedBy,
26202619
UserACL: arg.UserACL,
26212620
GroupACL: arg.GroupACL,

coderd/database/dbgen/generator.go

-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
6060
Provisioner: takeFirst(seed.Provisioner, database.ProvisionerTypeEcho),
6161
ActiveVersionID: takeFirst(seed.ActiveVersionID, uuid.New()),
6262
Description: takeFirst(seed.Description, namesgenerator.GetRandomName(1)),
63-
DefaultTTL: takeFirst(seed.DefaultTTL, 3600),
6463
CreatedBy: takeFirst(seed.CreatedBy, uuid.New()),
6564
Icon: takeFirst(seed.Icon, namesgenerator.GetRandomName(1)),
6665
UserACL: seed.UserACL,

coderd/database/modelqueries.go

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
7777
&i.GroupACL,
7878
&i.DisplayName,
7979
&i.AllowUserCancelWorkspaceJobs,
80+
&i.MaxTTL,
8081
); err != nil {
8182
return nil, err
8283
}

coderd/database/queries.sql.go

+2-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/templates.sql

+1-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ INSERT INTO
6767
provisioner,
6868
active_version_id,
6969
description,
70-
default_ttl,
7170
created_by,
7271
icon,
7372
user_acl,
@@ -76,7 +75,7 @@ INSERT INTO
7675
allow_user_cancel_workspace_jobs
7776
)
7877
VALUES
79-
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING *;
78+
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *;
8079

8180
-- name: UpdateTemplateActiveVersionByID :exec
8281
UPDATE

coderd/templates.go

+42-9
Original file line numberDiff line numberDiff line change
@@ -213,16 +213,31 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
213213
return
214214
}
215215

216-
var ttl time.Duration
216+
var (
217+
defaultTTL time.Duration
218+
maxTTL time.Duration
219+
)
217220
if createTemplate.DefaultTTLMillis != nil {
218-
ttl = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond
221+
defaultTTL = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond
222+
}
223+
if createTemplate.MaxTTLMillis != nil {
224+
maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond
225+
}
226+
227+
var validErrs []codersdk.ValidationError
228+
if defaultTTL < 0 {
229+
validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."})
230+
}
231+
if maxTTL < 0 {
232+
validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."})
233+
}
234+
if maxTTL != 0 && defaultTTL > maxTTL {
235+
validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be less than or equal to max_ttl_ms if max_ttl_ms is set."})
219236
}
220-
if ttl < 0 {
237+
if len(validErrs) > 0 {
221238
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
222-
Message: "Invalid create template request.",
223-
Validations: []codersdk.ValidationError{
224-
{Field: "default_ttl_ms", Detail: "Must be a positive integer."},
225-
},
239+
Message: "Invalid create template request.",
240+
Validations: validErrs,
226241
})
227242
return
228243
}
@@ -245,7 +260,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
245260
Provisioner: importJob.Provisioner,
246261
ActiveVersionID: templateVersion.ID,
247262
Description: createTemplate.Description,
248-
DefaultTTL: int64(ttl),
249263
CreatedBy: apiKey.UserID,
250264
UserACL: database.TemplateACL{},
251265
GroupACL: database.TemplateACL{
@@ -259,6 +273,17 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
259273
return xerrors.Errorf("insert template: %s", err)
260274
}
261275

276+
if defaultTTL != 0 || maxTTL != 0 {
277+
dbTemplate, err = (*api.TemplateScheduleStore.Load()).SetTemplateScheduleOptions(ctx, tx, dbTemplate, provisionerdserver.TemplateScheduleOptions{
278+
UserSchedulingEnabled: true,
279+
DefaultTTL: defaultTTL,
280+
MaxTTL: maxTTL,
281+
})
282+
if err != nil {
283+
return xerrors.Errorf("set template schedule options: %s", err)
284+
}
285+
}
286+
262287
templateAudit.New = dbTemplate
263288

264289
err = tx.UpdateTemplateVersionByID(ctx, database.UpdateTemplateVersionByIDParams{
@@ -453,6 +478,12 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
453478
if req.DefaultTTLMillis < 0 {
454479
validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."})
455480
}
481+
if req.MaxTTLMillis < 0 {
482+
validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."})
483+
}
484+
if req.MaxTTLMillis != 0 && req.DefaultTTLMillis > req.MaxTTLMillis {
485+
validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be less than or equal to max_ttl_ms if max_ttl_ms is set."})
486+
}
456487

457488
if len(validErrs) > 0 {
458489
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -469,7 +500,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
469500
req.DisplayName == template.DisplayName &&
470501
req.Icon == template.Icon &&
471502
req.AllowUserCancelWorkspaceJobs == template.AllowUserCancelWorkspaceJobs &&
472-
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() {
503+
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
504+
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() {
473505
return nil
474506
}
475507

@@ -647,6 +679,7 @@ func (api *API) convertTemplate(
647679
Description: template.Description,
648680
Icon: template.Icon,
649681
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
682+
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
650683
CreatedByID: template.CreatedBy,
651684
CreatedByName: createdByName,
652685
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,

coderd/templates_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,13 @@ func TestPatchTemplateMeta(t *testing.T) {
440440
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
441441
require.EqualValues(t, 0, template.MaxTTLMillis)
442442
req := codersdk.UpdateTemplateMeta{
443-
MaxTTLMillis: time.Hour.Milliseconds(),
443+
Name: template.Name,
444+
DisplayName: template.DisplayName,
445+
Description: template.Description,
446+
Icon: template.Icon,
447+
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
448+
DefaultTTLMillis: time.Hour.Milliseconds(),
449+
MaxTTLMillis: (2 * time.Hour).Milliseconds(),
444450
}
445451

446452
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)

codersdk/organizations.go

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ type CreateTemplateRequest struct {
8888
// DefaultTTLMillis allows optionally specifying the default TTL
8989
// for all workspaces created from this template.
9090
DefaultTTLMillis *int64 `json:"default_ttl_ms,omitempty"`
91+
// MaxTTLMillis allows optionally specifying the max lifetime for
92+
// workspaces created from this template.
93+
MaxTTLMillis *int64 `json:"max_ttl_ms,omitempty"`
9194

9295
// Allow users to cancel in-progress workspace jobs.
9396
// *bool as the default value is "true".

docs/api/schemas.md

+2
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
953953
"description": "string",
954954
"display_name": "string",
955955
"icon": "string",
956+
"max_ttl_ms": 0,
956957
"name": "string",
957958
"parameter_values": [
958959
{
@@ -976,6 +977,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
976977
| `description` | string | false | | Description is a description of what the template contains. It must be less than 128 bytes. |
977978
| `display_name` | string | false | | Display name is the displayed name of the template. |
978979
| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
980+
| `max_ttl_ms` | integer | false | | Max ttl ms allows optionally specifying the max lifetime for workspaces created from this template. |
979981
| `name` | string | true | | Name is the name of the template. |
980982
| `parameter_values` | array of [codersdk.CreateParameterRequest](#codersdkcreateparameterrequest) | false | | Parameter values is a structure used to create a new parameter value for a scope.] |
981983
| `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. |

docs/api/templates.md

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
192192
"description": "string",
193193
"display_name": "string",
194194
"icon": "string",
195+
"max_ttl_ms": 0,
195196
"name": "string",
196197
"parameter_values": [
197198
{

enterprise/coderd/templates_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"net/http"
77
"testing"
8+
"time"
89

910
"github.com/google/uuid"
1011
"github.com/stretchr/testify/require"
@@ -21,6 +22,44 @@ import (
2122
"github.com/coder/coder/testutil"
2223
)
2324

25+
func TestTemplates(t *testing.T) {
26+
t.Parallel()
27+
28+
t.Run("SetMaxTTL", func(t *testing.T) {
29+
t.Parallel()
30+
31+
client := coderdenttest.New(t, nil)
32+
user := coderdtest.CreateFirstUser(t, client)
33+
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
34+
Features: license.Features{
35+
codersdk.FeatureAdvancedTemplateScheduling: 1,
36+
},
37+
})
38+
39+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
40+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
41+
require.EqualValues(t, 0, template.MaxTTLMillis)
42+
43+
ctx, _ := testutil.Context(t)
44+
45+
updated, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
46+
Name: template.Name,
47+
DisplayName: template.DisplayName,
48+
Description: template.Description,
49+
Icon: template.Icon,
50+
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
51+
DefaultTTLMillis: time.Hour.Milliseconds(),
52+
MaxTTLMillis: (2 * time.Hour).Milliseconds(),
53+
})
54+
require.NoError(t, err)
55+
require.Equal(t, 2*time.Hour, time.Duration(updated.MaxTTLMillis)*time.Millisecond)
56+
57+
template, err = client.Template(ctx, template.ID)
58+
require.NoError(t, err)
59+
require.Equal(t, 2*time.Hour, time.Duration(template.MaxTTLMillis)*time.Millisecond)
60+
})
61+
}
62+
2463
func TestTemplateACL(t *testing.T) {
2564
t.Parallel()
2665

site/src/api/typesGenerated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export interface CreateTemplateRequest {
179179
readonly template_version_id: string
180180
readonly parameter_values?: CreateParameterRequest[]
181181
readonly default_ttl_ms?: number
182+
readonly max_ttl_ms?: number
182183
readonly allow_user_cancel_workspace_jobs?: boolean
183184
}
184185

site/src/i18n/en/createTemplatePage.json

+2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
"description": "Description",
2828
"icon": "Icon",
2929
"autoStop": "Auto-stop default",
30+
"maxTTL": "Maximum lifetime of workspaces",
3031
"allowUsersToCancel": "Allow users to cancel in-progress workspace jobs"
3132
},
3233
"helperText": {
3334
"autoStop": "Time in hours",
35+
"maxTTL": "Time in hours",
3436
"allowUsersToCancel": "If checked, users may be able to corrupt their workspace."
3537
},
3638
"upload": {

site/src/i18n/en/templateSettingsPage.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
"descriptionLabel": "Description",
66
"descriptionMaxError": "Please enter a description that is less than or equal to 128 characters.",
77
"defaultTtlLabel": "Auto-stop default",
8+
"maxTtlLabel": "Maximum lifetime for started workspaces",
89
"iconLabel": "Icon",
910
"formAriaLabel": "Template settings form",
1011
"selectEmoji": "Select emoji",
1112
"ttlMaxError": "Please enter a limit that is less than or equal to 168 hours (7 days).",
1213
"ttlMinError": "Default time until auto-stop must not be less than 0.",
13-
"ttlHelperText_zero": "Workspaces created from this template will run until stopped manually.",
14-
"ttlHelperText_one": "Workspaces created from this template will default to stopping after {{count}} hour.",
15-
"ttlHelperText_other": "Workspaces created from this template will default to stopping after {{count}} hours.",
14+
"defaultTTLHelperText_zero": "Workspaces created from this template will run until stopped manually.",
15+
"defaultTTLHelperText_one": "Workspaces created from this template will default to stopping after {{count}} hour.",
16+
"defaultTTLHelperText_other": "Workspaces created from this template will default to stopping after {{count}} hours.",
17+
"maxTTLHelperText_zero": "Workspaces created from this template may run indefinitely.",
18+
"maxTTLHelperText_one": "Workspaces created from this template must stop within {{count}} hour of starting.",
19+
"maxTTLHelperText_other": "Workspaces created from this template must stop within {{count}} hours of starting.",
1620
"allowUserCancelWorkspaceJobsLabel": "Allow users to cancel in-progress workspace jobs.",
1721
"allowUserCancelWorkspaceJobsNotice": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases.",
1822
"dangerZone": {

0 commit comments

Comments
 (0)