Skip to content

Commit bc80f7d

Browse files
committed
Add back create
1 parent b3927c9 commit bc80f7d

File tree

6 files changed

+761
-10
lines changed

6 files changed

+761
-10
lines changed

cli/templatecreate.go

Lines changed: 241 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,259 @@ package cli
22

33
import (
44
"fmt"
5+
"net/http"
6+
"time"
7+
"unicode/utf8"
8+
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/pretty"
512

613
"github.com/coder/coder/v2/cli/clibase"
714
"github.com/coder/coder/v2/cli/cliui"
8-
"github.com/coder/pretty"
15+
"github.com/coder/coder/v2/coderd/util/ptr"
16+
"github.com/coder/coder/v2/codersdk"
917
)
1018

11-
// TODO(f0ssel): This should be removed a few versions after coder 2.7.0 has been released.
12-
func (*RootCmd) templateCreate() *clibase.Cmd {
19+
func (r *RootCmd) templateCreate() *clibase.Cmd {
20+
var (
21+
provisioner string
22+
provisionerTags []string
23+
variablesFile string
24+
commandLineVariables []string
25+
disableEveryone bool
26+
requireActiveVersion bool
27+
28+
defaultTTL time.Duration
29+
failureTTL time.Duration
30+
dormancyThreshold time.Duration
31+
dormancyAutoDeletion time.Duration
32+
maxTTL time.Duration
33+
34+
uploadFlags templateUploadFlags
35+
)
36+
client := new(codersdk.Client)
1337
cmd := &clibase.Cmd{
14-
Use: "create [name]",
15-
Short: "Create a template from the current directory or as specified by flag",
16-
Hidden: true,
38+
Use: "create [name]",
39+
Short: "Create a template from the current directory or as specified by flag",
40+
Middleware: clibase.Chain(
41+
clibase.RequireRangeArgs(0, 1),
42+
r.InitClient(client),
43+
),
1744
Handler: func(inv *clibase.Invocation) error {
1845
_, _ = fmt.Fprintln(inv.Stdout, "\n"+pretty.Sprint(cliui.DefaultStyles.Wrap,
1946
pretty.Sprint(
20-
cliui.DefaultStyles.Error,
21-
"ERROR: The `coder templates create` command has been removed. "+
22-
"Use the `coder templates push` command to create and update templates. "+
23-
"Use the `coder templates edit` command to change template settings.")))
47+
cliui.DefaultStyles.Warn,
48+
"DEPRECATION WARNING: Use `coder templates push` command for creating and updating templates. "+
49+
"Use `coder templates edit` command for editing template settings."+
50+
"This command will be removed in a future release."+
51+
"Waiting 1 second...")+"\n"))
52+
time.Sleep(1 * time.Second)
53+
54+
isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 || maxTTL != 0
55+
56+
if isTemplateSchedulingOptionsSet || requireActiveVersion {
57+
if failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 {
58+
// This call can be removed when workspace_actions is no longer experimental
59+
experiments, exErr := client.Experiments(inv.Context())
60+
if exErr != nil {
61+
return xerrors.Errorf("get experiments: %w", exErr)
62+
}
63+
64+
if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) {
65+
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.")
66+
}
67+
}
68+
69+
entitlements, err := client.Entitlements(inv.Context())
70+
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
71+
return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
72+
} else if err != nil {
73+
return xerrors.Errorf("get entitlements: %w", err)
74+
}
75+
76+
if isTemplateSchedulingOptionsSet {
77+
if !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
78+
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, --inactivity-ttl, or --max-ttl")
79+
}
80+
}
81+
82+
if requireActiveVersion {
83+
if !entitlements.Features[codersdk.FeatureAccessControl].Enabled {
84+
return xerrors.Errorf("your license is not entitled to use enterprise access control, so you cannot set --require-active-version")
85+
}
86+
}
87+
}
88+
89+
organization, err := CurrentOrganization(inv, client)
90+
if err != nil {
91+
return err
92+
}
93+
94+
templateName, err := uploadFlags.templateName(inv.Args)
95+
if err != nil {
96+
return err
97+
}
98+
99+
if utf8.RuneCountInString(templateName) > 31 {
100+
return xerrors.Errorf("Template name must be less than 32 characters")
101+
}
102+
103+
_, err = client.TemplateByName(inv.Context(), organization.ID, templateName)
104+
if err == nil {
105+
return xerrors.Errorf("A template already exists named %q!", templateName)
106+
}
107+
108+
err = uploadFlags.checkForLockfile(inv)
109+
if err != nil {
110+
return xerrors.Errorf("check for lockfile: %w", err)
111+
}
112+
113+
message := uploadFlags.templateMessage(inv)
114+
115+
// Confirm upload of the directory.
116+
resp, err := uploadFlags.upload(inv, client)
117+
if err != nil {
118+
return err
119+
}
120+
121+
tags, err := ParseProvisionerTags(provisionerTags)
122+
if err != nil {
123+
return err
124+
}
125+
126+
userVariableValues, err := ParseUserVariableValues(
127+
variablesFile,
128+
commandLineVariables)
129+
if err != nil {
130+
return err
131+
}
132+
133+
job, err := createValidTemplateVersion(inv, createValidTemplateVersionArgs{
134+
Message: message,
135+
Client: client,
136+
Organization: organization,
137+
Provisioner: codersdk.ProvisionerType(provisioner),
138+
FileID: resp.ID,
139+
ProvisionerTags: tags,
140+
UserVariableValues: userVariableValues,
141+
})
142+
if err != nil {
143+
return err
144+
}
145+
146+
if !uploadFlags.stdin() {
147+
_, err = cliui.Prompt(inv, cliui.PromptOptions{
148+
Text: "Confirm create?",
149+
IsConfirm: true,
150+
})
151+
if err != nil {
152+
return err
153+
}
154+
}
155+
156+
createReq := codersdk.CreateTemplateRequest{
157+
Name: templateName,
158+
VersionID: job.ID,
159+
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
160+
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
161+
MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()),
162+
TimeTilDormantMillis: ptr.Ref(dormancyThreshold.Milliseconds()),
163+
TimeTilDormantAutoDeleteMillis: ptr.Ref(dormancyAutoDeletion.Milliseconds()),
164+
DisableEveryoneGroupAccess: disableEveryone,
165+
RequireActiveVersion: requireActiveVersion,
166+
}
167+
168+
_, err = client.CreateTemplate(inv.Context(), organization.ID, createReq)
169+
if err != nil {
170+
return err
171+
}
172+
173+
_, _ = fmt.Fprintln(inv.Stdout, "\n"+pretty.Sprint(cliui.DefaultStyles.Wrap,
174+
"The "+pretty.Sprint(
175+
cliui.DefaultStyles.Keyword, templateName)+" template has been created at "+
176+
pretty.Sprint(cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp))+"! "+
177+
"Developers can provision a workspace with this template using:")+"\n")
178+
179+
_, _ = fmt.Fprintln(inv.Stdout, " "+pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("coder create --template=%q [workspace name]", templateName)))
180+
_, _ = fmt.Fprintln(inv.Stdout)
181+
24182
return nil
25183
},
26184
}
185+
cmd.Options = clibase.OptionSet{
186+
{
187+
Flag: "private",
188+
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
189+
"The template permissions must be updated to allow non-admin users to use this template.",
190+
Value: clibase.BoolOf(&disableEveryone),
191+
},
192+
{
193+
Flag: "variables-file",
194+
Description: "Specify a file path with values for Terraform-managed variables.",
195+
Value: clibase.StringOf(&variablesFile),
196+
},
197+
{
198+
Flag: "variable",
199+
Description: "Specify a set of values for Terraform-managed variables.",
200+
Value: clibase.StringArrayOf(&commandLineVariables),
201+
},
202+
{
203+
Flag: "var",
204+
Description: "Alias of --variable.",
205+
Value: clibase.StringArrayOf(&commandLineVariables),
206+
},
207+
{
208+
Flag: "provisioner-tag",
209+
Description: "Specify a set of tags to target provisioner daemons.",
210+
Value: clibase.StringArrayOf(&provisionerTags),
211+
},
212+
{
213+
Flag: "default-ttl",
214+
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.",
215+
Default: "24h",
216+
Value: clibase.DurationOf(&defaultTTL),
217+
},
218+
{
219+
Flag: "failure-ttl",
220+
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.",
221+
Default: "0h",
222+
Value: clibase.DurationOf(&failureTTL),
223+
},
224+
{
225+
Flag: "dormancy-threshold",
226+
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.",
227+
Default: "0h",
228+
Value: clibase.DurationOf(&dormancyThreshold),
229+
},
230+
{
231+
Flag: "dormancy-auto-deletion",
232+
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.",
233+
Default: "0h",
234+
Value: clibase.DurationOf(&dormancyAutoDeletion),
235+
},
27236

237+
{
238+
Flag: "max-ttl",
239+
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.",
240+
Value: clibase.DurationOf(&maxTTL),
241+
},
242+
{
243+
Flag: "test.provisioner",
244+
Description: "Customize the provisioner backend.",
245+
Default: "terraform",
246+
Value: clibase.StringOf(&provisioner),
247+
Hidden: true,
248+
},
249+
{
250+
Flag: "require-active-version",
251+
Description: "Requires workspace builds to use the active template version. This setting does not apply to template admins. This is an enterprise-only feature.",
252+
Value: clibase.BoolOf(&requireActiveVersion),
253+
Default: "false",
254+
},
255+
256+
cliui.SkipPromptOption(),
257+
}
258+
cmd.Options = append(cmd.Options, uploadFlags.options()...)
28259
return cmd
29260
}

0 commit comments

Comments
 (0)