feat: helmfile apply [--auto-approve] (#263)
This command syncs releases only if there is any difference between the desired and the current state. It asks for an confirmation by default. Provide `--auto-approve` flag after the `apply` command to skip it. Resolves #205
This commit is contained in:
parent
bb3b44e511
commit
3840605e04
39
README.md
39
README.md
|
|
@ -185,25 +185,28 @@ USAGE:
|
|||
|
||||
COMMANDS:
|
||||
repos sync repositories from state file (helm repo add && helm repo update)
|
||||
charts sync charts from state file (helm upgrade --install)
|
||||
diff diff charts from state file against env (helm diff)
|
||||
charts sync releases from state file (helm upgrade --install)
|
||||
diff diff releases from state file against env (helm diff)
|
||||
lint lint charts from state file (helm lint)
|
||||
sync sync all resources from state file (repos, charts and local chart deps)
|
||||
sync sync all resources from state file (repos, releases and chart deps)
|
||||
apply apply all resources from state file only when there are changes
|
||||
status retrieve status of releases in state file
|
||||
delete delete charts from state file (helm delete)
|
||||
test tets releases from state file (helm test)
|
||||
delete delete releases from state file (helm delete)
|
||||
test test releases from state file (helm test)
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--file FILE, -f FILE load config from FILE (default: "helmfile.yaml")
|
||||
--quiet, -q silence output
|
||||
--namespace value, -n value Set namespace. Uses the namespace set in the context by default
|
||||
--selector,l value Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
|
||||
A release must match all labels in a group in order to be used. Multiple groups can be specified at once.
|
||||
--selector tier=frontend,tier!=proxy --selector tier=backend. Will match all frontend, non-proxy releases AND all backend releases.
|
||||
The name of a release can be used as a label. --selector name=myrelease
|
||||
--kube-context value Set kubectl context. Uses current context by default
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
--helm-binary value, -b value path to helm binary
|
||||
--file helmfile.yaml, -f helmfile.yaml load config from file or directory. defaults to helmfile.yaml or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference
|
||||
--quiet, -q Silence output. Equivalent to log-level warn
|
||||
--kube-context value Set kubectl context. Uses current context by default
|
||||
--log-level value Set log level, default info
|
||||
--namespace value, -n value Set namespace. Uses the namespace set in the context by default
|
||||
--selector value, -l value Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
|
||||
A release must match all labels in a group in order to be used. Multiple groups can be specified at once.
|
||||
--selector tier=frontend,tier!=proxy --selector tier=backend. Will match all frontend, non-proxy releases AND all backend releases.
|
||||
The name of a release can be used as a label. --selector name=myrelease
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
### sync
|
||||
|
|
@ -224,6 +227,12 @@ To supply the diff functionality Helmfile needs the [helm-diff](https://github.c
|
|||
you should be able to simply execute `helm plugin install https://github.com/databus23/helm-diff`. For more details
|
||||
please look at their [documentation](https://github.com/databus23/helm-diff#helm-diff-plugin).
|
||||
|
||||
### apply
|
||||
|
||||
The `helmfile apply` sub-command begins by executing `diff`. If `diff` finds that there is any changes, `sync` is executed after prompting you for an confirmation. `--auto-approve` skips the confirmation.
|
||||
|
||||
An expected use-case of `apply` is to schedule it to run periodically, so that you can auto-fix skews between the desired and the current state of your apps running on Kubernetes clusters.
|
||||
|
||||
### delete
|
||||
|
||||
The `helmfile delete` sub-command deletes all the releases defined in the manfiests
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Copyright (c) 2017 Roland Singer [roland.singer@desertbit.com]
|
||||
//
|
||||
// Shamelessly borrowed from @r0l1's awesome work that is available at https://gist.github.com/r0l1/3dcbb0c8f6cfe9c66ab8008f55f8f28b
|
||||
func askForConfirmation(s string) bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
for {
|
||||
fmt.Printf("%s [y/n]: ", s)
|
||||
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
|
||||
if response == "y" || response == "yes" {
|
||||
return true
|
||||
} else if response == "n" || response == "no" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
138
main.go
138
main.go
|
|
@ -184,25 +184,7 @@ func main() {
|
|||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
|
||||
args := args.GetArgs(c.String("args"), state)
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(args...)
|
||||
}
|
||||
if c.GlobalString("helm-binary") != "" {
|
||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||
}
|
||||
|
||||
if c.Bool("sync-repos") {
|
||||
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
detailedExitCode := c.Bool("detailed-exitcode")
|
||||
|
||||
return state.DiffReleases(helm, values, workers, detailedExitCode)
|
||||
return executeDiffCommand(c, state, helm, c.Bool("detailed-exitcode"))
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
@ -263,26 +245,64 @@ func main() {
|
|||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
|
||||
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
return executeSyncCommand(c, state, helm)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "apply",
|
||||
Usage: "apply all resources from state file only when there are changes",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "auto-approve",
|
||||
Usage: "Skip interactive approval before applying",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
|
||||
errs := executeDiffCommand(c, state, helm, true)
|
||||
|
||||
// sync only when there are changes
|
||||
if len(errs) > 0 {
|
||||
allErrsIndicateChanges := true
|
||||
for _, err := range errs {
|
||||
switch e := err.(type) {
|
||||
case *exec.ExitError:
|
||||
status := e.Sys().(syscall.WaitStatus)
|
||||
// `helm diff --detailed-exitcode` returns 2 when there are changes
|
||||
allErrsIndicateChanges = allErrsIndicateChanges && status.ExitStatus() == 2
|
||||
default:
|
||||
allErrsIndicateChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
msg := `Do you really want to apply?
|
||||
Helmfile will apply all your changes, as shown above.
|
||||
|
||||
`
|
||||
if allErrsIndicateChanges {
|
||||
autoApprove := c.Bool("auto-approve")
|
||||
if autoApprove || !autoApprove && askForConfirmation(msg) {
|
||||
return executeSyncCommand(c, state, helm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
args := args.GetArgs(c.String("args"), state)
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(args...)
|
||||
}
|
||||
if c.GlobalString("helm-binary") != "" {
|
||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
|
||||
return state.SyncReleases(helm, values, workers)
|
||||
return errs
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
@ -393,6 +413,50 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func executeSyncCommand(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)
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(args...)
|
||||
}
|
||||
if c.GlobalString("helm-binary") != "" {
|
||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
|
||||
return state.SyncReleases(helm, values, workers)
|
||||
}
|
||||
|
||||
func executeDiffCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface, detailedExitCode bool) []error {
|
||||
args := args.GetArgs(c.String("args"), state)
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(args...)
|
||||
}
|
||||
if c.GlobalString("helm-binary") != "" {
|
||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||
}
|
||||
|
||||
if c.Bool("sync-repos") {
|
||||
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
|
||||
return state.DiffReleases(helm, values, workers, detailedExitCode)
|
||||
}
|
||||
|
||||
func eachDesiredStateDo(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
||||
fileOrDirPath := c.GlobalString("file")
|
||||
desiredStateFiles, err := findDesiredStateFiles(fileOrDirPath)
|
||||
|
|
|
|||
Loading…
Reference in New Issue