Skip to content

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

Merged
merged 18 commits into from
Jun 17, 2022

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Jun 16, 2022

What this does

The main goal of this PR is to prompt the user for new params if they update a template version with new parameters.

How it does this

I looked into a few methods, and honestly it was more complicated than it should have been. The current solution I'm not 100% satisfied with.

The current CLI flow for creating a template is this:

  • Upload the tarball as a file
  • Attempt to create the template version without prompting for params
    • Since this almost always fails (as we need params) an empty template version now exists in the db
  • Read missing params error
  • Prompt missing params
  • Attempt create template version again with params
  • Create template for the said template version

So this process ends up with 2 template versions, 1 of which is never used. The reason this happens is because when you make a template version, the job that runs against the provisioner is async, so we don't know what params we are missing. Add ontop of this that param values should not be returned (or at least secret ones), it makes this new feature have a few restrictions.

Implementation

The current implementation adds a field to CreateParameterRequest called CopyFromParameter which is a uuid that points to another parameter. A list of CreateParameterRequests are sent to the backend with the template_version create.

The CLI then when finding it is missing parameters:

  1. Query the current active template version.
  2. Find it's params in the import_job scope (and only that scope)
  3. Use CopyFromParameter for the missing params.
  4. Prompt for any additional params (like newly added ones)

The backend then can fetch the parameter from CopyFromParameter and check a few things:

  1. Both template_versions share the same template_id (cannot import from other templtes)
  2. Scope is import_job

Alternatives tossed

Retrieve the param values and create the param requests

Secrets should never be readable. So any secrets cannot be pulled to be reinserted this way.

The ui/cli can not therefore "prepopulate" any param fields from the past params. At best we can say "previous value exists", but no always retrieve the value.

Imply inheriting parameters automatically

Another idea is if someone submits a template version that is missing params, the backend can automatically pull those params from the previous active version.

This falls apart. The param missing is checked in the provisioner, which does not have database access. So the job has to fail, and a new job has to be made with the params, which causes 2 job ids for 1 template version? It gets complicated.

The cli/ui is the actor watching the job failure and knows which params are missing. For the backend to implement this, the backend will have to watch for the job failure and act on that. We don't have this async generic job scheduler in the backend, so the implementation seems complicated at best.

Having the cli pass some "inherit all params" indicator, rather than a CopyFromParameter for each needed param

Because the job is async, the backend doesn't know which params are missing when creating a template version. So to do this, we'd always have to inherit all params, even if the params are excessive. This would cause extra params in the db that are no longer defined in the terraform.

@@ -703,17 +716,26 @@ 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) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made 1 query with filter options rather than adding new specific queries

Copy link
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no expert on the params stuff, but I think this looks solid.

Comment on lines 149 to 152
// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 making this opt-in

Copy link
Member Author

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.

ReuseParams bool
}

func createValidTemplateVersion(cmd *cobra.Command, args createValidTemplateVersionArgs, parameters ...codersdk.CreateParameterRequest) (*codersdk.TemplateVersion, []codersdk.CreateParameterRequest, error) {
Copy link
Member

@johnstcn johnstcn Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we're there now :D (args struct)

Copy link
Member Author

Choose a reason for hiding this comment

The 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...

Comment on lines 203 to 222
// 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
}
}

Copy link
Member

Choose a reason for hiding this comment

The 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?
Either way, I think this is probably the expected behaviour; you want to just re-use what you already have.

Copy link
Member Author

Choose a reason for hiding this comment

The 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

Comment on lines +661 to +663
// This is a bit inefficient, as we make a new db call for each
// param.
version, err := db.GetTemplateVersionByJobID(r.Context(), copy.ScopeID)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batch fetch params?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I can make this change if this method is ok with the team

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to punt on this. We only have a handful of params right now. If it's a problem I can optimize later

Comment on lines 641 to 646
inherits := make([]uuid.UUID, 0)
for _, parameterValue := range req.ParameterValues {
if parameterValue.CopyFromParameter != uuid.Nil {
inherits = append(inherits, parameterValue.CopyFromParameter)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just inherit by default if not provided? Then we wouldn't need CopyFromParameter, and I believe we could delete a buncha this!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to go down this route first, but it becomes really challenging in the backend to do this cleanly. The missing parameters are identified in the provisioner in the async job.

So the BE creates the template_version, and returns the cli. The CLI watches the job, reads the missing params error, then fills in the missing params.

To do this inheritance by default in the backend, either:

  1. The provisioner daemon would need to fetch the current active template version to find the missing params during the import_job.
  2. Some backend go routine/process would need to watch the import_job, identify the failure, and rerun the job with updated inherited params.

If I decide to just "always inherit all params" from the last version, then you end up with excessive params. Eg: if the new template version dropped params "a" and "b", it would still inherit these values. These excessive values just seem like a bug.


The implementation I came up with tracks the correct params. Deleted params from the terraform file are not carried forward. New params are prompted to the user.

@Emyrk Emyrk requested a review from kylecarbs June 16, 2022 20:09
Copy link
Member

@kylecarbs kylecarbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had time to run through the CLI experience, but this LGTM!

// succeed.
// No other fields are required if using this, as all fields will be copied
// from the other parameter.
CopyFromParameter uuid.UUID `json:"copy_from_parameter,omitempty" validate:"uuid4"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about CopyFrom instead? Or CloneID? No specific reason, just preference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloneID I like.

@@ -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 != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove the ParameterFile in favor of a --var=asd=asd approach. Separate issue, but I think it conflates some of this logic.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

@Emyrk Emyrk marked this pull request as ready for review June 17, 2022 15:00
@Emyrk Emyrk requested a review from a team as a code owner June 17, 2022 15:00
@Emyrk Emyrk merged commit 64b92ee into main Jun 17, 2022
@Emyrk Emyrk deleted the stevenmasley/template_update_params branch June 17, 2022 17:22
@Emyrk Emyrk changed the title WIP: Allow inheriting parameters from previous template_versions when updating a template feat: Allow inheriting parameters from previous template_versions when updating a template Jun 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants