Skip to content

Commit 76f47da

Browse files
committed
fix: make template push a superset of template create
1 parent e044d3b commit 76f47da

File tree

2 files changed

+182
-15
lines changed

2 files changed

+182
-15
lines changed

cli/templatecreate.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"io"
@@ -46,6 +47,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
4647
r.InitClient(client),
4748
),
4849
Handler: func(inv *clibase.Invocation) error {
50+
<<<<<<< HEAD
4951
isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 || maxTTL != 0
5052

5153
if isTemplateSchedulingOptionsSet || requireActiveVersion {
@@ -79,6 +81,19 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
7981
return xerrors.Errorf("your license is not entitled to use enterprise access control, so you cannot set --require-active-version")
8082
}
8183
}
84+
=======
85+
err := handleEntitlements(inv.Context(), handleEntitlementsArgs{
86+
client: client,
87+
requireActiveVersion: requireActiveVersion,
88+
defaultTTL: defaultTTL,
89+
failureTTL: failureTTL,
90+
dormancyThreshold: dormancyThreshold,
91+
dormancyAutoDeletion: dormancyAutoDeletion,
92+
maxTTL: maxTTL,
93+
})
94+
if err != nil {
95+
return err
96+
>>>>>>> 7b0afe8e9 (fix: make template push a superset of template create)
8297
}
8398

8499
organization, err := CurrentOrganization(inv, client)
@@ -357,3 +372,61 @@ func ParseProvisionerTags(rawTags []string) (map[string]string, error) {
357372
}
358373
return tags, nil
359374
}
375+
376+
type handleEntitlementsArgs struct {
377+
client *codersdk.Client
378+
requireActiveVersion bool
379+
defaultTTL time.Duration
380+
failureTTL time.Duration
381+
dormancyThreshold time.Duration
382+
dormancyAutoDeletion time.Duration
383+
maxTTL time.Duration
384+
}
385+
386+
func handleEntitlements(ctx context.Context, args handleEntitlementsArgs) error {
387+
isTemplateSchedulingOptionsSet := args.failureTTL != 0 || args.dormancyThreshold != 0 || args.dormancyAutoDeletion != 0 || args.maxTTL != 0
388+
389+
if isTemplateSchedulingOptionsSet || args.requireActiveVersion {
390+
if args.failureTTL != 0 || args.dormancyThreshold != 0 || args.dormancyAutoDeletion != 0 {
391+
// This call can be removed when workspace_actions is no longer experimental
392+
experiments, exErr := args.client.Experiments(ctx)
393+
if exErr != nil {
394+
return xerrors.Errorf("get experiments: %w", exErr)
395+
}
396+
397+
if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) {
398+
return xerrors.Errorf("--failure-ttl, --dormancy-threshold, and --dormancy-auto-deletion are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.")
399+
}
400+
}
401+
402+
entitlements, err := args.client.Entitlements(ctx)
403+
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
404+
return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
405+
} else if err != nil {
406+
return xerrors.Errorf("get entitlements: %w", err)
407+
}
408+
409+
if isTemplateSchedulingOptionsSet {
410+
if !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
411+
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, --inactivity-ttl, or --max-ttl")
412+
}
413+
}
414+
415+
if args.requireActiveVersion {
416+
if !entitlements.Features[codersdk.FeatureAccessControl].Enabled {
417+
return xerrors.Errorf("your license is not entitled to use enterprise access control, so you cannot set --require-active-version")
418+
}
419+
420+
experiments, exErr := args.client.Experiments(ctx)
421+
if exErr != nil {
422+
return xerrors.Errorf("get experiments: %w", exErr)
423+
}
424+
425+
if !experiments.Enabled(codersdk.ExperimentTemplateUpdatePolicies) {
426+
return xerrors.Errorf("--require-active-version is an experimental feature, contact an administrator to enable the 'template_update_policies' experiment on your Coder server")
427+
}
428+
}
429+
}
430+
431+
return nil
432+
}

cli/templatepush.go

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package cli
22

33
import (
44
"bufio"
5+
"errors"
56
"fmt"
67
"io"
8+
"net/http"
79
"os"
810
"path/filepath"
911
"strings"
1012
"time"
13+
"unicode/utf8"
1114

1215
"github.com/briandowns/spinner"
1316
"golang.org/x/xerrors"
@@ -158,14 +161,20 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
158161
var (
159162
versionName string
160163
provisioner string
161-
workdir string
162164
variablesFile string
163165
commandLineVariables []string
164166
alwaysPrompt bool
165167
provisionerTags []string
166168
uploadFlags templateUploadFlags
167169
activate bool
168-
create bool
170+
171+
requireActiveVersion bool
172+
disableEveryone bool
173+
defaultTTL time.Duration
174+
failureTTL time.Duration
175+
dormancyThreshold time.Duration
176+
dormancyAutoDeletion time.Duration
177+
maxTTL time.Duration
169178
)
170179
client := new(codersdk.Client)
171180
cmd := &clibase.Cmd{
@@ -176,7 +185,18 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
176185
r.InitClient(client),
177186
),
178187
Handler: func(inv *clibase.Invocation) error {
179-
uploadFlags.setWorkdir(workdir)
188+
err := handleEntitlements(inv.Context(), handleEntitlementsArgs{
189+
client: client,
190+
requireActiveVersion: requireActiveVersion,
191+
defaultTTL: defaultTTL,
192+
failureTTL: failureTTL,
193+
dormancyThreshold: dormancyThreshold,
194+
dormancyAutoDeletion: dormancyAutoDeletion,
195+
maxTTL: maxTTL,
196+
})
197+
if err != nil {
198+
return err
199+
}
180200

181201
organization, err := CurrentOrganization(inv, client)
182202
if err != nil {
@@ -188,10 +208,15 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
188208
return err
189209
}
190210

211+
if utf8.RuneCountInString(name) > 31 {
212+
return xerrors.Errorf("Template name must be less than 32 characters")
213+
}
214+
191215
var createTemplate bool
192216
template, err := client.TemplateByName(inv.Context(), organization.ID, name)
193217
if err != nil {
194-
if !create {
218+
var apiError *codersdk.Error
219+
if errors.As(err, &apiError) && apiError.StatusCode() != http.StatusNotFound {
195220
return err
196221
}
197222
createTemplate = true
@@ -268,6 +293,48 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
268293
}
269294
}
270295

296+
editTemplate := requireActiveVersion ||
297+
disableEveryone ||
298+
defaultTTL != 0 ||
299+
failureTTL != 0 ||
300+
dormancyThreshold != 0 ||
301+
dormancyAutoDeletion != 0 ||
302+
maxTTL != 0
303+
if editTemplate {
304+
if defaultTTL == 0 {
305+
defaultTTL = time.Duration(template.DefaultTTLMillis) * time.Millisecond
306+
}
307+
if failureTTL == 0 {
308+
failureTTL = time.Duration(template.FailureTTLMillis) * time.Millisecond
309+
}
310+
if dormancyThreshold == 0 {
311+
dormancyThreshold = time.Duration(template.TimeTilDormantMillis) * time.Millisecond
312+
}
313+
if dormancyAutoDeletion == 0 {
314+
dormancyAutoDeletion = time.Duration(template.TimeTilDormantAutoDeleteMillis) * time.Millisecond
315+
}
316+
if maxTTL == 0 {
317+
maxTTL = time.Duration(template.MaxTTLMillis) * time.Millisecond
318+
}
319+
req := codersdk.UpdateTemplateMeta{
320+
RequireActiveVersion: requireActiveVersion,
321+
DisableEveryone: disableEveryone,
322+
DefaultTTLMillis: defaultTTL.Milliseconds(),
323+
FailureTTLMillis: failureTTL.Milliseconds(),
324+
TimeTilDormantMillis: dormancyThreshold.Milliseconds(),
325+
TimeTilDormantAutoDeleteMillis: dormancyAutoDeletion.Milliseconds(),
326+
MaxTTLMillis: maxTTL.Milliseconds(),
327+
}
328+
329+
_, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req)
330+
if err != nil {
331+
return xerrors.Errorf("update template metadata: %w", err)
332+
}
333+
if err != nil {
334+
return err
335+
}
336+
}
337+
271338
_, _ = fmt.Fprintf(inv.Stdout, "Updated version at %s!\n", pretty.Sprint(cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp)))
272339
return nil
273340
},
@@ -282,14 +349,6 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
282349
// This is for testing!
283350
Hidden: true,
284351
},
285-
{
286-
Flag: "test.workdir",
287-
Description: "Customize the working directory.",
288-
Default: "",
289-
Value: clibase.StringOf(&workdir),
290-
// This is for testing!
291-
Hidden: true,
292-
},
293352
{
294353
Flag: "variables-file",
295354
Description: "Specify a file path with values for Terraform-managed variables.",
@@ -327,10 +386,45 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
327386
Value: clibase.BoolOf(&activate),
328387
},
329388
{
330-
Flag: "create",
331-
Description: "Create the template if it does not exist.",
389+
Flag: "require-active-version",
390+
Description: "Requires workspace builds to use the active template version. This setting does not apply to template admins. This is an enterprise-only feature.",
391+
Value: clibase.BoolOf(&requireActiveVersion),
332392
Default: "false",
333-
Value: clibase.BoolOf(&create),
393+
},
394+
{
395+
Flag: "default-ttl",
396+
Description: "Specify a default TTL for workspaces created from this template. It is the default time before shutdown - workspaces created from this template default to this value. Maps to \"Default autostop\" in the UI.",
397+
Default: "24h",
398+
Value: clibase.DurationOf(&defaultTTL),
399+
},
400+
{
401+
Flag: "failure-ttl",
402+
Description: "Specify a failure TTL for workspaces created from this template. It is the amount of time after a failed \"start\" build before coder automatically schedules a \"stop\" build to cleanup.This licensed feature's default is 0h (off). Maps to \"Failure cleanup\"in the UI.",
403+
Default: "0h",
404+
Value: clibase.DurationOf(&failureTTL),
405+
},
406+
{
407+
Flag: "dormancy-threshold",
408+
Description: "Specify a duration workspaces may be inactive prior to being moved to the dormant state. This licensed feature's default is 0h (off). Maps to \"Dormancy threshold\" in the UI.",
409+
Default: "0h",
410+
Value: clibase.DurationOf(&dormancyThreshold),
411+
},
412+
{
413+
Flag: "dormancy-auto-deletion",
414+
Description: "Specify a duration workspaces may be in the dormant state prior to being deleted. This licensed feature's default is 0h (off). Maps to \"Dormancy Auto-Deletion\" in the UI.",
415+
Default: "0h",
416+
Value: clibase.DurationOf(&dormancyAutoDeletion),
417+
},
418+
{
419+
Flag: "max-ttl",
420+
Description: "Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting. This is an enterprise-only feature.",
421+
Value: clibase.DurationOf(&maxTTL),
422+
},
423+
{
424+
Flag: "private",
425+
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
426+
"The template permissions must be updated to allow non-admin users to use this template.",
427+
Value: clibase.BoolOf(&disableEveryone),
334428
},
335429
cliui.SkipPromptOption(),
336430
}

0 commit comments

Comments
 (0)