Skip to content

Commit 64b92ee

Browse files
authored
feat: Allow inheriting parameters from previous template_versions when updating a template (#2397)
* WIP: feat: Update templates also updates parameters * Insert params for template version update * Working implementation of inherited params * Add "--always-prompt" flag and logging info
1 parent 18973a6 commit 64b92ee

File tree

16 files changed

+551
-171
lines changed

16 files changed

+551
-171
lines changed

cli/parameterslist.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,23 @@ func parameterList() *cobra.Command {
4545
return xerrors.Errorf("get workspace template: %w", err)
4646
}
4747
scopeID = template.ID
48-
49-
case codersdk.ParameterScopeImportJob, "template_version":
50-
scope = string(codersdk.ParameterScopeImportJob)
48+
case codersdk.ParameterImportJob, "template_version":
49+
scope = string(codersdk.ParameterImportJob)
5150
scopeID, err = uuid.Parse(name)
5251
if err != nil {
5352
return xerrors.Errorf("%q must be a uuid for this scope type", name)
5453
}
54+
55+
// Could be a template_version id or a job id. Check for the
56+
// version id.
57+
tv, err := client.TemplateVersion(cmd.Context(), scopeID)
58+
if err == nil {
59+
scopeID = tv.Job.ID
60+
}
61+
5562
default:
5663
return xerrors.Errorf("%q is an unsupported scope, use %v", scope, []codersdk.ParameterScope{
57-
codersdk.ParameterWorkspace, codersdk.ParameterTemplate, codersdk.ParameterScopeImportJob,
64+
codersdk.ParameterWorkspace, codersdk.ParameterTemplate, codersdk.ParameterImportJob,
5865
})
5966
}
6067

cli/templatecreate.go

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,13 @@ func templateCreate() *cobra.Command {
8282
}
8383
spin.Stop()
8484

85-
job, parameters, err := createValidTemplateVersion(cmd, client, organization, database.ProvisionerType(provisioner), resp.Hash, parameterFile)
85+
job, _, err := createValidTemplateVersion(cmd, createValidTemplateVersionArgs{
86+
Client: client,
87+
Organization: organization,
88+
Provisioner: database.ProvisionerType(provisioner),
89+
FileHash: resp.Hash,
90+
ParameterFile: parameterFile,
91+
})
8692
if err != nil {
8793
return err
8894
}
@@ -98,7 +104,6 @@ func templateCreate() *cobra.Command {
98104
createReq := codersdk.CreateTemplateRequest{
99105
Name: templateName,
100106
VersionID: job.ID,
101-
ParameterValues: parameters,
102107
MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()),
103108
MinAutostartIntervalMillis: ptr.Ref(minAutostartInterval.Milliseconds()),
104109
}
@@ -133,14 +138,34 @@ func templateCreate() *cobra.Command {
133138
return cmd
134139
}
135140

136-
func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, organization codersdk.Organization, provisioner database.ProvisionerType, hash string, parameterFile string, parameters ...codersdk.CreateParameterRequest) (*codersdk.TemplateVersion, []codersdk.CreateParameterRequest, error) {
141+
type createValidTemplateVersionArgs struct {
142+
Client *codersdk.Client
143+
Organization codersdk.Organization
144+
Provisioner database.ProvisionerType
145+
FileHash string
146+
ParameterFile string
147+
// Template is only required if updating a template's active version.
148+
Template *codersdk.Template
149+
// ReuseParameters will attempt to reuse params from the Template field
150+
// before prompting the user. Set to false to always prompt for param
151+
// values.
152+
ReuseParameters bool
153+
}
154+
155+
func createValidTemplateVersion(cmd *cobra.Command, args createValidTemplateVersionArgs, parameters ...codersdk.CreateParameterRequest) (*codersdk.TemplateVersion, []codersdk.CreateParameterRequest, error) {
137156
before := time.Now()
138-
version, err := client.CreateTemplateVersion(cmd.Context(), organization.ID, codersdk.CreateTemplateVersionRequest{
157+
client := args.Client
158+
159+
req := codersdk.CreateTemplateVersionRequest{
139160
StorageMethod: codersdk.ProvisionerStorageMethodFile,
140-
StorageSource: hash,
141-
Provisioner: codersdk.ProvisionerType(provisioner),
161+
StorageSource: args.FileHash,
162+
Provisioner: codersdk.ProvisionerType(args.Provisioner),
142163
ParameterValues: parameters,
143-
})
164+
}
165+
if args.Template != nil {
166+
req.TemplateID = args.Template.ID
167+
}
168+
version, err := client.CreateTemplateVersion(cmd.Context(), args.Organization.ID, req)
144169
if err != nil {
145170
return nil, nil, err
146171
}
@@ -175,33 +200,77 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
175200
return nil, nil, err
176201
}
177202

203+
// lastParameterValues are pulled from the current active template version if
204+
// templateID is provided. This allows pulling params from the last
205+
// version instead of prompting if we are updating template versions.
206+
lastParameterValues := make(map[string]codersdk.Parameter)
207+
if args.ReuseParameters && args.Template != nil {
208+
activeVersion, err := client.TemplateVersion(cmd.Context(), args.Template.ActiveVersionID)
209+
if err != nil {
210+
return nil, nil, xerrors.Errorf("Fetch current active template version: %w", err)
211+
}
212+
213+
// We don't want to compute the params, we only want to copy from this scope
214+
values, err := client.Parameters(cmd.Context(), codersdk.ParameterImportJob, activeVersion.Job.ID)
215+
if err != nil {
216+
return nil, nil, xerrors.Errorf("Fetch previous version parameters: %w", err)
217+
}
218+
for _, value := range values {
219+
lastParameterValues[value.Name] = value
220+
}
221+
}
222+
178223
if provisionerd.IsMissingParameterError(version.Job.Error) {
179224
valuesBySchemaID := map[string]codersdk.TemplateVersionParameter{}
180225
for _, parameterValue := range parameterValues {
181226
valuesBySchemaID[parameterValue.SchemaID.String()] = parameterValue
182227
}
228+
183229
sort.Slice(parameterSchemas, func(i, j int) bool {
184230
return parameterSchemas[i].Name < parameterSchemas[j].Name
185231
})
232+
233+
// parameterMapFromFile can be nil if parameter file is not specified
234+
var parameterMapFromFile map[string]string
235+
if args.ParameterFile != "" {
236+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Attempting to read the variables from the parameter file.")+"\r\n")
237+
parameterMapFromFile, err = createParameterMapFromFile(args.ParameterFile)
238+
if err != nil {
239+
return nil, nil, err
240+
}
241+
}
242+
243+
// pulled params come from the last template version
244+
pulled := make([]string, 0)
186245
missingSchemas := make([]codersdk.ParameterSchema, 0)
187246
for _, parameterSchema := range parameterSchemas {
188247
_, ok := valuesBySchemaID[parameterSchema.ID.String()]
189248
if ok {
190249
continue
191250
}
192-
missingSchemas = append(missingSchemas, parameterSchema)
193-
}
194-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has required variables! They are scoped to the template, and not viewable after being set.")+"\r\n")
195251

196-
// parameterMapFromFile can be nil if parameter file is not specified
197-
var parameterMapFromFile map[string]string
198-
if parameterFile != "" {
199-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Attempting to read the variables from the parameter file.")+"\r\n")
200-
parameterMapFromFile, err = createParameterMapFromFile(parameterFile)
201-
if err != nil {
202-
return nil, nil, err
252+
// The file values are handled below. So don't handle them here,
253+
// just check if a value is present in the file.
254+
_, fileOk := parameterMapFromFile[parameterSchema.Name]
255+
if inherit, ok := lastParameterValues[parameterSchema.Name]; ok && !fileOk {
256+
// If the value is not in the param file, and can be pulled from the last template version,
257+
// then don't mark it as missing.
258+
parameters = append(parameters, codersdk.CreateParameterRequest{
259+
CloneID: inherit.ID,
260+
})
261+
pulled = append(pulled, fmt.Sprintf("%q", parameterSchema.Name))
262+
continue
203263
}
264+
265+
missingSchemas = append(missingSchemas, parameterSchema)
204266
}
267+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has required variables! They are scoped to the template, and not viewable after being set."))
268+
if len(pulled) > 0 {
269+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(fmt.Sprintf("The following parameter values are being pulled from the latest template version: %s.", strings.Join(pulled, ", "))))
270+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Use \"--always-prompt\" flag to change the values."))
271+
}
272+
_, _ = fmt.Fprint(cmd.OutOrStdout(), "\r\n")
273+
205274
for _, parameterSchema := range missingSchemas {
206275
parameterValue, err := getParameterValueFromMapOrInput(cmd, parameterMapFromFile, parameterSchema)
207276
if err != nil {
@@ -218,7 +287,7 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
218287

219288
// This recursion is only 1 level deep in practice.
220289
// The first pass populates the missing parameters, so it does not enter this `if` block again.
221-
return createValidTemplateVersion(cmd, client, organization, provisioner, hash, parameterFile, parameters...)
290+
return createValidTemplateVersion(cmd, args, parameters...)
222291
}
223292

224293
if version.Job.Status != codersdk.ProvisionerJobSucceeded {

cli/templateupdate.go

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import (
1010
"golang.org/x/xerrors"
1111

1212
"github.com/coder/coder/cli/cliui"
13+
"github.com/coder/coder/coderd/database"
1314
"github.com/coder/coder/codersdk"
1415
"github.com/coder/coder/provisionersdk"
1516
)
1617

1718
func templateUpdate() *cobra.Command {
1819
var (
19-
directory string
20-
provisioner string
20+
directory string
21+
provisioner string
22+
parameterFile string
23+
alwaysPrompt bool
2124
)
2225

2326
cmd := &cobra.Command{
@@ -64,42 +67,30 @@ func templateUpdate() *cobra.Command {
6467
}
6568
spin.Stop()
6669

67-
before := time.Now()
68-
templateVersion, err := client.CreateTemplateVersion(cmd.Context(), organization.ID, codersdk.CreateTemplateVersionRequest{
69-
TemplateID: template.ID,
70-
StorageMethod: codersdk.ProvisionerStorageMethodFile,
71-
StorageSource: resp.Hash,
72-
Provisioner: codersdk.ProvisionerType(provisioner),
70+
job, _, err := createValidTemplateVersion(cmd, createValidTemplateVersionArgs{
71+
Client: client,
72+
Organization: organization,
73+
Provisioner: database.ProvisionerType(provisioner),
74+
FileHash: resp.Hash,
75+
ParameterFile: parameterFile,
76+
Template: &template,
77+
ReuseParameters: !alwaysPrompt,
7378
})
7479
if err != nil {
7580
return err
7681
}
77-
logs, err := client.TemplateVersionLogsAfter(cmd.Context(), templateVersion.ID, before)
78-
if err != nil {
79-
return err
80-
}
81-
for {
82-
log, ok := <-logs
83-
if !ok {
84-
break
85-
}
86-
_, _ = fmt.Printf("%s (%s): %s\n", provisioner, log.Level, log.Output)
87-
}
88-
templateVersion, err = client.TemplateVersion(cmd.Context(), templateVersion.ID)
89-
if err != nil {
90-
return err
91-
}
9282

93-
if templateVersion.Job.Status != codersdk.ProvisionerJobSucceeded {
94-
return xerrors.Errorf("job failed: %s", templateVersion.Job.Error)
83+
if job.Job.Status != codersdk.ProvisionerJobSucceeded {
84+
return xerrors.Errorf("job failed: %s", job.Job.Status)
9585
}
9686

9787
err = client.UpdateActiveTemplateVersion(cmd.Context(), template.ID, codersdk.UpdateActiveTemplateVersion{
98-
ID: templateVersion.ID,
88+
ID: job.ID,
9989
})
10090
if err != nil {
10191
return err
10292
}
93+
10394
_, _ = fmt.Printf("Updated version!\n")
10495
return nil
10596
},
@@ -108,6 +99,8 @@ func templateUpdate() *cobra.Command {
10899
currentDirectory, _ := os.Getwd()
109100
cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from")
110101
cmd.Flags().StringVarP(&provisioner, "test.provisioner", "", "terraform", "Customize the provisioner backend")
102+
cmd.Flags().StringVarP(&parameterFile, "parameter-file", "", "", "Specify a file path with parameter values.")
103+
cmd.Flags().BoolVar(&alwaysPrompt, "always-prompt", false, "Always prompt all parameters. Does not pull parameter values from active template version")
111104
cliui.AllowSkipPrompt(cmd)
112105
// This is for testing!
113106
err := cmd.Flags().MarkHidden("test.provisioner")

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy