fix: `helmfile destroy` should delete releases in the specified tiller namespace (#536)

Actually, 4 helm commands including "list", "diff", "status" and "delete" were not taking tiller-rerelated flags into account in helmfile. This fixes all these commands.

Fixes #534
This commit is contained in:
KUOKA Yusuke 2019-04-03 23:41:41 +09:00 committed by GitHub
parent f522ba320b
commit 0e00cdf2a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 220 additions and 34 deletions

View File

@ -103,16 +103,16 @@ func (helm *execer) SyncRelease(name, chart string, flags ...string) error {
return err return err
} }
func (helm *execer) ReleaseStatus(name string) error { func (helm *execer) ReleaseStatus(name string, flags ...string) error {
helm.logger.Infof("Getting status %v", name) helm.logger.Infof("Getting status %v", name)
out, err := helm.exec(append([]string{"status", name})...) out, err := helm.exec(append([]string{"status", name}, flags...)...)
helm.write(out) helm.write(out)
return err return err
} }
func (helm *execer) List(filter string) (string, error) { func (helm *execer) List(filter string, flags ...string) (string, error) {
helm.logger.Infof("Listing releases matching %v", filter) helm.logger.Infof("Listing releases matching %v", filter)
out, err := helm.exec(append([]string{"list", filter})...) out, err := helm.exec(append([]string{"list", filter}, flags...)...)
helm.write(out) helm.write(out)
return string(out), err return string(out), err
} }

View File

@ -14,9 +14,9 @@ type Interface interface {
TemplateRelease(chart string, flags ...string) error TemplateRelease(chart string, flags ...string) error
Fetch(chart string, flags ...string) error Fetch(chart string, flags ...string) error
Lint(chart string, flags ...string) error Lint(chart string, flags ...string) error
ReleaseStatus(name string) error ReleaseStatus(name string, flags ...string) error
DeleteRelease(name string, flags ...string) error DeleteRelease(name string, flags ...string) error
TestRelease(name string, flags ...string) error TestRelease(name string, flags ...string) error
List(filter string) (string, error) List(filter string, flags ...string) (string, error)
DecryptSecret(name string) (string, error) DecryptSecret(name string) (string, error)
} }

View File

@ -277,8 +277,8 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu
return res, errs return res, errs
} }
func isReleaseInstalled(helm helmexec.Interface, release ReleaseSpec) (bool, error) { func (st *HelmState) isReleaseInstalled(helm helmexec.Interface, release ReleaseSpec) (bool, error) {
out, err := helm.List("^" + release.Name + "$") out, err := helm.List("^"+release.Name+"$", st.tillerFlags(&release)...)
if err != nil { if err != nil {
return false, err return false, err
} else if out != "" { } else if out != "" {
@ -292,7 +292,7 @@ func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*Rele
for i, _ := range st.Releases { for i, _ := range st.Releases {
release := st.Releases[i] release := st.Releases[i]
if !release.Desired() { if !release.Desired() {
installed, err := isReleaseInstalled(helm, release) installed, err := st.isReleaseInstalled(helm, release)
if err != nil { if err != nil {
return nil, err return nil, err
} else if installed { } else if installed {
@ -330,7 +330,7 @@ func (st *HelmState) SyncReleases(helm helmexec.Interface, additionalValues []st
chart := normalizeChart(st.basePath, release.Chart) chart := normalizeChart(st.basePath, release.Chart)
var relErr *ReleaseError var relErr *ReleaseError
if !release.Desired() { if !release.Desired() {
installed, err := isReleaseInstalled(helm, *release) installed, err := st.isReleaseInstalled(helm, *release)
if err != nil { if err != nil {
relErr = &ReleaseError{release, err} relErr = &ReleaseError{release, err}
} else if installed { } else if installed {
@ -735,7 +735,11 @@ func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) [
if !release.Desired() { if !release.Desired() {
return nil return nil
} }
return helm.ReleaseStatus(release.Name)
flags := []string{}
flags = st.appendTillerFlags(flags, &release)
return helm.ReleaseStatus(release.Name, flags...)
}) })
} }
@ -750,7 +754,9 @@ func (st *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error
if purge { if purge {
flags = append(flags, "--purge") flags = append(flags, "--purge")
} }
installed, err := isReleaseInstalled(helm, release) flags = st.appendTillerFlags(flags, &release)
installed, err := st.isReleaseInstalled(helm, release)
if err != nil { if err != nil {
return err return err
} }
@ -773,6 +779,8 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
flags = append(flags, "--cleanup") flags = append(flags, "--cleanup")
} }
flags = append(flags, "--timeout", strconv.Itoa(timeout)) flags = append(flags, "--timeout", strconv.Itoa(timeout))
flags = st.appendTillerFlags(flags, &release)
return helm.TestRelease(release.Name, flags...) return helm.TestRelease(release.Name, flags...)
}) })
} }
@ -976,6 +984,15 @@ func findChartDirectory(topLevelDir string) (string, error) {
} }
func (st *HelmState) appendTillerFlags(flags []string, release *ReleaseSpec) []string { func (st *HelmState) appendTillerFlags(flags []string, release *ReleaseSpec) []string {
adds := st.tillerFlags(release)
for _, a := range adds {
flags = append(flags, a)
}
return flags
}
func (st *HelmState) tillerFlags(release *ReleaseSpec) []string {
flags := []string{}
if release.TillerNamespace != "" { if release.TillerNamespace != "" {
flags = append(flags, "--tiller-namespace", release.TillerNamespace) flags = append(flags, "--tiller-namespace", release.TillerNamespace)
} else if st.HelmDefaults.TillerNamespace != "" { } else if st.HelmDefaults.TillerNamespace != "" {

View File

@ -631,12 +631,18 @@ func Test_normalizeChart(t *testing.T) {
// mocking helmexec.Interface // mocking helmexec.Interface
type listKey struct {
filter string
flags string
}
type mockHelmExec struct { type mockHelmExec struct {
charts []string charts []string
repo []string repo []string
releases []mockRelease releases []mockRelease
deleted []mockRelease deleted []mockRelease
lists map[string]string lists map[listKey]string
diffed []mockRelease
} }
type mockRelease struct { type mockRelease struct {
@ -682,21 +688,22 @@ func (helm *mockHelmExec) SyncRelease(name, chart string, flags ...string) error
return nil return nil
} }
func (helm *mockHelmExec) DiffRelease(name, chart string, flags ...string) error { func (helm *mockHelmExec) DiffRelease(name, chart string, flags ...string) error {
helm.diffed = append(helm.diffed, mockRelease{name: name, flags: flags})
return nil return nil
} }
func (helm *mockHelmExec) ReleaseStatus(release string) error { func (helm *mockHelmExec) ReleaseStatus(release string, flags ...string) error {
if strings.Contains(release, "error") { if strings.Contains(release, "error") {
return errors.New("error") return errors.New("error")
} }
helm.releases = append(helm.releases, mockRelease{name: release, flags: []string{}}) helm.releases = append(helm.releases, mockRelease{name: release, flags: flags})
return nil return nil
} }
func (helm *mockHelmExec) DeleteRelease(name string, flags ...string) error { func (helm *mockHelmExec) DeleteRelease(name string, flags ...string) error {
helm.deleted = append(helm.deleted, mockRelease{name: name, flags: flags}) helm.deleted = append(helm.deleted, mockRelease{name: name, flags: flags})
return nil return nil
} }
func (helm *mockHelmExec) List(filter string) (string, error) { func (helm *mockHelmExec) List(filter string, flags ...string) (string, error) {
return helm.lists[filter], nil return helm.lists[listKey{filter: filter, flags: strings.Join(flags, "")}], nil
} }
func (helm *mockHelmExec) DecryptSecret(name string) (string, error) { func (helm *mockHelmExec) DecryptSecret(name string) (string, error) {
return "", nil return "", nil
@ -807,6 +814,18 @@ func TestHelmState_SyncReleases(t *testing.T) {
helm: &mockHelmExec{}, helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{}}}, wantReleases: []mockRelease{{"releaseName", []string{}}},
}, },
{
name: "with tiller args",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
TillerNamespace: "tillerns",
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{"--tiller-namespace", "tillerns"}}},
},
{ {
name: "escaped values", name: "escaped values",
releases: []ReleaseSpec{ releases: []ReleaseSpec{
@ -887,6 +906,120 @@ func TestHelmState_SyncReleases(t *testing.T) {
} }
} }
func TestHelmState_DiffReleases(t *testing.T) {
tests := []struct {
name string
releases []ReleaseSpec
helm *mockHelmExec
wantReleases []mockRelease
}{
{
name: "normal release",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{}}},
},
{
name: "with tiller args",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
TillerNamespace: "tillerns",
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{"--tiller-namespace", "tillerns"}}},
},
{
name: "escaped values",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
SetValues: []SetValue{
{
Name: "someList",
Value: "a,b,c",
},
{
Name: "json",
Value: "{\"name\": \"john\"}",
},
},
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}"}}},
},
{
name: "set single value from file",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
SetValues: []SetValue{
{
Name: "foo",
Value: "FOO",
},
{
Name: "bar",
File: "path/to/bar",
},
{
Name: "baz",
Value: "BAZ",
},
},
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ"}}},
},
{
name: "set single array value in an array",
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
SetValues: []SetValue{
{
Name: "foo.bar[0]",
Values: []string{
"A",
"B",
},
},
},
},
},
helm: &mockHelmExec{},
wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo.bar[0]={A,B}"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
}
_, err := state.DiffReleases(tt.helm, []string{}, 1, false, false, false)
if err != nil {
fmt.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(tt.helm.diffed, tt.wantReleases) {
t.Errorf("HelmState.DiffReleases() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.wantReleases)
}
})
}
}
func TestHelmState_SyncReleasesCleanup(t *testing.T) { func TestHelmState_SyncReleasesCleanup(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -1156,6 +1289,17 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
helm: &mockHelmExec{}, helm: &mockHelmExec{},
wantErr: false, wantErr: false,
}, },
{
name: "with tiller args",
releases: []ReleaseSpec{
{
Name: "releaseA",
TillerNamespace: "tillerns",
},
},
helm: &mockHelmExec{},
want: []mockRelease{{"releaseA", []string{"--tiller-namespace", "tillerns"}}},
},
} }
for _, tt := range tests { for _, tt := range tests {
i := func(t *testing.T) { i := func(t *testing.T) {
@ -1190,12 +1334,13 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
cleanup bool cleanup bool
releases []ReleaseSpec releases []ReleaseSpec
helm *mockHelmExec helm *mockHelmExec
want []mockRelease want []mockRelease
wantErr bool wantErr bool
tillerNamespace string
}{ }{
{ {
name: "happy path", name: "happy path",
@ -1228,6 +1373,17 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
helm: &mockHelmExec{}, helm: &mockHelmExec{},
wantErr: true, wantErr: true,
}, },
{
name: "with tiller args",
releases: []ReleaseSpec{
{
Name: "releaseA",
TillerNamespace: "tillerns",
},
},
helm: &mockHelmExec{},
want: []mockRelease{{"releaseA", []string{"--timeout", "1", "--tiller-namespace", "tillerns"}}},
},
} }
for _, tt := range tests { for _, tt := range tests {
i := func(t *testing.T) { i := func(t *testing.T) {
@ -1297,12 +1453,14 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
func TestHelmState_Delete(t *testing.T) { func TestHelmState_Delete(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
deleted []mockRelease deleted []mockRelease
wantErr bool wantErr bool
desired *bool desired *bool
installed bool installed bool
purge bool purge bool
flags string
tillerNamespace string
}{ }{
{ {
name: "desired and installed (purge=false)", name: "desired and installed (purge=false)",
@ -1376,12 +1534,23 @@ func TestHelmState_Delete(t *testing.T) {
purge: true, purge: true,
deleted: []mockRelease{}, deleted: []mockRelease{},
}, },
{
name: "with tiller args",
wantErr: false,
desired: nil,
installed: true,
purge: true,
tillerNamespace: "tillerns",
flags: "--tiller-namespacetillerns",
deleted: []mockRelease{{"releaseA", []string{"--purge", "--tiller-namespace", "tillerns"}}},
},
} }
for _, tt := range tests { for _, tt := range tests {
i := func(t *testing.T) { i := func(t *testing.T) {
release := ReleaseSpec{ release := ReleaseSpec{
Name: "releaseA", Name: "releaseA",
Installed: tt.desired, Installed: tt.desired,
TillerNamespace: tt.tillerNamespace,
} }
releases := []ReleaseSpec{ releases := []ReleaseSpec{
release, release,
@ -1391,15 +1560,15 @@ func TestHelmState_Delete(t *testing.T) {
logger: logger, logger: logger,
} }
helm := &mockHelmExec{ helm := &mockHelmExec{
lists: map[string]string{}, lists: map[listKey]string{},
deleted: []mockRelease{}, deleted: []mockRelease{},
} }
if tt.installed { if tt.installed {
helm.lists["^releaseA$"] = "releaseA" helm.lists[listKey{filter: "^releaseA$", flags: tt.flags}] = "releaseA"
} }
errs := state.DeleteReleases(helm, tt.purge) errs := state.DeleteReleases(helm, tt.purge)
if (errs != nil) != tt.wantErr { if (errs != nil) != tt.wantErr {
t.Errorf("DeleteREleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) t.Errorf("DeleteReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
return return
} }
if !reflect.DeepEqual(tt.deleted, helm.deleted) { if !reflect.DeepEqual(tt.deleted, helm.deleted) {