-
Notifications
You must be signed in to change notification settings - Fork 894
feat: Allow inheriting parameters from previous template_versions when updating a template #2397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
ed33a1c
1f2892c
8d62c96
aceca62
effbde7
e569f0f
2e5bf44
e5b90bc
9d17dac
04098c3
3422844
5f881aa
bcca07a
c43aedc
fa9b230
9438db8
5c590ae
f46b4af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,7 +82,13 @@ func templateCreate() *cobra.Command { | |
} | ||
spin.Stop() | ||
|
||
job, parameters, err := createValidTemplateVersion(cmd, client, organization, database.ProvisionerType(provisioner), resp.Hash, parameterFile) | ||
job, _, err := createValidTemplateVersion(cmd, createValidTemplateVersionArgs{ | ||
Client: client, | ||
Organization: organization, | ||
Provisioner: database.ProvisionerType(provisioner), | ||
FileHash: resp.Hash, | ||
ParameterFile: parameterFile, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -98,7 +104,6 @@ func templateCreate() *cobra.Command { | |
createReq := codersdk.CreateTemplateRequest{ | ||
Name: templateName, | ||
VersionID: job.ID, | ||
ParameterValues: parameters, | ||
MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()), | ||
MinAutostartIntervalMillis: ptr.Ref(minAutostartInterval.Milliseconds()), | ||
} | ||
|
@@ -133,14 +138,34 @@ func templateCreate() *cobra.Command { | |
return cmd | ||
} | ||
|
||
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) { | ||
type createValidTemplateVersionArgs struct { | ||
Client *codersdk.Client | ||
Organization codersdk.Organization | ||
Provisioner database.ProvisionerType | ||
FileHash string | ||
ParameterFile string | ||
// Template is only required if updating a template's active version. | ||
Template *codersdk.Template | ||
// ReuseParams will attempt to reuse params from the Template field | ||
// before prompting the user. Set to false to always prompt for param | ||
// values. | ||
ReuseParams bool | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
func createValidTemplateVersion(cmd *cobra.Command, args createValidTemplateVersionArgs, parameters ...codersdk.CreateParameterRequest) (*codersdk.TemplateVersion, []codersdk.CreateParameterRequest, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah we're there now :D (args struct) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really want to try refactoring this function. It's growing limbs and becoming quite large lol. "Get it working" was my first step... |
||
before := time.Now() | ||
version, err := client.CreateTemplateVersion(cmd.Context(), organization.ID, codersdk.CreateTemplateVersionRequest{ | ||
client := args.Client | ||
|
||
req := codersdk.CreateTemplateVersionRequest{ | ||
StorageMethod: codersdk.ProvisionerStorageMethodFile, | ||
StorageSource: hash, | ||
Provisioner: codersdk.ProvisionerType(provisioner), | ||
StorageSource: args.FileHash, | ||
Provisioner: codersdk.ProvisionerType(args.Provisioner), | ||
ParameterValues: parameters, | ||
}) | ||
} | ||
if args.Template != nil { | ||
req.TemplateID = args.Template.ID | ||
} | ||
version, err := client.CreateTemplateVersion(cmd.Context(), args.Organization.ID, req) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
@@ -175,11 +200,32 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org | |
return nil, nil, err | ||
} | ||
|
||
// lastParameterValues are pulled from the current active template version if | ||
// templateID is provided. This allows pulling params from the last | ||
// version instead of prompting if we are updating template versions. | ||
lastParameterValues := make(map[string]codersdk.Parameter) | ||
if args.ReuseParams && args.Template != nil { | ||
activeVersion, err := client.TemplateVersion(cmd.Context(), args.Template.ActiveVersionID) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("Fetch current active template version: %w", err) | ||
} | ||
|
||
// We don't want to compute the params, we only want to copy from this scope | ||
values, err := client.Parameters(cmd.Context(), codersdk.ParameterImportJob, activeVersion.Job.ID) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("Fetch previous version parameters: %w", err) | ||
} | ||
for _, value := range values { | ||
lastParameterValues[value.Name] = value | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question (non-blocking): is the "currently active" template version always guaranteed to be the "last" one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it's not always the last one. This is actually intentional, as if you upload a template_version that fails, it won't become the "active" template, but it will be the "last" template. So the "active" template is usually the last working template version |
||
if provisionerd.IsMissingParameterError(version.Job.Error) { | ||
valuesBySchemaID := map[string]codersdk.TemplateVersionParameter{} | ||
for _, parameterValue := range parameterValues { | ||
valuesBySchemaID[parameterValue.SchemaID.String()] = parameterValue | ||
} | ||
|
||
sort.Slice(parameterSchemas, func(i, j int) bool { | ||
return parameterSchemas[i].Name < parameterSchemas[j].Name | ||
}) | ||
|
@@ -195,14 +241,22 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org | |
|
||
// parameterMapFromFile can be nil if parameter file is not specified | ||
var parameterMapFromFile map[string]string | ||
if parameterFile != "" { | ||
if args.ParameterFile != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file logic needs to be looked at for sure. I kept it as is for now. |
||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Attempting to read the variables from the parameter file.")+"\r\n") | ||
parameterMapFromFile, err = createParameterMapFromFile(parameterFile) | ||
parameterMapFromFile, err = createParameterMapFromFile(args.ParameterFile) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
for _, parameterSchema := range missingSchemas { | ||
// If the value is in the file, skip trying to reuse the param | ||
_, fileOk := parameterMapFromFile[parameterSchema.Name] | ||
if inherit, ok := lastParameterValues[parameterSchema.Name]; ok && !fileOk { | ||
parameters = append(parameters, codersdk.CreateParameterRequest{ | ||
CopyFromParameter: inherit.ID, | ||
}) | ||
continue | ||
} | ||
parameterValue, err := getParameterValueFromMapOrInput(cmd, parameterMapFromFile, parameterSchema) | ||
if err != nil { | ||
return nil, nil, err | ||
|
@@ -218,7 +272,7 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org | |
|
||
// This recursion is only 1 level deep in practice. | ||
// The first pass populates the missing parameters, so it does not enter this `if` block again. | ||
return createValidTemplateVersion(cmd, client, organization, provisioner, hash, parameterFile, parameters...) | ||
return createValidTemplateVersion(cmd, args, parameters...) | ||
} | ||
|
||
if version.Job.Status != codersdk.ProvisionerJobSucceeded { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,19 @@ func (q *fakeQuerier) AcquireProvisionerJob(_ context.Context, arg database.Acqu | |
return database.ProvisionerJob{}, sql.ErrNoRows | ||
} | ||
|
||
func (q *fakeQuerier) ParameterValue(_ context.Context, id uuid.UUID) (database.ParameterValue, error) { | ||
q.mutex.Lock() | ||
defer q.mutex.Unlock() | ||
|
||
for _, parameterValue := range q.parameterValues { | ||
if parameterValue.ID.String() != id.String() { | ||
continue | ||
} | ||
return parameterValue, nil | ||
} | ||
return database.ParameterValue{}, sql.ErrNoRows | ||
} | ||
|
||
func (q *fakeQuerier) DeleteParameterValueByID(_ context.Context, id uuid.UUID) error { | ||
q.mutex.Lock() | ||
defer q.mutex.Unlock() | ||
|
@@ -703,17 +716,27 @@ func (q *fakeQuerier) GetOrganizationsByUserID(_ context.Context, userID uuid.UU | |
return organizations, nil | ||
} | ||
|
||
func (q *fakeQuerier) GetParameterValuesByScope(_ context.Context, arg database.GetParameterValuesByScopeParams) ([]database.ParameterValue, error) { | ||
func (q *fakeQuerier) ParameterValues(_ context.Context, arg database.ParameterValuesParams) ([]database.ParameterValue, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made 1 query with filter options rather than adding new specific queries |
||
q.mutex.RLock() | ||
defer q.mutex.RUnlock() | ||
|
||
parameterValues := make([]database.ParameterValue, 0) | ||
for _, parameterValue := range q.parameterValues { | ||
if parameterValue.Scope != arg.Scope { | ||
continue | ||
if len(arg.Scopes) > 0 { | ||
if !contains(arg.Scopes, parameterValue.Scope) { | ||
continue | ||
} | ||
} | ||
if parameterValue.ScopeID != arg.ScopeID { | ||
continue | ||
if len(arg.ScopeIds) > 0 { | ||
if !contains(arg.ScopeIds, parameterValue.ScopeID) { | ||
continue | ||
} | ||
} | ||
|
||
if len(arg.Ids) > 0 { | ||
if !contains(arg.Ids, parameterValue.ID) { | ||
continue | ||
} | ||
} | ||
parameterValues = append(parameterValues, parameterValue) | ||
} | ||
|
@@ -1966,3 +1989,12 @@ func (q *fakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAudit | |
|
||
return alog, nil | ||
} | ||
|
||
func contains[T comparable](haystack []T, needle T) bool { | ||
for _, hay := range haystack { | ||
if needle == hay { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 making this opt-in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to add some kind of
--always-prompt
flag if you want to change the params.