feat: least frequent repository update (#356)

Prevents helmfile from consuming unnecessarily much time in running `helm repo update` over and over.

helmfile now marks which repository was updated, and skip second and further `helm repo update` when all the `repositories` found in a helmfile.yaml was marked as already updated.

Let's say you had two helmfiles, the first one with repositories `foo` and `bar`, an the second one with only `bar`. `helmfile repos` will run `helm update repo` for the first helmfile, marking `foo` and `bar` as already updated. The second helmfile.yaml contains only `bar`, which is marked as already updated. So helmfile won't run `helm repo update` for the second.

This applies to all the helmfile command that results in `helm repo update`, like `repos`, `sync`, `diff` and so on.

Resolves #335
This commit is contained in:
KUOKA Yusuke 2018-09-27 02:10:11 +09:00 committed by GitHub
parent f2b610afdf
commit b94265122f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 36 deletions

93
main.go
View File

@ -114,13 +114,13 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return visitAllDesiredStates(c, func(state *state.HelmState, helm helmexec.Interface) (bool, []error) {
return visitAllDesiredStates(c, func(state *state.HelmState, helm helmexec.Interface, ctx context) (bool, []error) {
args := args.GetArgs(c.String("args"), state)
if len(args) > 0 {
helm.SetExtraArgs(args...)
}
errs := state.SyncRepos(helm)
errs := ctx.SyncReposOnce(state, helm)
ok := len(state.Repositories) > 0 && len(errs) == 0
@ -148,7 +148,7 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, _ context) []error {
return executeSyncCommand(c, state, helm)
})
},
@ -185,9 +185,11 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if errs := state.PrepareRelease(helm, "diff"); errs != nil && len(errs) > 0 {
return errs
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx context) []error {
if c.Bool("sync-repos") {
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
return errs
}
}
_, errs := executeDiffCommand(c, state, helm, c.Bool("detailed-exitcode"), c.Bool("suppress-secrets"))
@ -215,10 +217,17 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx context) []error {
if errs := state.PrepareRelease(helm, "template"); errs != nil && len(errs) > 0 {
return errs
}
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
return errs
}
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
return errs
}
return executeTemplateCommand(c, state, helm)
})
@ -244,11 +253,11 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx context) []error {
values := c.StringSlice("values")
args := args.GetArgs(c.String("args"), state)
workers := c.Int("concurrency")
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
return errs
}
if errs := state.PrepareRelease(helm, "lint"); errs != nil && len(errs) > 0 {
@ -278,8 +287,8 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx context) []error {
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
return errs
}
if errs := state.PrepareRelease(helm, "sync"); errs != nil && len(errs) > 0 {
@ -324,9 +333,9 @@ func main() {
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(st *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(st *state.HelmState, helm helmexec.Interface, ctx context) []error {
if !c.Bool("skip-repo-update") {
if errs := st.SyncRepos(helm); errs != nil && len(errs) > 0 {
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
return errs
}
}
@ -413,7 +422,7 @@ Do you really want to apply?
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, _ context) []error {
workers := c.Int("concurrency")
args := args.GetArgs(c.String("args"), state)
@ -444,7 +453,7 @@ Do you really want to apply?
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface, _ context) []error {
purge := c.Bool("purge")
args := args.GetArgs(c.String("args"), state)
@ -492,7 +501,7 @@ Do you really want to delete?
},
},
Action: func(c *cli.Context) error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, _ context) []error {
cleanup := c.Bool("cleanup")
timeout := c.Int("timeout")
@ -527,14 +536,6 @@ func executeSyncCommand(c *cli.Context, state *state.HelmState, helm helmexec.In
}
func executeTemplateCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface) []error {
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
return errs
}
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
return errs
}
args := args.GetArgs(c.String("args"), state)
values := c.StringSlice("values")
workers := c.Int("concurrency")
@ -548,12 +549,6 @@ func executeDiffCommand(c *cli.Context, st *state.HelmState, helm helmexec.Inter
helm.SetExtraArgs(args...)
}
if c.Bool("sync-repos") {
if errs := st.SyncRepos(helm); errs != nil && len(errs) > 0 {
return []*state.ReleaseSpec{}, errs
}
}
values := c.StringSlice("values")
workers := c.Int("concurrency")
@ -574,7 +569,7 @@ type app struct {
selectors []string
}
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error {
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface, context) []error) error {
return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, false, converge)
}
@ -612,17 +607,43 @@ func initAppEntry(c *cli.Context, reverse bool) (*app, string, error) {
return app, fileOrDir, nil
}
func visitAllDesiredStates(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
type context struct {
updatedRepos map[string]struct{}
}
func (ctx context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) []error {
var errs []error
allUpdated := true
for _, r := range st.Repositories {
_, exists := ctx.updatedRepos[r.Name]
allUpdated = allUpdated && exists
}
if !allUpdated {
errs = st.SyncRepos(helm)
for _, r := range st.Repositories {
ctx.updatedRepos[r.Name] = struct{}{}
}
}
return errs
}
func visitAllDesiredStates(c *cli.Context, converge func(*state.HelmState, helmexec.Interface, context) (bool, []error)) error {
app, fileOrDir, err := initAppEntry(c, false)
if err != nil {
return err
}
ctx := context{}
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
if c.GlobalString("helm-binary") != "" {
helm.SetHelmBinary(c.GlobalString("helm-binary"))
}
return converge(st, helm)
return converge(st, helm, ctx)
}
err = app.VisitDesiredStates(fileOrDir, convergeWithHelmBinary)
@ -648,17 +669,19 @@ func toCliError(err error) error {
return err
}
func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface) []error) error {
func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface, context) []error) error {
app, fileOrDir, err := initAppEntry(c, reverse)
if err != nil {
return err
}
ctx := context{}
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) []error {
if c.GlobalString("helm-binary") != "" {
helm.SetHelmBinary(c.GlobalString("helm-binary"))
}
return converge(st, helm)
return converge(st, helm, ctx)
}
err = app.VisitDesiredStatesWithReleasesFiltered(fileOrDir, convergeWithHelmBinary)

View File

@ -128,8 +128,13 @@ func (state *HelmState) applyDefaultsTo(spec *ReleaseSpec) {
}
}
type RepoUpdater interface {
AddRepo(name, repository, certfile, keyfile, username, password string) error
UpdateRepo() error
}
// SyncRepos will update the given helm releases
func (state *HelmState) SyncRepos(helm helmexec.Interface) []error {
func (state *HelmState) SyncRepos(helm RepoUpdater) []error {
errs := []error{}
for _, repo := range state.Repositories {
@ -828,6 +833,7 @@ func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand
for _, release := range state.Releases {
if _, err := state.triggerPrepareEvent(&release, helmfileCommand); err != nil {
errs = append(errs, &ReleaseError{&release, err})
continue
}
}
if len(errs) != 0 {