feat: `helmfile diff --detailed-exitcode` should also detect deletions (#1186)
Resolves #499 Resolves #1072
This commit is contained in:
parent
98886df5d2
commit
870cc03c70
|
|
@ -116,12 +116,20 @@ func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
|
|||
}
|
||||
|
||||
func (a *App) Diff(c DiffConfigProvider) error {
|
||||
var deferredErrs []error
|
||||
var allDiffDetectedErrs []error
|
||||
|
||||
err := a.ForEachStateFiltered(func(run *Run) []error {
|
||||
var affectedAny bool
|
||||
|
||||
err := a.ForEachState(func(run *Run) (bool, []error) {
|
||||
var criticalErrs []error
|
||||
|
||||
errs := run.Diff(c)
|
||||
msg, matched, affected, errs := run.Diff(c)
|
||||
|
||||
if msg != nil {
|
||||
a.Logger.Info(*msg)
|
||||
}
|
||||
|
||||
affectedAny = affectedAny || affected
|
||||
|
||||
for i := range errs {
|
||||
switch e := errs[i].(type) {
|
||||
|
|
@ -129,7 +137,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
|
|||
switch e.Code {
|
||||
case 2:
|
||||
// See https://github.com/roboll/helmfile/issues/874
|
||||
deferredErrs = append(deferredErrs, e)
|
||||
allDiffDetectedErrs = append(allDiffDetectedErrs, e)
|
||||
default:
|
||||
criticalErrs = append(criticalErrs, e)
|
||||
}
|
||||
|
|
@ -138,14 +146,14 @@ func (a *App) Diff(c DiffConfigProvider) error {
|
|||
}
|
||||
}
|
||||
|
||||
return criticalErrs
|
||||
return matched, criticalErrs
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(deferredErrs) > 0 {
|
||||
if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) {
|
||||
// We take the first release error w/ exit status 2 (although all the defered errs should have exit status 2)
|
||||
// to just let helmfile itself to exit with 2
|
||||
// See https://github.com/roboll/helmfile/issues/749
|
||||
|
|
@ -806,84 +814,28 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
|
|||
Set: c.Set(),
|
||||
}
|
||||
|
||||
var changedReleases []state.ReleaseSpec
|
||||
var deletingReleases []state.ReleaseSpec
|
||||
var planningErrs []error
|
||||
|
||||
// TODO Better way to detect diff on only filtered releases
|
||||
{
|
||||
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.SuppressDiff(), false, diffOpts)
|
||||
|
||||
var err error
|
||||
deletingReleases, err = st.DetectReleasesToBeDeletedForSync(helm, st.Releases)
|
||||
if err != nil {
|
||||
planningErrs = append(planningErrs, err)
|
||||
}
|
||||
infoMsg, releasesToBeUpdated, releasesToBeDeleted, errs := r.diff(false, detailedExitCode, c, diffOpts)
|
||||
if len(errs) > 0 {
|
||||
return false, false, errs
|
||||
}
|
||||
|
||||
fatalErrs := []error{}
|
||||
|
||||
for _, e := range planningErrs {
|
||||
switch err := e.(type) {
|
||||
case *state.ReleaseError:
|
||||
if err.Code != 2 {
|
||||
fatalErrs = append(fatalErrs, e)
|
||||
}
|
||||
default:
|
||||
fatalErrs = append(fatalErrs, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fatalErrs) > 0 {
|
||||
return false, false, fatalErrs
|
||||
}
|
||||
|
||||
releasesToBeDeleted := map[string]state.ReleaseSpec{}
|
||||
for _, r := range deletingReleases {
|
||||
id := state.ReleaseToID(&r)
|
||||
releasesToBeDeleted[id] = r
|
||||
}
|
||||
|
||||
releasesToBeUpdated := map[string]state.ReleaseSpec{}
|
||||
for _, r := range changedReleases {
|
||||
id := state.ReleaseToID(&r)
|
||||
|
||||
// If `helm-diff` detected changes but it is not being `helm delete`ed, we should run `helm upgrade`
|
||||
if _, ok := releasesToBeDeleted[id]; !ok {
|
||||
releasesToBeUpdated[id] = r
|
||||
}
|
||||
}
|
||||
|
||||
// sync only when there are changes
|
||||
if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 {
|
||||
// TODO better way to get the logger
|
||||
if releasesToBeDeleted == nil && releasesToBeUpdated == nil {
|
||||
if infoMsg != nil {
|
||||
logger := c.Logger()
|
||||
logger.Infof("")
|
||||
logger.Infof("No affected releases")
|
||||
logger.Infof(*infoMsg)
|
||||
}
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, r := range releasesToBeUpdated {
|
||||
names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart))
|
||||
}
|
||||
for _, r := range releasesToBeDeleted {
|
||||
names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart))
|
||||
}
|
||||
// Make the output deterministic for testing purpose
|
||||
sort.Strings(names)
|
||||
|
||||
infoMsg := fmt.Sprintf(`Affected releases are:
|
||||
%s
|
||||
`, strings.Join(names, "\n"))
|
||||
confMsg := fmt.Sprintf(`%s
|
||||
Do you really want to apply?
|
||||
Helmfile will apply all your changes, as shown above.
|
||||
|
||||
`, infoMsg)
|
||||
`, *infoMsg)
|
||||
interactive := c.Interactive()
|
||||
if !interactive {
|
||||
a.Logger.Debug(infoMsg)
|
||||
a.Logger.Debug(*infoMsg)
|
||||
}
|
||||
|
||||
syncErrs := []error{}
|
||||
|
|
|
|||
|
|
@ -2524,6 +2524,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
10 release(s) found in helmfile.yaml
|
||||
|
||||
worker 1/1 started
|
||||
worker 1/1 finished
|
||||
worker 1/1 started
|
||||
|
|
@ -2711,6 +2713,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
10:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
3 release(s) found in helmfile.yaml
|
||||
|
||||
worker 1/1 started
|
||||
worker 1/1 finished
|
||||
worker 1/1 started
|
||||
|
|
@ -2989,6 +2993,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
12:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
worker 1/1 started
|
||||
worker 1/1 finished
|
||||
worker 1/1 started
|
||||
|
|
@ -3335,6 +3341,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
worker 1/1 started
|
||||
worker 1/1 finished
|
||||
worker 1/1 started
|
||||
|
|
@ -3473,6 +3481,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
0 release(s) matching app=test_non_existent found in helmfile.yaml
|
||||
|
||||
`,
|
||||
},
|
||||
//
|
||||
|
|
@ -3535,6 +3545,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
worker 1/1 started
|
||||
worker 1/1 finished
|
||||
worker 1/1 started
|
||||
|
|
|
|||
|
|
@ -284,6 +284,8 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
|||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
10 release(s) found in helmfile.yaml
|
||||
|
||||
processing 5 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v3, frontend-v2, frontend-v1
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
129
pkg/app/run.go
129
pkg/app/run.go
|
|
@ -1,9 +1,12 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/argparser"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Run struct {
|
||||
|
|
@ -61,21 +64,36 @@ func (r *Run) Status(c StatusesConfigProvider) []error {
|
|||
return r.state.ReleaseStatuses(r.helm, workers)
|
||||
}
|
||||
|
||||
func (r *Run) Diff(c DiffConfigProvider) []error {
|
||||
func (r *Run) Diff(c DiffConfigProvider) (*string, bool, bool, []error) {
|
||||
st := r.state
|
||||
helm := r.helm
|
||||
ctx := r.ctx
|
||||
|
||||
allReleases := st.GetReleasesWithOverrides()
|
||||
|
||||
toDiff, err := st.GetSelectedReleasesWithOverrides()
|
||||
if err != nil {
|
||||
return nil, false, false, []error{err}
|
||||
}
|
||||
|
||||
if len(toDiff) == 0 {
|
||||
return nil, false, false, nil
|
||||
}
|
||||
|
||||
// Do build deps and prepare only on selected releases so that we won't waste time
|
||||
// on running various helm commands on unnecessary releases
|
||||
st.Releases = toDiff
|
||||
|
||||
if !c.SkipDeps() {
|
||||
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
return nil, false, false, errs
|
||||
}
|
||||
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
return nil, false, false, errs
|
||||
}
|
||||
}
|
||||
if errs := st.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
return nil, false, false, errs
|
||||
}
|
||||
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
|
@ -85,9 +103,28 @@ func (r *Run) Diff(c DiffConfigProvider) []error {
|
|||
NoColor: c.NoColor(),
|
||||
Set: c.Set(),
|
||||
}
|
||||
_, errs := st.DiffReleases(helm, c.Values(), c.Concurrency(), c.DetailedExitcode(), c.IncludeTests(), c.SuppressSecrets(), c.SuppressDiff(), true, opts)
|
||||
|
||||
return errs
|
||||
// Validate all releases for missing `needs` targets
|
||||
st.Releases = allReleases
|
||||
|
||||
if _, err := st.PlanReleases(false); err != nil {
|
||||
return nil, false, false, []error{err}
|
||||
}
|
||||
|
||||
// Diff only targeted releases
|
||||
|
||||
st.Releases = toDiff
|
||||
|
||||
filtered := &Run{
|
||||
state: st,
|
||||
helm: r.helm,
|
||||
ctx: r.ctx,
|
||||
Ask: r.Ask,
|
||||
}
|
||||
|
||||
infoMsg, updated, deleted, errs := filtered.diff(true, c.DetailedExitcode(), c, opts)
|
||||
|
||||
return infoMsg, true, len(deleted) > 0 || len(updated) > 0, errs
|
||||
}
|
||||
|
||||
func (r *Run) Test(c TestConfigProvider) []error {
|
||||
|
|
@ -124,3 +161,83 @@ func (r *Run) Lint(c LintConfigProvider) []error {
|
|||
}
|
||||
return st.LintReleases(helm, values, args, workers, opts)
|
||||
}
|
||||
|
||||
func (run *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) {
|
||||
st := run.state
|
||||
helm := run.helm
|
||||
|
||||
var changedReleases []state.ReleaseSpec
|
||||
var deletingReleases []state.ReleaseSpec
|
||||
var planningErrs []error
|
||||
|
||||
// TODO Better way to detect diff on only filtered releases
|
||||
{
|
||||
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.SuppressDiff(), triggerCleanupEvent, diffOpts)
|
||||
|
||||
var err error
|
||||
deletingReleases, err = st.DetectReleasesToBeDeletedForSync(helm, st.Releases)
|
||||
if err != nil {
|
||||
planningErrs = append(planningErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
fatalErrs := []error{}
|
||||
|
||||
for _, e := range planningErrs {
|
||||
switch err := e.(type) {
|
||||
case *state.ReleaseError:
|
||||
if err.Code != 2 {
|
||||
fatalErrs = append(fatalErrs, e)
|
||||
}
|
||||
default:
|
||||
fatalErrs = append(fatalErrs, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fatalErrs) > 0 {
|
||||
return nil, nil, nil, fatalErrs
|
||||
}
|
||||
|
||||
releasesToBeDeleted := map[string]state.ReleaseSpec{}
|
||||
for _, r := range deletingReleases {
|
||||
id := state.ReleaseToID(&r)
|
||||
releasesToBeDeleted[id] = r
|
||||
}
|
||||
|
||||
releasesToBeUpdated := map[string]state.ReleaseSpec{}
|
||||
for _, r := range changedReleases {
|
||||
id := state.ReleaseToID(&r)
|
||||
|
||||
// If `helm-diff` detected changes but it is not being `helm delete`ed, we should run `helm upgrade`
|
||||
if _, ok := releasesToBeDeleted[id]; !ok {
|
||||
releasesToBeUpdated[id] = r
|
||||
}
|
||||
}
|
||||
|
||||
// sync only when there are changes
|
||||
if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 {
|
||||
var msg *string
|
||||
if c.DetailedExitcode() {
|
||||
// TODO better way to get the logger
|
||||
m := "No affected releases"
|
||||
msg = &m
|
||||
}
|
||||
return msg, nil, nil, nil
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for _, r := range releasesToBeUpdated {
|
||||
names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart))
|
||||
}
|
||||
for _, r := range releasesToBeDeleted {
|
||||
names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart))
|
||||
}
|
||||
// Make the output deterministic for testing purpose
|
||||
sort.Strings(names)
|
||||
|
||||
infoMsg := fmt.Sprintf(`Affected releases are:
|
||||
%s
|
||||
`, strings.Join(names, "\n"))
|
||||
|
||||
return &infoMsg, releasesToBeUpdated, releasesToBeDeleted, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1327,6 +1327,15 @@ func (st *HelmState) GetSelectedReleasesWithOverrides() ([]ReleaseSpec, error) {
|
|||
releases = append(releases, r.ReleaseSpec)
|
||||
}
|
||||
}
|
||||
|
||||
var extra string
|
||||
|
||||
if len(st.Selectors) > 0 {
|
||||
extra = " matching " + strings.Join(st.Selectors, ",")
|
||||
}
|
||||
|
||||
st.logger.Debugf("%d release(s)%s found in %s\n", len(releases), extra, st.FilePath)
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
|
|
@ -1337,7 +1346,6 @@ func (st *HelmState) FilterReleases() error {
|
|||
return err
|
||||
}
|
||||
st.Releases = releases
|
||||
st.logger.Debugf("%d release(s) matching %s found in %s\n", len(releases), strings.Join(st.Selectors, ","), st.FilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue