feat: `helmfile destroy` deletes and purges releases (#530)
* feat: `helmfile destroy` deletes and purges releases This adds `helmfile destroy` that is basically `helmfile delete --purge`. I've also tweaked the behavior of `delete` and `destroy` for releases with `installed: false`, so that it becomes consistent with other helmfile commands. It now delete releases only when `installed: true` AND the release is already installed. **Why an another command?** Because it's easy to remember, and it also makes it easier to iterate on your helmfile. We've been using `helmfile delete` from the beginning of helmfile, and several months have been passed since we've added `--purge` to it. We noticed that we always prefer to use `--purge` so that we can quickly iterate on helmfile by e.g. `helmfile delete --purge && helmfile sync`. But making `--purge` default makes the `delete` command inconsistent with the helm's `delete`. `destroy`, on the other hand, doesn't have such problem, and is still easy to remember for terraform users. Resolves #511 * Update docs about `helmfile delete` and `helmfile destroy`
This commit is contained in:
parent
fa95e0dd92
commit
8f1a15c9cd
20
README.md
20
README.md
|
|
@ -212,16 +212,20 @@ NAME:
|
|||
USAGE:
|
||||
helmfile [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
v0.52.0
|
||||
|
||||
COMMANDS:
|
||||
repos sync repositories from state file (helm repo add && helm repo update)
|
||||
charts sync releases from state file (helm upgrade --install)
|
||||
charts DEPRECATED: sync releases from state file (helm upgrade --install)
|
||||
diff diff releases from state file against env (helm diff)
|
||||
template template releases from state file against env (helm template)
|
||||
lint lint charts from state file (helm lint)
|
||||
sync sync all resources from state file (repos, releases and chart deps)
|
||||
apply apply all resources from state file only when there are changes
|
||||
status retrieve status of releases in state file
|
||||
delete delete releases from state file (helm delete)
|
||||
delete DEPRECATED: delete releases from state file (helm delete)
|
||||
destroy deletes and then purges releases
|
||||
test test releases from state file (helm test)
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
|
|
@ -265,7 +269,15 @@ The `helmfile apply` sub-command begins by executing `diff`. If `diff` finds tha
|
|||
|
||||
An expected use-case of `apply` is to schedule it to run periodically, so that you can auto-fix skews between the desired and the current state of your apps running on Kubernetes clusters.
|
||||
|
||||
### delete
|
||||
### destroy
|
||||
|
||||
The `helmfile destroy` sub-command deletes and purges all the releases defined in the manifests.
|
||||
|
||||
`helmfile --interactive destroy` instructs Helmfile to request your confirmation before actually deleting releases.
|
||||
|
||||
`destroy` basically runs `helm delete --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead.
|
||||
|
||||
### delete (DEPRECATED)
|
||||
|
||||
The `helmfile delete` sub-command deletes all the releases defined in the manifests.
|
||||
|
||||
|
|
@ -762,7 +774,7 @@ Please see #203 for more context.
|
|||
|
||||
## Running helmfile interactively
|
||||
|
||||
`helmfile --interactive [apply|delete]` requests confirmation from you before actually modifying your cluster.
|
||||
`helmfile --interactive [apply|destroy]` requests confirmation from you before actually modifying your cluster.
|
||||
|
||||
Use it when you're running `helmfile` manually on your local machine or a kind of secure administrative hosts.
|
||||
|
||||
|
|
|
|||
41
main.go
41
main.go
|
|
@ -117,7 +117,7 @@ func main() {
|
|||
},
|
||||
{
|
||||
Name: "charts",
|
||||
Usage: "sync releases from state file (helm upgrade --install)",
|
||||
Usage: "DEPRECATED: sync releases from state file (helm upgrade --install)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
|
|
@ -435,7 +435,7 @@ Do you really want to apply?
|
|||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "delete releases from state file (helm delete)",
|
||||
Usage: "DEPRECATED: delete releases from state file (helm delete)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
|
|
@ -476,6 +476,43 @@ Do you really want to delete?
|
|||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "destroy",
|
||||
Usage: "deletes and then purges releases",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return cmd.FindAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
||||
args := args.GetArgs(c.String("args"), state)
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(args...)
|
||||
}
|
||||
|
||||
names := make([]string, len(state.Releases))
|
||||
for i, r := range state.Releases {
|
||||
names[i] = fmt.Sprintf(" %s (%s)", r.Name, r.Chart)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(`Affected releases are:
|
||||
%s
|
||||
|
||||
Do you really want to delete?
|
||||
Helmfile will delete all your releases, as shown above.
|
||||
|
||||
`, strings.Join(names, "\n"))
|
||||
interactive := c.GlobalBool("interactive")
|
||||
if !interactive || interactive && askForConfirmation(msg) {
|
||||
return state.DeleteReleases(helm, true)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test",
|
||||
Usage: "test releases from state file (helm test)",
|
||||
|
|
|
|||
|
|
@ -742,6 +742,10 @@ func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) [
|
|||
// DeleteReleases wrapper for executing helm delete on the releases
|
||||
func (st *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error {
|
||||
return st.scatterGatherReleases(helm, len(st.Releases), func(release ReleaseSpec) error {
|
||||
if !release.Desired() {
|
||||
return nil
|
||||
}
|
||||
|
||||
flags := []string{}
|
||||
if purge {
|
||||
flags = append(flags, "--purge")
|
||||
|
|
|
|||
|
|
@ -635,6 +635,8 @@ type mockHelmExec struct {
|
|||
charts []string
|
||||
repo []string
|
||||
releases []mockRelease
|
||||
deleted []mockRelease
|
||||
lists map[string]string
|
||||
}
|
||||
|
||||
type mockRelease struct {
|
||||
|
|
@ -690,10 +692,11 @@ func (helm *mockHelmExec) ReleaseStatus(release string) error {
|
|||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) DeleteRelease(name string, flags ...string) error {
|
||||
helm.deleted = append(helm.deleted, mockRelease{name: name, flags: flags})
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) List(filter string) (string, error) {
|
||||
return "", nil
|
||||
return helm.lists[filter], nil
|
||||
}
|
||||
func (helm *mockHelmExec) DecryptSecret(name string) (string, error) {
|
||||
return "", nil
|
||||
|
|
@ -1291,3 +1294,118 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
|
|||
t.Run(tt.name, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmState_Delete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
deleted []mockRelease
|
||||
wantErr bool
|
||||
desired *bool
|
||||
installed bool
|
||||
purge bool
|
||||
}{
|
||||
{
|
||||
name: "desired and installed (purge=false)",
|
||||
wantErr: false,
|
||||
desired: boolValue(true),
|
||||
installed: true,
|
||||
purge: false,
|
||||
deleted: []mockRelease{{"releaseA", []string{}}},
|
||||
},
|
||||
{
|
||||
name: "desired(default) and installed (purge=false)",
|
||||
wantErr: false,
|
||||
desired: nil,
|
||||
installed: true,
|
||||
purge: false,
|
||||
deleted: []mockRelease{{"releaseA", []string{}}},
|
||||
},
|
||||
{
|
||||
name: "desired and installed (purge=true)",
|
||||
wantErr: false,
|
||||
desired: boolValue(true),
|
||||
installed: true,
|
||||
purge: true,
|
||||
deleted: []mockRelease{{"releaseA", []string{"--purge"}}},
|
||||
},
|
||||
{
|
||||
name: "desired but not installed (purge=false)",
|
||||
wantErr: false,
|
||||
desired: boolValue(true),
|
||||
installed: false,
|
||||
purge: false,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
{
|
||||
name: "desired but not installed (purge=true)",
|
||||
wantErr: false,
|
||||
desired: boolValue(true),
|
||||
installed: false,
|
||||
purge: true,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
{
|
||||
name: "installed but filtered (purge=false)",
|
||||
wantErr: false,
|
||||
desired: boolValue(false),
|
||||
installed: true,
|
||||
purge: false,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
{
|
||||
name: "installed but filtered (purge=true)",
|
||||
wantErr: false,
|
||||
desired: boolValue(false),
|
||||
installed: true,
|
||||
purge: true,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
{
|
||||
name: "not installed, and filtered (purge=false)",
|
||||
wantErr: false,
|
||||
desired: boolValue(false),
|
||||
installed: false,
|
||||
purge: false,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
{
|
||||
name: "not installed, and filtered (purge=true)",
|
||||
wantErr: false,
|
||||
desired: boolValue(false),
|
||||
installed: false,
|
||||
purge: true,
|
||||
deleted: []mockRelease{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
i := func(t *testing.T) {
|
||||
release := ReleaseSpec{
|
||||
Name: "releaseA",
|
||||
Installed: tt.desired,
|
||||
}
|
||||
releases := []ReleaseSpec{
|
||||
release,
|
||||
}
|
||||
state := &HelmState{
|
||||
Releases: releases,
|
||||
logger: logger,
|
||||
}
|
||||
helm := &mockHelmExec{
|
||||
lists: map[string]string{},
|
||||
deleted: []mockRelease{},
|
||||
}
|
||||
if tt.installed {
|
||||
helm.lists["^releaseA$"] = "releaseA"
|
||||
}
|
||||
errs := state.DeleteReleases(helm, tt.purge)
|
||||
if (errs != nil) != tt.wantErr {
|
||||
t.Errorf("DeleteREleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(tt.deleted, helm.deleted) {
|
||||
t.Errorf("unexpected deletions happened: expected %v, got %v", tt.deleted, helm.deleted)
|
||||
}
|
||||
}
|
||||
t.Run(tt.name, i)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue