feat: `prepare` and `cleanup` release event hooks (#349)
Resolves #295 Resolves #330 Resolves #329 (Supports templating of only `releases[].hooks[].command` and `args` right now Resolves #324
This commit is contained in:
parent
4da13b44ee
commit
b9de22b256
104
README.md
104
README.md
|
|
@ -593,6 +593,110 @@ mysetting: |
|
||||||
|
|
||||||
The possibility is endless. Try importing values from your golang app, bash script, jsonnet, or anything!
|
The possibility is endless. Try importing values from your golang app, bash script, jsonnet, or anything!
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
A helmfile hook is a per-release extension point that is composed of:
|
||||||
|
|
||||||
|
- `events`
|
||||||
|
- `command`
|
||||||
|
- `args`
|
||||||
|
|
||||||
|
helmfile triggers various `events` while it is running.
|
||||||
|
Once `events` are triggered, associated `hooks` are executed, by running the `command` with `args`.
|
||||||
|
|
||||||
|
Currently supported `events` are:
|
||||||
|
|
||||||
|
- `prepare`
|
||||||
|
- `cleanup`
|
||||||
|
|
||||||
|
Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before executed.
|
||||||
|
|
||||||
|
Hooks associated to `cleanup` events are triggered after each release is processed.
|
||||||
|
|
||||||
|
The following is an example hook that just prints the contextual information provided to hook:
|
||||||
|
|
||||||
|
```
|
||||||
|
releases:
|
||||||
|
- name: myapp
|
||||||
|
chart: mychart
|
||||||
|
# *snip*
|
||||||
|
hooks:
|
||||||
|
- events: ["prepare", "cleanup"]
|
||||||
|
command: "echo"
|
||||||
|
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
|
||||||
|
"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's say you ran `helmfile --environment prod sync`, the above hook results in executing:
|
||||||
|
|
||||||
|
```
|
||||||
|
echo {{Environment.Name}} {{.Release.Name}} {{.HelmfileCommand}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whereas the template expressions are executed thus the command becomes:
|
||||||
|
|
||||||
|
```
|
||||||
|
echo prod myapp sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, replace `echo` with any command you like, and rewrite `args` that actually conforms to the command, so that you can integrate any command that does:
|
||||||
|
|
||||||
|
- templating
|
||||||
|
- linting
|
||||||
|
- testing
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Helmfile + Kustomize
|
||||||
|
|
||||||
|
Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features
|
||||||
|
like rollback, history, and so on? This section is for you!
|
||||||
|
|
||||||
|
The combination of `hooks` and [helmify-kustomize](https://gist.github.com/mumoshu/f9d0bd98e0eb77f636f79fc2fb130690)
|
||||||
|
enables you to integrate [kustomize](https://github.com/kubernetes-sigs/kustomize) into helmfle.
|
||||||
|
|
||||||
|
That is, you can use `kustommize` to build a local helm chart from a kustomize overlay.
|
||||||
|
|
||||||
|
Let's assume you have a kustomize project named `foo-kustomize` like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo-kustomize/
|
||||||
|
├── base
|
||||||
|
│ ├── configMap.yaml
|
||||||
|
│ ├── deployment.yaml
|
||||||
|
│ ├── kustomization.yaml
|
||||||
|
│ └── service.yaml
|
||||||
|
└── overlays
|
||||||
|
├── default
|
||||||
|
│ ├── kustomization.yaml
|
||||||
|
│ └── map.yaml
|
||||||
|
├── production
|
||||||
|
│ ├── deployment.yaml
|
||||||
|
│ └── kustomization.yaml
|
||||||
|
└── staging
|
||||||
|
├── kustomization.yaml
|
||||||
|
└── map.yaml
|
||||||
|
|
||||||
|
5 directories, 10 files
|
||||||
|
```
|
||||||
|
|
||||||
|
Write `helmfile.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: kustomize
|
||||||
|
chart: ./foo
|
||||||
|
hooks:
|
||||||
|
- events: ["prepare", "cleanup"]
|
||||||
|
command: "../helmify"
|
||||||
|
args: ["{{`{{if eq .Event.Name \"prepare\"}}build{{else}}clean{{end}}`}}", "{{`{{.Release.Ch\
|
||||||
|
art}}`}}", "{{`{{.Environment.Name}}`}}"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `helmfile --environment staging sync` and see it results in helmfile running `kustomize build foo-kustomize/overlays/staging > foo/templates/all.yaml`.
|
||||||
|
|
||||||
|
Voilà! You can mix helm releases that are backed by remote charts, local charts, and even kustomize overlays.
|
||||||
|
|
||||||
## Using env files
|
## Using env files
|
||||||
|
|
||||||
helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal:
|
helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/roboll/helmfile/environment"
|
||||||
|
"github.com/roboll/helmfile/helmexec"
|
||||||
|
"github.com/roboll/helmfile/tmpl"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hook struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Events []string `yaml:"events"`
|
||||||
|
Command string `yaml:"command"`
|
||||||
|
Args []string `yaml:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type event struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bus struct {
|
||||||
|
Runner helmexec.Runner
|
||||||
|
Hooks []Hook
|
||||||
|
|
||||||
|
BasePath string
|
||||||
|
StateFilePath string
|
||||||
|
Namespace string
|
||||||
|
|
||||||
|
Env environment.Environment
|
||||||
|
|
||||||
|
ReadFile func(string) ([]byte, error)
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bus *Bus) Trigger(evt string, context map[string]interface{}) (bool, error) {
|
||||||
|
if bus.Runner == nil {
|
||||||
|
bus.Runner = helmexec.ShellRunner{
|
||||||
|
Dir: bus.BasePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executed := false
|
||||||
|
|
||||||
|
for _, hook := range bus.Hooks {
|
||||||
|
contained := false
|
||||||
|
for _, e := range hook.Events {
|
||||||
|
contained = contained || e == evt
|
||||||
|
}
|
||||||
|
if !contained {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
name := hook.Name
|
||||||
|
if name == "" {
|
||||||
|
name = hook.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: basePath=%s\n", bus.StateFilePath, bus.BasePath)
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Environment": bus.Env,
|
||||||
|
"Namespace": bus.Namespace,
|
||||||
|
"Event": event{
|
||||||
|
Name: evt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for k, v := range context {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
render := tmpl.NewTextRenderer(bus.ReadFile, bus.BasePath, data)
|
||||||
|
|
||||||
|
bus.Logger.Debugf("hook[%s]: triggered by event \"%s\"\n", name, evt)
|
||||||
|
|
||||||
|
command, err := render.RenderTemplateText(hook.Command)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("hook[%s]: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]string, len(hook.Args))
|
||||||
|
for i, raw := range hook.Args {
|
||||||
|
args[i], err = render.RenderTemplateText(raw)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("hook[%s]: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := bus.Runner.Execute(command, args)
|
||||||
|
bus.Logger.Debugf("hook[%s]: %s\n", name, string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("hook[%s]: command `%s` failed: %v", name, command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
executed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return executed, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/roboll/helmfile/environment"
|
||||||
|
"github.com/roboll/helmfile/helmexec"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = helmexec.NewLogger(os.Stdout, "warn")
|
||||||
|
|
||||||
|
type runner struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) Execute(cmd string, args []string) ([]byte, error) {
|
||||||
|
if cmd == "ng" {
|
||||||
|
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
|
||||||
|
}
|
||||||
|
for _, a := range args {
|
||||||
|
if a == "ng" {
|
||||||
|
return nil, fmt.Errorf("cmd failed due to invalid arg: %s", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrigger(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
hook *Hook
|
||||||
|
triggeredEvt string
|
||||||
|
expectedResult bool
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"okhook1",
|
||||||
|
&Hook{"okhook1", []string{"foo"}, "ok", []string{}},
|
||||||
|
"foo",
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missinghook1",
|
||||||
|
&Hook{"okhook1", []string{"foo"}, "ok", []string{}},
|
||||||
|
"bar",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nohook1",
|
||||||
|
nil,
|
||||||
|
"bar",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nghook1",
|
||||||
|
&Hook{"nghook1", []string{"foo"}, "ng", []string{}},
|
||||||
|
"foo",
|
||||||
|
false,
|
||||||
|
"hook[nghook1]: command `ng` failed: cmd failed due to invalid cmd: ng",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nghook2",
|
||||||
|
&Hook{"nghook2", []string{"foo"}, "ok", []string{"ng"}},
|
||||||
|
"foo",
|
||||||
|
false,
|
||||||
|
"hook[nghook2]: command `ok` failed: cmd failed due to invalid arg: ng",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
readFile := func(filename string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("unexpected call to readFile: %s", filename)
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
hooks := []Hook{}
|
||||||
|
if c.hook != nil {
|
||||||
|
hooks = append(hooks, *c.hook)
|
||||||
|
}
|
||||||
|
bus := &Bus{
|
||||||
|
Hooks: hooks,
|
||||||
|
StateFilePath: "path/to/helmfile.yaml",
|
||||||
|
BasePath: "path/to",
|
||||||
|
Namespace: "myns",
|
||||||
|
Env: environment.Environment{Name: "prod"},
|
||||||
|
Logger: logger,
|
||||||
|
ReadFile: readFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.Runner = &runner{}
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Release": "myrel",
|
||||||
|
"HelmfileCommand": "mycmd",
|
||||||
|
}
|
||||||
|
ok, err := bus.Trigger(c.triggeredEvt, data)
|
||||||
|
|
||||||
|
if ok != c.expectedResult {
|
||||||
|
t.Errorf("unexpected result for case \"%s\": expected=%v, actual=%v", c.name, c.expectedResult, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.expectedErr != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("error expected, but not occurred")
|
||||||
|
} else if err.Error() != c.expectedErr {
|
||||||
|
t.Errorf("unexpected error for case \"%s\": expected=%s, actual=%v", c.name, c.expectedErr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error for case \"%s\": %v", c.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,13 @@ type Runner interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShellRunner implemention for shell commands
|
// ShellRunner implemention for shell commands
|
||||||
type ShellRunner struct{}
|
type ShellRunner struct {
|
||||||
|
Dir string
|
||||||
|
}
|
||||||
|
|
||||||
// Execute a shell command
|
// Execute a shell command
|
||||||
func (shell ShellRunner) Execute(cmd string, args []string) ([]byte, error) {
|
func (shell ShellRunner) Execute(cmd string, args []string) ([]byte, error) {
|
||||||
preparedCmd := exec.Command(cmd, args...)
|
preparedCmd := exec.Command(cmd, args...)
|
||||||
|
preparedCmd.Dir = shell.Dir
|
||||||
return preparedCmd.CombinedOutput()
|
return preparedCmd.CombinedOutput()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
main.go
13
main.go
|
|
@ -209,6 +209,10 @@ func main() {
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
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) []error {
|
||||||
|
if errs := state.PrepareRelease(helm, "template"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
return executeTemplateCommand(c, state, helm)
|
return executeTemplateCommand(c, state, helm)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -240,6 +244,9 @@ func main() {
|
||||||
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
if errs := state.PrepareRelease(helm, "lint"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
return state.LintReleases(helm, values, args, workers)
|
return state.LintReleases(helm, values, args, workers)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -268,6 +275,9 @@ func main() {
|
||||||
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
if errs := state.PrepareRelease(helm, "sync"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
@ -313,6 +323,9 @@ func main() {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if errs := st.PrepareRelease(helm, "apply"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
if errs := st.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
if errs := st.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
|
"github.com/roboll/helmfile/event"
|
||||||
"github.com/roboll/helmfile/valuesfile"
|
"github.com/roboll/helmfile/valuesfile"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
@ -42,6 +43,8 @@ type HelmState struct {
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
|
||||||
readFile func(string) ([]byte, error)
|
readFile func(string) ([]byte, error)
|
||||||
|
|
||||||
|
runner helmexec.Runner
|
||||||
}
|
}
|
||||||
|
|
||||||
// HelmSpec to defines helmDefault values
|
// HelmSpec to defines helmDefault values
|
||||||
|
|
@ -91,6 +94,9 @@ type ReleaseSpec struct {
|
||||||
// Installed, when set to true, `delete --purge` the release
|
// Installed, when set to true, `delete --purge` the release
|
||||||
Installed *bool `yaml:"installed"`
|
Installed *bool `yaml:"installed"`
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
|
||||||
// Name is the name of this release
|
// Name is the name of this release
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Namespace string `yaml:"namespace"`
|
Namespace string `yaml:"namespace"`
|
||||||
|
|
@ -175,6 +181,7 @@ func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalV
|
||||||
go func() {
|
go func() {
|
||||||
for release := range jobs {
|
for release := range jobs {
|
||||||
state.applyDefaultsTo(release)
|
state.applyDefaultsTo(release)
|
||||||
|
|
||||||
flags, flagsErr := state.flagsForUpgrade(helm, release)
|
flags, flagsErr := state.flagsForUpgrade(helm, release)
|
||||||
if flagsErr != nil {
|
if flagsErr != nil {
|
||||||
results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}}
|
results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}}
|
||||||
|
|
@ -282,6 +289,10 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
} else {
|
} else {
|
||||||
results <- syncResult{}
|
results <- syncResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := state.triggerCleanupEvent(prep.release, "sync"); err != nil {
|
||||||
|
state.logger.Warnf("warn: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +323,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadCharts will download and untar charts for Lint and Template
|
// downloadCharts will download and untar charts for Lint and Template
|
||||||
func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int) (map[string]string, []error) {
|
func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int, helmfileCommand string) (map[string]string, []error) {
|
||||||
temp := make(map[string]string, len(state.Releases))
|
temp := make(map[string]string, len(state.Releases))
|
||||||
type downloadResults struct {
|
type downloadResults struct {
|
||||||
releaseName string
|
releaseName string
|
||||||
|
|
@ -387,7 +398,7 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
temp, errs := state.downloadCharts(helm, dir, workerLimit)
|
temp, errs := state.downloadCharts(helm, dir, workerLimit, "template")
|
||||||
|
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
|
@ -420,6 +431,10 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := state.triggerCleanupEvent(&release, "template"); err != nil {
|
||||||
|
state.logger.Warnf("warn: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
|
|
@ -440,7 +455,7 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
temp, errs := state.downloadCharts(helm, dir, workerLimit)
|
temp, errs := state.downloadCharts(helm, dir, workerLimit, "lint")
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
return errs
|
return errs
|
||||||
|
|
@ -472,6 +487,10 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := state.triggerCleanupEvent(&release, "lint"); err != nil {
|
||||||
|
state.logger.Warnf("warn: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
|
|
@ -617,6 +636,10 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [
|
||||||
// diff succeeded, found no changes
|
// diff succeeded, found no changes
|
||||||
results <- diffResult{}
|
results <- diffResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := state.triggerCleanupEvent(prep.release, "diff"); err != nil {
|
||||||
|
state.logger.Warnf("warn: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
@ -799,6 +822,48 @@ func (state *HelmState) FilterReleases(labels []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand string) []error {
|
||||||
|
errs := []error{}
|
||||||
|
|
||||||
|
for _, release := range state.Releases {
|
||||||
|
if isLocalChart(release.Chart) {
|
||||||
|
if _, err := state.triggerPrepareEvent(&release, helmfileCommand); err != nil {
|
||||||
|
errs = append(errs, &ReleaseError{&release, err})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
||||||
|
return state.triggerReleaseEvent("prepare", r, helmfileCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *HelmState) triggerCleanupEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
||||||
|
return state.triggerReleaseEvent("cleanup", r, helmfileCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfileCmd string) (bool, error) {
|
||||||
|
bus := &event.Bus{
|
||||||
|
Hooks: r.Hooks,
|
||||||
|
StateFilePath: state.FilePath,
|
||||||
|
BasePath: state.basePath,
|
||||||
|
Namespace: state.Namespace,
|
||||||
|
Env: state.Env,
|
||||||
|
Logger: state.logger,
|
||||||
|
ReadFile: state.readFile,
|
||||||
|
}
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Release": r,
|
||||||
|
"HelmfileCommand": helmfileCmd,
|
||||||
|
}
|
||||||
|
return bus.Trigger(evt, data)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateDeps wrapper for updating dependencies on the releases
|
// UpdateDeps wrapper for updating dependencies on the releases
|
||||||
func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package tmpl
|
||||||
|
|
||||||
|
type templateTextRenderer struct {
|
||||||
|
ReadText func(string) ([]byte, error)
|
||||||
|
Context *Context
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextRenderer interface {
|
||||||
|
RenderTemplateText(text string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextRenderer(readFile func(filename string) ([]byte, error), basePath string, data interface{}) *templateTextRenderer {
|
||||||
|
return &templateTextRenderer{
|
||||||
|
ReadText: readFile,
|
||||||
|
Context: &Context{
|
||||||
|
basePath: basePath,
|
||||||
|
readFile: readFile,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *templateTextRenderer) RenderTemplateText(text string) (string, error) {
|
||||||
|
buf, err := r.Context.RenderTemplateToBuffer(text, r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue