Skip to content

Commit

Permalink
feat: `helmfile apply [--auto-approve]
Browse files Browse the repository at this point in the history
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 roboll#205
  • Loading branch information
mumoshu committed Aug 30, 2018
1 parent bb3b44e commit 255518b
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 35 deletions.
33 changes: 33 additions & 0 deletions ask.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
134 changes: 99 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
})
},
},
Expand Down Expand Up @@ -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
}
}

if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
return errs
}
msg := `Do you really want to apply?
Helmfile will apply all your changes, as shown above.
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 allErrsIndicateChanges {
autoApprove := c.Bool("auto-approve")
if autoApprove || !autoApprove && askForConfirmation(msg) {
return executeSyncCommand(c, state, helm)
}
}
}

values := c.StringSlice("values")
workers := c.Int("concurrency")

return state.SyncReleases(helm, values, workers)
return errs
})
},
},
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 255518b

Please sign in to comment.