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  }