get.porter.sh/porter@v1.3.0/pkg/porter/uninstall.go (about) 1 package porter 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 9 "get.porter.sh/porter/pkg/cnab" 10 "get.porter.sh/porter/pkg/storage" 11 "get.porter.sh/porter/pkg/tracing" 12 "github.com/hashicorp/go-multierror" 13 ) 14 15 var _ BundleAction = NewUninstallOptions() 16 17 // ErrUnsafeInstallationDeleteRetryForceDelete presents the ErrUnsafeInstallationDelete error and provides a retry option of --force-delete 18 var ErrUnsafeInstallationDeleteRetryForceDelete = fmt.Errorf("%s; if you are sure it should be deleted, retry the last command with the --force-delete flag", ErrUnsafeInstallationDelete) 19 20 // UninstallOptions that may be specified when uninstalling a bundle. 21 // Porter handles defaulting any missing values. 22 type UninstallOptions struct { 23 *BundleExecutionOptions 24 UninstallDeleteOptions 25 } 26 27 func NewUninstallOptions() UninstallOptions { 28 return UninstallOptions{ 29 BundleExecutionOptions: NewBundleExecutionOptions(), 30 } 31 } 32 33 func (o UninstallOptions) GetAction() string { 34 return cnab.ActionUninstall 35 } 36 37 func (o UninstallOptions) GetActionVerb() string { 38 return "uninstalling" 39 } 40 41 // UninstallDeleteOptions supply options for deletion on uninstall 42 type UninstallDeleteOptions struct { 43 Delete bool 44 ForceDelete bool 45 } 46 47 func (opts *UninstallDeleteOptions) shouldDelete() bool { 48 return opts.Delete || opts.ForceDelete 49 } 50 51 func (opts *UninstallDeleteOptions) unsafeDelete() bool { 52 return opts.Delete && !opts.ForceDelete 53 } 54 55 func (opts *UninstallDeleteOptions) handleUninstallErrs(out io.Writer, err error) error { 56 if err == nil { 57 return nil 58 } 59 60 if opts.unsafeDelete() { 61 return multierror.Append(err, ErrUnsafeInstallationDeleteRetryForceDelete) 62 } 63 64 if opts.ForceDelete { 65 fmt.Fprintf(out, "ignoring the following errors as --force-delete is true:\n %s", err.Error()) 66 return nil 67 } 68 return err 69 } 70 71 // UninstallBundle accepts a set of pre-validated UninstallOptions and uses 72 // them to uninstall a bundle. 73 func (p *Porter) UninstallBundle(ctx context.Context, opts UninstallOptions) error { 74 ctx, log := tracing.StartSpan(ctx) 75 defer log.EndSpan() 76 77 installation, err := p.Installations.GetInstallation(ctx, opts.Namespace, opts.Name) 78 if err != nil { 79 return fmt.Errorf("could not find installation %s/%s: %w", opts.Namespace, opts.Name, err) 80 } 81 82 err = p.applyActionOptionsToInstallation(ctx, opts, &installation) 83 if err != nil { 84 return err 85 } 86 87 deperator := newDependencyExecutioner(p, installation, opts) 88 err = deperator.Prepare(ctx) 89 if err != nil { 90 return err 91 } 92 93 actionArgs, err := deperator.PrepareRootActionArguments(ctx) 94 if err != nil { 95 return err 96 } 97 98 log.Infof("%s bundle", opts.GetActionVerb()) 99 err = p.CNAB.Execute(ctx, actionArgs) 100 101 var uninstallErrs error 102 if err != nil { 103 uninstallErrs = multierror.Append(uninstallErrs, err) 104 105 // If the installation is not found, no further action is needed 106 err := errors.Unwrap(err) 107 if errors.Is(err, storage.ErrNotFound{}) { 108 return err 109 } 110 111 if len(deperator.deps) > 0 && !opts.ForceDelete { 112 uninstallErrs = multierror.Append(uninstallErrs, 113 fmt.Errorf("failed to uninstall the %s bundle, the remaining dependencies were not uninstalled", opts.Name)) 114 } 115 116 uninstallErrs = opts.handleUninstallErrs(p.Out, uninstallErrs) 117 if uninstallErrs != nil { 118 return uninstallErrs 119 } 120 } 121 122 // TODO(PEP-003): See https://github.com/getporter/porter/issues/465 for flag to allow keeping around the dependencies 123 // Note(schristoff): For now we check if the parentLabel is on the dep 124 // to decide if we delete. We only add the parentLabel on the dep 125 // if they were installed *together* 126 // Users can add a label (for now) if they want to delete it 127 // Label is: sh.porter.parentInstallation: $INSTALLATIONNAME 128 err = opts.handleUninstallErrs(p.Out, deperator.Execute(ctx)) 129 if err != nil { 130 return err 131 } 132 133 if opts.shouldDelete() { 134 log.Info("deleting installation records") 135 return p.Installations.RemoveInstallation(ctx, opts.Namespace, opts.Name) 136 } 137 return nil 138 }