@@ -82,7 +82,13 @@ func templateCreate() *cobra.Command {
82
82
}
83
83
spin .Stop ()
84
84
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
+ })
86
92
if err != nil {
87
93
return err
88
94
}
@@ -98,7 +104,6 @@ func templateCreate() *cobra.Command {
98
104
createReq := codersdk.CreateTemplateRequest {
99
105
Name :templateName ,
100
106
VersionID :job .ID ,
101
- ParameterValues :parameters ,
102
107
MaxTTLMillis :ptr .Ref (maxTTL .Milliseconds ()),
103
108
MinAutostartIntervalMillis :ptr .Ref (minAutostartInterval .Milliseconds ()),
104
109
}
@@ -133,14 +138,34 @@ func templateCreate() *cobra.Command {
133
138
return cmd
134
139
}
135
140
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 ) {
137
156
before := time .Now ()
138
- version ,err := client .CreateTemplateVersion (cmd .Context (),organization .ID , codersdk.CreateTemplateVersionRequest {
157
+ client := args .Client
158
+
159
+ req := codersdk.CreateTemplateVersionRequest {
139
160
StorageMethod :codersdk .ProvisionerStorageMethodFile ,
140
- StorageSource :hash ,
141
- Provisioner :codersdk .ProvisionerType (provisioner ),
161
+ StorageSource :args . FileHash ,
162
+ Provisioner :codersdk .ProvisionerType (args . Provisioner ),
142
163
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 )
144
169
if err != nil {
145
170
return nil ,nil ,err
146
171
}
@@ -175,33 +200,77 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
175
200
return nil ,nil ,err
176
201
}
177
202
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
+
178
223
if provisionerd .IsMissingParameterError (version .Job .Error ) {
179
224
valuesBySchemaID := map [string ]codersdk.TemplateVersionParameter {}
180
225
for _ ,parameterValue := range parameterValues {
181
226
valuesBySchemaID [parameterValue .SchemaID .String ()]= parameterValue
182
227
}
228
+
183
229
sort .Slice (parameterSchemas ,func (i ,j int )bool {
184
230
return parameterSchemas [i ].Name < parameterSchemas [j ].Name
185
231
})
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 )
186
245
missingSchemas := make ([]codersdk.ParameterSchema ,0 )
187
246
for _ ,parameterSchema := range parameterSchemas {
188
247
_ ,ok := valuesBySchemaID [parameterSchema .ID .String ()]
189
248
if ok {
190
249
continue
191
250
}
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 " )
195
251
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
203
263
}
264
+
265
+ missingSchemas = append (missingSchemas ,parameterSchema )
204
266
}
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
+
205
274
for _ ,parameterSchema := range missingSchemas {
206
275
parameterValue ,err := getParameterValueFromMapOrInput (cmd ,parameterMapFromFile ,parameterSchema )
207
276
if err != nil {
@@ -218,7 +287,7 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
218
287
219
288
// This recursion is only 1 level deep in practice.
220
289
// 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 ... )
222
291
}
223
292
224
293
if version .Job .Status != codersdk .ProvisionerJobSucceeded {