Add global hooks (#1301)

Changes:

* Add global hooks
* Add top level hooks field to yaml spec
* Add functions for global prepare and cleanup events
* Call global prepare and cleanup events in withPreparedCharts function
* Update README
* Add helmfileCommand variable to withPreparedCharts
  Pass the information on what helmfileCommand has been run down from the
  top level functions through withReposAndPreparedCharts and withPreparedCharts.
This commit is contained in:
Max Audron 2020-06-11 03:05:38 +02:00 committed by GitHub
parent 0f86cc9b87
commit f16d96bc8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 19 deletions

View File

@ -983,6 +983,19 @@ Now, replace `echo` with any command you like, and rewrite `args` that actually
For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine.
It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm.
### Global Hooks
In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively.
They use the same syntax as per release hooks, but at the top level of your helmfile:
``` yaml
hooks:
- events: ["prepare", "cleanup"]
showlogs: true
command: "echo"
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
"]
```
### Helmfile + Kustomize
Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features

View File

@ -101,7 +101,7 @@ func Init(app *App) *App {
func (a *App) Deps(c DepsConfigProvider) error {
return a.ForEachStateFiltered(func(run *Run) (errs []error) {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipRepos(), func() {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipRepos(), "deps", func() {
errs = run.Deps(c)
})
@ -113,7 +113,7 @@ func (a *App) Deps(c DepsConfigProvider) error {
func (a *App) Repos(c ReposConfigProvider) error {
return a.ForEachStateFiltered(func(run *Run) (errs []error) {
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "repos", func() {
errs = run.Repos(c)
})
@ -127,7 +127,7 @@ func (a *App) Repos(c ReposConfigProvider) error {
func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
return a.ForEachStateFiltered(func(run *Run) (errs []error) {
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "charts", func() {
errs = run.DeprecatedSyncCharts(c)
})
@ -153,7 +153,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
var errs []error
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), "diff", func() {
msg, matched, affected, errs = run.Diff(c)
})
@ -204,7 +204,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
func (a *App) Template(c TemplateConfigProvider) error {
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), func() {
prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), "template", func() {
ok, errs = a.template(run, c)
})
@ -216,7 +216,7 @@ func (a *App) Template(c TemplateConfigProvider) error {
func (a *App) Lint(c LintConfigProvider) error {
return a.ForEachStateFiltered(func(run *Run) (errs []error) {
prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), func() {
prepErrs := run.withReposAndPreparedCharts(true, c.SkipDeps(), "lint", func() {
errs = run.Lint(c)
})
@ -228,7 +228,7 @@ func (a *App) Lint(c LintConfigProvider) error {
func (a *App) Sync(c SyncConfigProvider) error {
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), "sync", func() {
ok, errs = a.sync(run, c)
})
@ -248,7 +248,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
opts = append(opts, SetRetainValuesFiles(c.RetainValuesFiles()))
err := a.ForEachState(func(run *Run) (ok bool, errs []error) {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), func() {
prepErrs := run.withReposAndPreparedCharts(false, c.SkipDeps(), "apply", func() {
matched, updated, es := a.apply(run, c)
mut.Lock()
@ -278,7 +278,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
func (a *App) Status(c StatusesConfigProvider) error {
return a.ForEachStateFiltered(func(run *Run) (errs []error) {
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "status", func() {
errs = run.Status(c)
})
@ -292,7 +292,7 @@ func (a *App) Status(c StatusesConfigProvider) error {
func (a *App) Delete(c DeleteConfigProvider) error {
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "delete", func() {
ok, errs = a.delete(run, c.Purge(), c)
})
@ -306,7 +306,7 @@ func (a *App) Delete(c DeleteConfigProvider) error {
func (a *App) Destroy(c DestroyConfigProvider) error {
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "destroy", func() {
ok, errs = a.delete(run, true, c)
})
@ -326,7 +326,7 @@ func (a *App) Test(c TestConfigProvider) error {
"or set helm.sh/hook-delete-policy\n")
}
err := run.withPreparedCharts(false, func() {
err := run.withPreparedCharts(false, "test", func() {
errs = run.Test(c)
})
@ -340,7 +340,7 @@ func (a *App) Test(c TestConfigProvider) error {
func (a *App) PrintState(c StateConfigProvider) error {
return a.VisitDesiredStatesWithReleasesFiltered(a.FileOrDir, func(st *state.HelmState) (errs []error) {
err := NewRun(st, nil, NewContext()).withPreparedCharts(false, func() {
err := NewRun(st, nil, NewContext()).withPreparedCharts(false, "build", func() {
state, err := st.ToYaml()
if err != nil {
errs = []error{err}
@ -363,7 +363,7 @@ func (a *App) ListReleases(c ListConfigProvider) error {
var releases []*HelmRelease
err := a.VisitDesiredStatesWithReleasesFiltered(a.FileOrDir, func(st *state.HelmState) []error {
err := NewRun(st, nil, NewContext()).withPreparedCharts(false, func() {
err := NewRun(st, nil, NewContext()).withPreparedCharts(false, "list", func() {
//var releases m
for _, r := range st.Releases {

View File

@ -32,7 +32,7 @@ func (r *Run) askForConfirmation(msg string) bool {
return AskForConfirmation(msg)
}
func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, f func()) []error {
func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, helmfileCommand string, f func()) []error {
if !skipRepos {
ctx := r.ctx
if errs := ctx.SyncReposOnce(r.state, r.helm); errs != nil && len(errs) > 0 {
@ -40,14 +40,14 @@ func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, f f
}
}
if err := r.withPreparedCharts(forceDownload, f); err != nil {
if err := r.withPreparedCharts(forceDownload, helmfileCommand, f); err != nil {
return []error{err}
}
return nil
}
func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
func (r *Run) withPreparedCharts(forceDownload bool, helmfileCommand string, f func()) error {
if r.ReleaseToChart != nil {
panic("Run.PrepareCharts can be called only once")
}
@ -59,7 +59,11 @@ func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
}
defer os.RemoveAll(dir)
releaseToChart, errs := state.PrepareCharts(r.helm, r.state, dir, 2, "template", forceDownload)
if _, err = r.state.TriggerGlobalPrepareEvent(helmfileCommand); err != nil {
return err
}
releaseToChart, errs := state.PrepareCharts(r.helm, r.state, dir, 2, helmfileCommand, forceDownload)
if len(errs) > 0 {
return fmt.Errorf("%v", errs)
@ -75,7 +79,9 @@ func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
f()
return nil
_, err = r.state.TriggerGlobalCleanupEvent(helmfileCommand)
return err
}
func (r *Run) Deps(c DepsConfigProvider) []error {

View File

@ -59,6 +59,9 @@ type HelmState struct {
Selectors []string `yaml:"-"`
ApiVersions []string `yaml:"apiVersions,omitempty"`
// Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile
Hooks []event.Hook `yaml:"hooks,omitempty"`
Templates map[string]TemplateSpec `yaml:"templates"`
Env environment.Environment `yaml:"-"`
@ -1441,6 +1444,30 @@ func (st *HelmState) PrepareReleases(helm helmexec.Interface, helmfileCommand st
return nil
}
func (st *HelmState) TriggerGlobalPrepareEvent(helmfileCommand string) (bool, error) {
return st.triggerGlobalReleaseEvent("prepare", nil, helmfileCommand)
}
func (st *HelmState) TriggerGlobalCleanupEvent(helmfileCommand string) (bool, error) {
return st.triggerGlobalReleaseEvent("cleanup", nil, helmfileCommand)
}
func (st *HelmState) triggerGlobalReleaseEvent(evt string, evtErr error, helmfileCmd string) (bool, error) {
bus := &event.Bus{
Hooks: st.Hooks,
StateFilePath: st.FilePath,
BasePath: st.basePath,
Namespace: st.OverrideNamespace,
Env: st.Env,
Logger: st.logger,
ReadFile: st.readFile,
}
data := map[string]interface{}{
"HelmfileCommand": helmfileCmd,
}
return bus.Trigger(evt, evtErr, data)
}
func (st *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
return st.triggerReleaseEvent("prepare", nil, r, helmfileCommand)
}