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

6 changes: 6 additions & 0 deletions cmd/porter/bundle.go
Expand Up @@ -249,6 +249,8 @@ For example, the 'debug' driver may be specified, which simply logs the info giv
porter bundle uninstall --parameter-set azure --param test-mode=true --param header-color=blue
porter bundle uninstall --cred azure --cred kubernetes
porter bundle uninstall --driver debug
porter bundle uninstall --delete
porter bundle uninstall --force-delete
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Validate(args, p)
Expand All @@ -273,6 +275,10 @@ For example, the 'debug' driver may be specified, which simply logs the info giv
"Credential to use when uninstalling the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
f.StringVarP(&opts.Driver, "driver", "d", porter.DefaultDriver,
"Specify a driver to use. Allowed values: docker, debug")
f.BoolVar(&opts.Delete, "delete", false,
"Delete all records associated with the installation, assuming the uninstall action succeeds")
f.BoolVar(&opts.ForceDelete, "force-delete", false,
"UNSAFE. Delete all records associated with the installation, even if uninstall fails. This is intended for cleaning up test data and is not recommended for production environments.")
addBundlePullFlags(f, &opts.BundlePullOptions)

return cmd
Expand Down
27 changes: 27 additions & 0 deletions cmd/porter/installations.go
Expand Up @@ -19,6 +19,7 @@ func buildInstallationCommands(p *porter.Porter) *cobra.Command {
cmd.AddCommand(buildInstallationsListCommand(p))
cmd.AddCommand(buildInstallationShowCommand(p))
cmd.AddCommand(buildInstallationOutputsCommands(p))
cmd.AddCommand(buildInstallationDeleteCommand(p))

return cmd
}
Expand Down Expand Up @@ -77,3 +78,29 @@ Optional output formats include json and yaml.

return &cmd
}

func buildInstallationDeleteCommand(p *porter.Porter) *cobra.Command {
opts := porter.DeleteOptions{}

cmd := cobra.Command{
Use: "delete [INSTALLATION]",
Short: "Delete an installation",
Long: "Deletes all records and outputs associated with an installation",
Example: ` porter installation delete
porter installation delete wordpress
porter installation delete --force
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Validate(args, p.Context)
},
RunE: func(cmd *cobra.Command, args []string) error {
return p.DeleteInstallation(opts)
},
}

f := cmd.Flags()
f.BoolVar(&opts.Force, "force", false,
"Force a delete the installation, regardless of last completed action")

return &cmd
}
4 changes: 4 additions & 0 deletions docs/content/cli/bundles_uninstall.md
Expand Up @@ -30,6 +30,8 @@ porter bundles uninstall [INSTALLATION] [flags]
porter bundle uninstall --parameter-set azure --param test-mode=true --param header-color=blue
porter bundle uninstall --cred azure --cred kubernetes
porter bundle uninstall --driver debug
porter bundle uninstall --delete
porter bundle uninstall --force-delete

```

Expand All @@ -39,9 +41,11 @@ porter bundles uninstall [INSTALLATION] [flags]
--allow-docker-host-access Controls if the bundle should have access to the host's Docker daemon with elevated privileges. See https://porter.sh/configuration/#allow-docker-host-access for the full implications of this flag.
--cnab-file string Path to the CNAB bundle.json file.
-c, --cred strings Credential to use when uninstalling the bundle. May be either a named set of credentials or a filepath, and specified multiple times.
--delete Delete all records associated with the installation, assuming the uninstall action succeeds
-d, --driver string Specify a driver to use. Allowed values: docker, debug (default "docker")
-f, --file string Path to the porter manifest file. Defaults to the bundle in the current directory. Optional unless a newer version of the bundle should be used to uninstall the bundle.
--force Force a fresh pull of the bundle
--force-delete UNSAFE. Delete all records associated with the installation, even if uninstall fails. This is intended for cleaning up test data and is not recommended for production environments.
-h, --help help for uninstall
--insecure-registry Don't require TLS for the registry
--param strings Define an individual parameter in the form NAME=VALUE. Overrides parameters otherwise set via --parameter-set. May be specified multiple times.
Expand Down
1 change: 1 addition & 0 deletions docs/content/cli/installations.md
Expand Up @@ -27,6 +27,7 @@ Commands for working with installations of a bundle
### SEE ALSO

* [porter](/cli/porter/) - I am porter 👩🏽‍✈️, the friendly neighborhood CNAB authoring tool
* [porter installations delete](/cli/porter_installations_delete/) - Delete an installation
* [porter installations list](/cli/porter_installations_list/) - List installed bundles
* [porter installations output](/cli/porter_installations_output/) - Output commands
* [porter installations show](/cli/porter_installations_show/) - Show an installation of a bundle
Expand Down
44 changes: 44 additions & 0 deletions docs/content/cli/installations_delete.md
@@ -0,0 +1,44 @@
---
title: "porter installations delete"
slug: porter_installations_delete
url: /cli/porter_installations_delete/
---
## porter installations delete

Delete an installation

### Synopsis

Deletes all records and outputs associated with an installation

```
porter installations delete [INSTALLATION] [flags]
```

### Examples

```
porter installation delete
porter installation delete wordpress
porter installation delete --force

```

### Options

```
--force Force a delete the installation, regardless of last completed action
-h, --help help for delete
```

### Options inherited from parent commands

```
--debug Enable debug logging
--debug-plugins Enable plugin debug logging
```

### SEE ALSO

* [porter installations](/cli/porter_installations/) - Installation commands

4 changes: 4 additions & 0 deletions docs/content/cli/uninstall.md
Expand Up @@ -30,6 +30,8 @@ porter uninstall [INSTALLATION] [flags]
porter uninstall --parameter-set azure --param test-mode=true --param header-color=blue
porter uninstall --cred azure --cred kubernetes
porter uninstall --driver debug
porter uninstall --delete
porter uninstall --force-delete

```

Expand All @@ -39,9 +41,11 @@ porter uninstall [INSTALLATION] [flags]
--allow-docker-host-access Controls if the bundle should have access to the host's Docker daemon with elevated privileges. See https://porter.sh/configuration/#allow-docker-host-access for the full implications of this flag.
--cnab-file string Path to the CNAB bundle.json file.
-c, --cred strings Credential to use when uninstalling the bundle. May be either a named set of credentials or a filepath, and specified multiple times.
--delete Delete all records associated with the installation, assuming the uninstall action succeeds
-d, --driver string Specify a driver to use. Allowed values: docker, debug (default "docker")
-f, --file string Path to the porter manifest file. Defaults to the bundle in the current directory. Optional unless a newer version of the bundle should be used to uninstall the bundle.
--force Force a fresh pull of the bundle
--force-delete UNSAFE. Delete all records associated with the installation, even if uninstall fails. This is intended for cleaning up test data and is not recommended for production environments.
-h, --help help for uninstall
--insecure-registry Don't require TLS for the registry
--param strings Define an individual parameter in the form NAME=VALUE. Overrides parameters otherwise set via --parameter-set. May be specified multiple times.
Expand Down
4 changes: 3 additions & 1 deletion pkg/cnab/provider/helpers.go
Expand Up @@ -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.

👍

args.Driver = debugDriver
}
return t.Runtime.Execute(args)
}
45 changes: 45 additions & 0 deletions pkg/context/helpers.go
Expand Up @@ -135,6 +135,51 @@ func (c *TestContext) AddTestDirectory(srcDir, destDir string) {
}
}

func (c *TestContext) AddTestDriver(src, name string) string {
c.T.Helper()

data, err := ioutil.ReadFile(src)
if err != nil {
c.T.Fatal(err)
}

dirname, err := c.FileSystem.TempDir("", "porter")
if err != nil {
c.T.Fatal(err)
}

// filename in accordance with cnab-go's command driver expectations
filename := fmt.Sprintf("%s/cnab-%s", dirname, name)

newfile, err := c.FileSystem.Create(filename)
if err != nil {
c.T.Fatal(err)
}

if len(data) > 0 {
_, err := newfile.Write(data)
if err != nil {
c.T.Fatal(err)
}
}

err = c.FileSystem.Chmod(newfile.Name(), os.ModePerm)
if err != nil {
c.T.Fatal(err)
}
err = newfile.Close()
if err != nil {
c.T.Fatal(err)
}

path := os.Getenv("PATH")
pathlist := []string{dirname, path}
newpath := strings.Join(pathlist, string(os.PathListSeparator))
os.Setenv("PATH", newpath)

return dirname
}

// GetOutput returns all text printed to stdout.
func (c *TestContext) GetOutput() string {
return string(c.capturedOut.Bytes())
Expand Down
61 changes: 61 additions & 0 deletions pkg/porter/delete.go
@@ -0,0 +1,61 @@
package porter

import (
"fmt"

"get.porter.sh/porter/pkg/context"
claims "github.com/cnabio/cnab-go/claim"
"github.com/pkg/errors"
)

const installationDeleteTmpl = "deleting installation records for %s...\n"

var (
// ErrUnsafeInstallationDelete warns the user that deletion of an unsuccessfully uninstalled installation is unsafe
ErrUnsafeInstallationDelete = errors.New("it is unsafe to delete an installation when the last action wasn't a successful uninstall")

// ErrUnsafeInstallationDeleteRetryForce presents the ErrUnsafeInstallationDelete error and provides a retry option of --force
ErrUnsafeInstallationDeleteRetryForce = fmt.Errorf("%s; if you are sure it should be deleted, retry the last command with the --force flag", ErrUnsafeInstallationDelete)
)

// DeleteOptions represent options for Porter's installation delete command
type DeleteOptions struct {
sharedOptions
Force bool
}

// Validate prepares for an installation delete action and validates the args/options.
func (o *DeleteOptions) Validate(args []string, cxt *context.Context) error {
// Ensure only one argument exists (installation name) if args length non-zero
err := o.sharedOptions.validateInstallationName(args)
if err != nil {
return err
}

return o.sharedOptions.defaultBundleFiles(cxt)
}

// DeleteInstallation handles deletion of an installation
func (p *Porter) DeleteInstallation(opts DeleteOptions) error {
err := p.applyDefaultOptions(&opts.sharedOptions)
if err != nil {
return err
}

installation, err := p.Claims.ReadInstallationStatus(opts.Name)
if err != nil {
return errors.Wrapf(err, "unable to read status for installation %s", opts.Name)
}

claim, err := installation.GetLastClaim()
if err != nil {
return errors.Wrapf(err, "unable to read most recent record for installation %s", opts.Name)
}

if (claim.Action != claims.ActionUninstall || installation.GetLastStatus() != claims.StatusSucceeded) && !opts.Force {
return ErrUnsafeInstallationDeleteRetryForce
}

fmt.Fprintf(p.Out, installationDeleteTmpl, opts.Name)
return p.Claims.DeleteInstallation(opts.Name)
}
80 changes: 80 additions & 0 deletions pkg/porter/delete_test.go
@@ -0,0 +1,80 @@
package porter

import (
"testing"

"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/claim"
"github.com/stretchr/testify/require"
)

func TestDeleteInstallation(t *testing.T) {
testcases := []struct {
name string
lastAction string
lastActionStatus string
force bool
installationRemains bool
wantError string
}{
{
name: "not yet installed",
wantError: "unable to read status for installation test: Installation does not exist",
}, {
name: "last action not uninstall; no --force",
lastAction: "install",
lastActionStatus: claim.StatusSucceeded,
installationRemains: true,
wantError: ErrUnsafeInstallationDeleteRetryForce.Error(),
}, {
name: "last action failed uninstall; no --force",
lastAction: "uninstall",
lastActionStatus: claim.StatusFailed,
installationRemains: true,
wantError: ErrUnsafeInstallationDeleteRetryForce.Error(),
}, {
name: "last action not uninstall; --force",
lastAction: "install",
lastActionStatus: claim.StatusSucceeded,
force: true,
}, {
name: "last action failed uninstall; --force",
lastAction: "uninstall",
lastActionStatus: claim.StatusFailed,
force: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
p := NewTestPorter(t)
p.TestConfig.SetupPorterHome()

var err error

// Create test claim
if tc.lastAction != "" {
c := p.TestClaims.CreateClaim("test", tc.lastAction, bundle.Bundle{}, nil)
carolynvs marked this conversation as resolved.
Show resolved Hide resolved
_ = p.TestClaims.CreateResult(c, tc.lastActionStatus)
}

opts := DeleteOptions{}
opts.Name = "test"
opts.Force = tc.force

err = p.DeleteInstallation(opts)
if tc.wantError != "" {
require.EqualError(t, err, tc.wantError)
} else {
require.NoError(t, err, "expected DeleteInstallation to succeed")
}

_, err = p.Claims.ReadInstallation("test")
if tc.installationRemains {
require.NoError(t, err, "expected installation to exist")
} else {
require.EqualError(t, err, "Installation does not exist")
}
})
}
}