Skip to content
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

feat(*): add installation delete options/flags to relevant commands #1189

Conversation

vdice
Copy link
Member

@vdice vdice commented Jul 31, 2020

What does this change

  • Adds logic to enable deleting an installation, either via porter uninstall or porter installation delete (as designed in Do we need a purge? #1163)

What issue does it fix

Closes #1163

Notes for the reviewer

  • Do we want any additional docs besides the auto-gen'd CLI docs?
  • Suggestions for simplifying logic around uninstall +/- delete +/- force-delete welcome.
  • Note how we are swallowing errors around the execution of the uninstall action if force-delete is true -- do we like this approach? Or should we continue to return any/all execution errors after we force delete the installation?

Checklist

  • Unit Tests
  • Documentation
  • Schema (porter.yaml) N/A

If this is your first pull request, please add your name to the bottom of our Contributors list. Thank you for making Porter better! 🙇‍♀️

@carolynvs carolynvs added this to In Progress in Porter and Mixins [OLD] via automation Aug 4, 2020
Copy link
Member

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

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

I wasn't able to finish reviewing but here are some initial comments

cmd/porter/bundle.go Outdated Show resolved Hide resolved
cmd/porter/installations.go Outdated Show resolved Hide resolved
cmd/porter/installations.go Outdated Show resolved Hide resolved
@@ -45,6 +45,8 @@ func (t *TestRuntime) LoadBundle(bundleFile string) (bundle.Bundle, error) {
}

func (t *TestRuntime) Execute(args ActionArguments) error {
args.Driver = debugDriver
if args.Driver == "" {
Copy link
Member

Choose a reason for hiding this comment

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

👍

pkg/context/helpers.go Outdated Show resolved Hide resolved
pkg/porter/delete.go Outdated Show resolved Hide resolved
pkg/porter/delete.go Outdated Show resolved Hide resolved
pkg/porter/delete.go Outdated Show resolved Hide resolved
pkg/porter/delete_test.go Show resolved Hide resolved
pkg/porter/delete_test.go Outdated Show resolved Hide resolved
@vdice vdice force-pushed the feat/the-life-changing-magic-of-tidying-up-installations branch from 42f8439 to fbf316c Compare August 4, 2020 19:16
Copy link
Member

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

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

Okay! Reviewed all the files now


if depArgs.Action == claim.ActionUninstall {
if e.parentOpts.Delete && !e.parentOpts.ForceDelete {
executeErrs = multierror.Append(executeErrs, fmt.Errorf("not deleting installation %s as uninstall was not successful; use --force-delete to override", depArgs.Installation))
Copy link
Member

Choose a reason for hiding this comment

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

Can you update this error message to match the other as well that you just updated?

fmt.Fprintf(p.Out, "uninstalling %s...\n", opts.Name)
err = p.CNAB.Execute(opts.ToActionArgs(deperator))
if err != nil {
uninstallErrs = multierror.Append(uninstallErrs, err)
if strings.Contains(err.Error(), claim.ErrInstallationNotFound.Error()) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment on why this special handling for ErrInstallationNotFound is needed?

}

if opts.Delete && !opts.ForceDelete {
uninstallErrs = multierror.Append(uninstallErrs, fmt.Errorf("not deleting installation %s as uninstall was not successful; use --force-delete to override", opts.Name))
Copy link
Member

Choose a reason for hiding this comment

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

Sorry looks like that error message I had you update was used here as well.

if !opts.ForceDelete {
return uninstallErrs
}
// else, we swallow uninstallErrs as opts.ForceDelete is true
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 it would be helpful to no matter what, print out the errors even on --force-delete. We just will have a 0 exit code for --force-delete even when there are errors. That way you know what happened.

}

// TODO: See https://github.com/deislabs/porter/issues/465 for flag to allow keeping around the dependencies
return deperator.Execute()
err = deperator.Execute()
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Right now it's confusing how the ForceDelete/Delete is handled by both this function and the Deperator, and then they have all sorts of conditionals for handling the error, printing that warning about using --force, etc. I am concerned that it's going to be difficult to maintain without introducing bugs or fixing a bug accidentally in just one place (and forgetting to fix it in the dependencies.go for example).

🤔 Looking at it in a github code review it's hard to say "just change X" to simplify it. But I do wish there was a way to do so. Maybe we can take a second pass at it and see if there's a way to either extract the logic for dealing with it, or consolidate the logic for checking and printing the messages? I'm not sure what to suggest which is why I think trying again in a follow-up or whenever we next feel clever or inspired is fine.

One idea is to have a function takes in the flags (delete, force delete and the installation status) and it handles deciding if we should delete the installation and printing the warning. Then at least we could reuse that in 2+ places?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, this area warrants further thought. Thank you for the initial suggestions. I'll spend some more time here and hopefully have an update w/ less fragility and duplication.

Copy link
Member Author

Choose a reason for hiding this comment

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

(Still working on this one...)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, refactored for a bit of simplification... or at least some shared logic. Not sure if better, but perhaps it might generate some further ideas: dd5dd04

@@ -31,6 +31,8 @@ func TestDependenciesLifecycle(t *testing.T) {
invokeWordpressBundle(p, namespace)

uninstallWordpressBundle(p, namespace)

noReallyUninstallWordpressBundle(p, namespace)
Copy link
Member

Choose a reason for hiding this comment

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

😂

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I just realized, should I just place this uninstall+delete logic into cleanupWordpressBundle? Or keep it separate?

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 updated cleanupWordpressBundle w/ uninstall+delete logic in 6af718a)

installationExists bool
wantError string
}{
{"not yet installed", false, false, false, false, false, "1 error occurred:\n\t* could not load installation HELLO: Installation does not exist\n\n"},
Copy link
Member

Choose a reason for hiding this comment

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

I suggest test cases like this, which are really long, from setting every field without using the field name (which is quite hard to read without the support of an editor) to using the field names and only setting the ones that are non-default values,

{name: "not yet installed", wantError: "1 error occurred..."},

@vdice vdice force-pushed the feat/the-life-changing-magic-of-tidying-up-installations branch from ccebc29 to 29d0a32 Compare August 6, 2020 16:10
@carolynvs carolynvs self-assigned this Aug 6, 2020
func cleanupWordpressBundle(p *porter.TestPorter, namespace string) {
uninstallOptions := porter.UninstallOptions{}
uninstallOptions.CredentialIdentifiers = []string{"ci"}
uninstallOptions.Delete = true
Copy link
Member

Choose a reason for hiding this comment

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

👍

@@ -9,6 +9,7 @@ type BundleLifecycleOpts struct {
sharedOptions
BundlePullOptions
AllowAccessToDockerHost bool
UninstallDeleteOptions
Copy link
Member

Choose a reason for hiding this comment

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

This isn't the right place for these options as it applies to a bunch of commands: install, upgrade, invoke, etc. Can you move it to UninstallOptions as I think these flags only apply to the uninstall command, right?


dep.outputs = outputs
if e.parentOpts.shouldDelete() {
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 you put the UninstallDeleteOptions on the generic BundleLifecycleOptions so that you could make this check here, right?

Instead, since I'm asking further down to move that to the UninstallOptions type would be to do a type check

if uninstOpts, ok := e.parentOpts.(UninstallOptions); ok {
...
}

Right now parentOpts is just a BundleLifecycleOptions but we could make it an interface (pseudo code, not tested, but something like below), so that we could cast and get at the original options as needed while still having access to the other data that we were using on the parentOpts the rest of the time.

What do you think?

type BundleAction interface {
    // Implemented for free because of the embedded BundleLifecycleOpts
    ToActionArgs()

    // Let us grab and get direct access to the bundle opts fields like we need in some funcs
    GetBundleLifecycleOptions()
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Great suggestion! Thank you for providing the blueprint on this approach. Here's what I've updated: 4b2b647

How does it look?

return opts.Delete && !opts.ForceDelete
}

func (opts *UninstallDeleteOptions) handleUninstallErrs(out io.Writer, err error) error {
Copy link
Member

Choose a reason for hiding this comment

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

👍

vdice and others added 8 commits August 18, 2020 09:50
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Co-authored-by: Carolyn Van Slyck <me@carolynvanslyck.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
…cuting dependencies

Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
@vdice vdice force-pushed the feat/the-life-changing-magic-of-tidying-up-installations branch from 6af718a to 353dfd5 Compare August 18, 2020 19:19
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Copy link
Member

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

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

Looks good, just had a question that I'm not sure matters yet or not

if executeErrs != nil {
return executeErrs
}
}

if e.parentOpts.shouldDelete() {
if uninstallOpts.shouldDelete() {
Copy link
Member

Choose a reason for hiding this comment

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

If uninstallOpts is an empty struct (because parentOpts was some other action), what does this call do?

Copy link
Member Author

@vdice vdice Aug 18, 2020

Choose a reason for hiding this comment

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

I think all methods on an empty struct (when action not uninstall) return the uninitialized/default values for the struct fields... so mostly boolean false values, which is what we'd want here. The tests seem to agree.

However, I can see this approach/code being fragile... even if the assumptions are true. What do we think? Should we refactor the areas where we rely on uninstallOpts to run only if we are sure the struct is actually that type (and not empty)?

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 it would get pretty gnarly to conditionally call those, so the empty struct is fine. I was just double checking. Maybe let's just add a comment about if it's not uninstall, it's a no-op handler?

// when the implementation contains further, action-specific options beyond
// the stock BundleLifecycleOptions.
type BundleAction interface {
ToActionArgs(*dependencyExecutioner) cnabprovider.ActionArguments
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this isn't used anywhere? I thought it may be used depending on how the implementation worked out but guess not.

Copy link
Member Author

Choose a reason for hiding this comment

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

it is used here: https://github.com/deislabs/porter/blob/4b2b64746636e6997ee1f3dae8c87953c8b0cddc/pkg/porter/dependencies.go#L86

But, to your point, it could just as easily be removed from this interface and exist as it once was (a method on BundleLifecycleOpts), since at this time we don't see a need to have BundleAction implementations overriding its behavior. Shall I change this back and remove the method from the interface?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, thanks I obviously missed that. Either way is fine, up to you!

Copy link
Member Author

@vdice vdice Aug 18, 2020

Choose a reason for hiding this comment

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

Ok, removed from interface 👍

@@ -28,8 +39,12 @@ func (o *BundleLifecycleOpts) Validate(args []string, porter *Porter) error {
return nil
}

func (o BundleLifecycleOpts) GetBundleLifecycleOptions() BundleLifecycleOpts {
Copy link
Member

Choose a reason for hiding this comment

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

Brilliant! I wouldn't have thought to implement this here, and would have put it on each of the actions but this is sneakier. 💯

Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
Copy link
Member

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

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

Thanks this is going to be super helpful for cleaning up my dev machine! 😂

@carolynvs carolynvs merged commit c525954 into getporter:main Aug 19, 2020
Porter and Mixins [OLD] automation moved this from In Progress to Done Aug 19, 2020
@vdice vdice deleted the feat/the-life-changing-magic-of-tidying-up-installations branch August 19, 2020 14:48
vdice added a commit to vdice/porter that referenced this pull request Aug 19, 2020
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
carolynvs added a commit that referenced this pull request Aug 20, 2020
docs(cli): missed doc updates from PR #1189
@vdice vdice removed this from Done in Porter and Mixins [OLD] Sep 16, 2020
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.

Do we need a purge?
2 participants