feat: helmfile as a go library (#639)
* feat: helmfile as a go library This removes almost all the dependencies from the helmfile core logic to urfave/cli. `main.go` is now a thin wrapper around the core logic implemented in `pkg/app`.
This commit is contained in:
parent
f6057a1cca
commit
e2d6dc4afa
98
cmd/cmd.go
98
cmd/cmd.go
|
|
@ -1,98 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/roboll/helmfile/pkg/app"
|
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
|
||||||
"github.com/roboll/helmfile/pkg/state"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func VisitAllDesiredStates(c *cli.Context, converge func(*state.HelmState, helmexec.Interface, app.Context) (bool, []error)) error {
|
|
||||||
a, fileOrDir, err := InitAppEntry(c, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := app.NewContext()
|
|
||||||
|
|
||||||
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, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.VisitDesiredStates(fileOrDir, app.LoadOpts{Selectors: a.Selectors}, convergeWithHelmBinary)
|
|
||||||
|
|
||||||
return toCliError(c, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitAppEntry(c *cli.Context, reverse bool) (*app.App, string, error) {
|
|
||||||
if c.NArg() > 0 {
|
|
||||||
cli.ShowAppHelp(c)
|
|
||||||
return nil, "", fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
fileOrDir := c.GlobalString("file")
|
|
||||||
kubeContext := c.GlobalString("kube-context")
|
|
||||||
namespace := c.GlobalString("namespace")
|
|
||||||
selectors := c.GlobalStringSlice("selector")
|
|
||||||
logger := c.App.Metadata["logger"].(*zap.SugaredLogger)
|
|
||||||
|
|
||||||
env := c.GlobalString("environment")
|
|
||||||
if env == "" {
|
|
||||||
env = state.DefaultEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
app := app.Init(&app.App{
|
|
||||||
KubeContext: kubeContext,
|
|
||||||
Logger: logger,
|
|
||||||
Reverse: reverse,
|
|
||||||
Env: env,
|
|
||||||
Namespace: namespace,
|
|
||||||
Selectors: selectors,
|
|
||||||
})
|
|
||||||
|
|
||||||
return app, fileOrDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface, app.Context) []error) error {
|
|
||||||
a, fileOrDir, err := InitAppEntry(c, reverse)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := app.NewContext()
|
|
||||||
|
|
||||||
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) []error {
|
|
||||||
if c.GlobalString("helm-binary") != "" {
|
|
||||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
|
||||||
}
|
|
||||||
return converge(st, helm, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.VisitDesiredStatesWithReleasesFiltered(fileOrDir, convergeWithHelmBinary)
|
|
||||||
|
|
||||||
return toCliError(c, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toCliError(c *cli.Context, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
switch e := err.(type) {
|
|
||||||
case *app.NoMatchingHelmfileError:
|
|
||||||
noMatchingExitCode := 3
|
|
||||||
if c.GlobalBool("allow-no-matching-release") {
|
|
||||||
noMatchingExitCode = 0
|
|
||||||
}
|
|
||||||
return cli.NewExitError(e.Error(), noMatchingExitCode)
|
|
||||||
case *app.Error:
|
|
||||||
return cli.NewExitError(e.Error(), e.Code())
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
37
cmd/deps.go
37
cmd/deps.go
|
|
@ -1,37 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/roboll/helmfile/pkg/app"
|
|
||||||
"github.com/roboll/helmfile/pkg/argparser"
|
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
|
||||||
"github.com/roboll/helmfile/pkg/state"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Deps() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "deps",
|
|
||||||
Usage: "update charts based on the contents of requirements.yaml",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "args",
|
|
||||||
Value: "",
|
|
||||||
Usage: "pass args to helm exec",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return VisitAllDesiredStates(c, func(state *state.HelmState, helm helmexec.Interface, ctx app.Context) (bool, []error) {
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := state.UpdateDeps(helm)
|
|
||||||
|
|
||||||
ok := len(errs) == 0
|
|
||||||
|
|
||||||
return ok, errs
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
456
main.go
456
main.go
|
|
@ -2,9 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/roboll/helmfile/cmd"
|
|
||||||
"github.com/roboll/helmfile/pkg/app"
|
"github.com/roboll/helmfile/pkg/app"
|
||||||
"github.com/roboll/helmfile/pkg/argparser"
|
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
"github.com/roboll/helmfile/pkg/helmexec"
|
||||||
"github.com/roboll/helmfile/pkg/state"
|
"github.com/roboll/helmfile/pkg/state"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
@ -94,7 +92,20 @@ func main() {
|
||||||
|
|
||||||
cliApp.Before = configureLogging
|
cliApp.Before = configureLogging
|
||||||
cliApp.Commands = []cli.Command{
|
cliApp.Commands = []cli.Command{
|
||||||
cmd.Deps(),
|
{
|
||||||
|
Name: "deps",
|
||||||
|
Usage: "update charts based on the contents of requirements.yaml",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "args",
|
||||||
|
Value: "",
|
||||||
|
Usage: "pass args to helm exec",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
|
return run.Deps(c)
|
||||||
|
}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "repos",
|
Name: "repos",
|
||||||
Usage: "sync repositories from state file (helm repo add && helm repo update)",
|
Usage: "sync repositories from state file (helm repo add && helm repo update)",
|
||||||
|
|
@ -105,20 +116,9 @@ func main() {
|
||||||
Usage: "pass args to helm exec",
|
Usage: "pass args to helm exec",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return cmd.VisitAllDesiredStates(c, func(state *state.HelmState, helm helmexec.Interface, ctx app.Context) (bool, []error) {
|
return run.Repos(c)
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
}),
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := ctx.SyncReposOnce(state, helm)
|
|
||||||
|
|
||||||
ok := len(state.Repositories) > 0 && len(errs) == 0
|
|
||||||
|
|
||||||
return ok, errs
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "charts",
|
Name: "charts",
|
||||||
|
|
@ -139,14 +139,9 @@ func main() {
|
||||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
affectedReleases := state.AffectedReleases{}
|
return run.DeprecatedSyncCharts(c)
|
||||||
errs := findAndIterateOverDesiredStatesUsingFlags(c, func(st *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
}),
|
||||||
return executeSyncCommand(c, &affectedReleases, st, helm)
|
|
||||||
})
|
|
||||||
affectedReleases.DisplayAffectedReleases(c.App.Metadata["logger"].(*zap.SugaredLogger))
|
|
||||||
return errs
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "diff",
|
Name: "diff",
|
||||||
|
|
@ -179,24 +174,9 @@ func main() {
|
||||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx app.Context) []error {
|
return run.Diff(c)
|
||||||
if !c.Bool("skip-deps") {
|
}),
|
||||||
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
if errs := state.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errs := state.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
_, errs := ExecuteDiffCommand(NewUrfaveCliConfigImpl(c), state, helm, c.Bool("detailed-exitcode"), c.Bool("suppress-secrets"))
|
|
||||||
return errs
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "template",
|
Name: "template",
|
||||||
|
|
@ -221,22 +201,9 @@ func main() {
|
||||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx app.Context) []error {
|
return run.Template(c)
|
||||||
if !c.Bool("skip-deps") {
|
}),
|
||||||
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
if errs := state.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errs := state.PrepareReleases(helm, "template"); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return executeTemplateCommand(c, state, helm)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "lint",
|
Name: "lint",
|
||||||
|
|
@ -261,25 +228,9 @@ func main() {
|
||||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, ctx app.Context) []error {
|
return run.Lint(c)
|
||||||
values := c.StringSlice("values")
|
}),
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
workers := c.Int("concurrency")
|
|
||||||
if !c.Bool("skip-deps") {
|
|
||||||
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
if errs := state.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errs := state.PrepareReleases(helm, "lint"); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return state.LintReleases(helm, values, args, workers)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "sync",
|
Name: "sync",
|
||||||
|
|
@ -304,25 +255,9 @@ func main() {
|
||||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
affectedReleases := state.AffectedReleases{}
|
return run.Sync(c)
|
||||||
errs := findAndIterateOverDesiredStatesUsingFlags(c, func(st *state.HelmState, helm helmexec.Interface, ctx app.Context) []error {
|
}),
|
||||||
if !c.Bool("skip-deps") {
|
|
||||||
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errs := st.PrepareReleases(helm, "sync"); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return executeSyncCommand(c, &affectedReleases, st, helm)
|
|
||||||
})
|
|
||||||
affectedReleases.DisplayAffectedReleases(c.App.Metadata["logger"].(*zap.SugaredLogger))
|
|
||||||
return errs
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "apply",
|
Name: "apply",
|
||||||
|
|
@ -351,88 +286,9 @@ func main() {
|
||||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
affectedReleases := state.AffectedReleases{}
|
return run.Apply(c)
|
||||||
errs := findAndIterateOverDesiredStatesUsingFlags(c, func(st *state.HelmState, helm helmexec.Interface, ctx app.Context) []error {
|
}),
|
||||||
if !c.Bool("skip-deps") {
|
|
||||||
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
releases, errs := ExecuteDiffCommand(NewUrfaveCliConfigImpl(c), st, helm, true, c.Bool("suppress-secrets"))
|
|
||||||
|
|
||||||
releasesToBeDeleted, err := st.DetectReleasesToBeDeleted(helm)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fatalErrs := []error{}
|
|
||||||
|
|
||||||
noError := true
|
|
||||||
for _, e := range errs {
|
|
||||||
switch err := e.(type) {
|
|
||||||
case *state.ReleaseError:
|
|
||||||
if err.Code != 2 {
|
|
||||||
noError = false
|
|
||||||
fatalErrs = append(fatalErrs, e)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
noError = false
|
|
||||||
fatalErrs = append(fatalErrs, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync only when there are changes
|
|
||||||
if noError {
|
|
||||||
if len(releases) == 0 && len(releasesToBeDeleted) == 0 {
|
|
||||||
// TODO better way to get the logger
|
|
||||||
logger := c.App.Metadata["logger"].(*zap.SugaredLogger)
|
|
||||||
logger.Infof("")
|
|
||||||
logger.Infof("No affected releases")
|
|
||||||
} else {
|
|
||||||
names := []string{}
|
|
||||||
for _, r := range releases {
|
|
||||||
names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart))
|
|
||||||
}
|
|
||||||
for _, r := range releasesToBeDeleted {
|
|
||||||
names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart))
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(`Affected releases are:
|
|
||||||
%s
|
|
||||||
|
|
||||||
Do you really want to apply?
|
|
||||||
Helmfile will apply all your changes, as shown above.
|
|
||||||
|
|
||||||
`, strings.Join(names, "\n"))
|
|
||||||
interactive := c.GlobalBool("interactive")
|
|
||||||
if !interactive || interactive && askForConfirmation(msg) {
|
|
||||||
rs := []state.ReleaseSpec{}
|
|
||||||
for _, r := range releases {
|
|
||||||
rs = append(rs, *r)
|
|
||||||
}
|
|
||||||
for _, r := range releasesToBeDeleted {
|
|
||||||
rs = append(rs, *r)
|
|
||||||
}
|
|
||||||
|
|
||||||
st.Releases = rs
|
|
||||||
return executeSyncCommand(c, &affectedReleases, st, helm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fatalErrs
|
|
||||||
})
|
|
||||||
affectedReleases.DisplayAffectedReleases(c.App.Metadata["logger"].(*zap.SugaredLogger))
|
|
||||||
return errs
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "status",
|
Name: "status",
|
||||||
|
|
@ -449,18 +305,9 @@ Do you really want to apply?
|
||||||
Usage: "pass args to helm exec",
|
Usage: "pass args to helm exec",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
return run.Status(c)
|
||||||
workers := c.Int("concurrency")
|
}),
|
||||||
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.ReleaseStatuses(helm, workers)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
|
|
@ -476,37 +323,9 @@ Do you really want to apply?
|
||||||
Usage: "purge releases i.e. free release names and histories",
|
Usage: "purge releases i.e. free release names and histories",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
affectedReleases := state.AffectedReleases{}
|
return run.Delete(c)
|
||||||
errs := cmd.FindAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
}),
|
||||||
purge := c.Bool("purge")
|
|
||||||
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
names := make([]string, len(state.Releases))
|
|
||||||
for i, r := range state.Releases {
|
|
||||||
names[i] = fmt.Sprintf(" %s (%s)", r.Name, r.Chart)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(`Affected releases are:
|
|
||||||
%s
|
|
||||||
|
|
||||||
Do you really want to delete?
|
|
||||||
Helmfile will delete all your releases, as shown above.
|
|
||||||
|
|
||||||
`, strings.Join(names, "\n"))
|
|
||||||
interactive := c.GlobalBool("interactive")
|
|
||||||
if !interactive || interactive && askForConfirmation(msg) {
|
|
||||||
return state.DeleteReleases(&affectedReleases, helm, purge)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
affectedReleases.DisplayAffectedReleases(c.App.Metadata["logger"].(*zap.SugaredLogger))
|
|
||||||
return errs
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "destroy",
|
Name: "destroy",
|
||||||
|
|
@ -518,35 +337,9 @@ Do you really want to delete?
|
||||||
Usage: "pass args to helm exec",
|
Usage: "pass args to helm exec",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
affectedReleases := state.AffectedReleases{}
|
return run.Destroy(c)
|
||||||
errs := cmd.FindAndIterateOverDesiredStatesUsingFlagsWithReverse(c, true, func(state *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
}),
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
names := make([]string, len(state.Releases))
|
|
||||||
for i, r := range state.Releases {
|
|
||||||
names[i] = fmt.Sprintf(" %s (%s)", r.Name, r.Chart)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(`Affected releases are:
|
|
||||||
%s
|
|
||||||
|
|
||||||
Do you really want to delete?
|
|
||||||
Helmfile will delete all your releases, as shown above.
|
|
||||||
|
|
||||||
`, strings.Join(names, "\n"))
|
|
||||||
interactive := c.GlobalBool("interactive")
|
|
||||||
if !interactive || interactive && askForConfirmation(msg) {
|
|
||||||
return state.DeleteReleases(&affectedReleases, helm, true)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
affectedReleases.DisplayAffectedReleases(c.App.Metadata["logger"].(*zap.SugaredLogger))
|
|
||||||
return errs
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
|
|
@ -572,20 +365,9 @@ Do you really want to delete?
|
||||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: action(func(run *app.App, c configImpl) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface, _ app.Context) []error {
|
return run.Test(c)
|
||||||
cleanup := c.Bool("cleanup")
|
}),
|
||||||
timeout := c.Int("timeout")
|
|
||||||
concurrency := c.Int("concurrency")
|
|
||||||
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.TestReleases(helm, cleanup, timeout, concurrency)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,41 +378,19 @@ Do you really want to delete?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeSyncCommand(c *cli.Context, affectedReleases *state.AffectedReleases, state *state.HelmState, helm helmexec.Interface) []error {
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
if len(args) > 0 {
|
|
||||||
helm.SetExtraArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
values := c.StringSlice("values")
|
|
||||||
workers := c.Int("concurrency")
|
|
||||||
|
|
||||||
return state.SyncReleases(affectedReleases, helm, values, workers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeTemplateCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface) []error {
|
|
||||||
args := argparser.GetArgs(c.String("args"), state)
|
|
||||||
values := c.StringSlice("values")
|
|
||||||
workers := c.Int("concurrency")
|
|
||||||
|
|
||||||
return state.TemplateReleases(helm, values, args, workers)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config interface {
|
|
||||||
HasCommandName(string) bool
|
|
||||||
Values() []string
|
|
||||||
Concurrency() int
|
|
||||||
Args() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type configImpl struct {
|
type configImpl struct {
|
||||||
c *cli.Context
|
c *cli.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUrfaveCliConfigImpl(c *cli.Context) configImpl {
|
func NewUrfaveCliConfigImpl(c *cli.Context) (configImpl, error) {
|
||||||
|
if c.NArg() > 0 {
|
||||||
|
cli.ShowAppHelp(c)
|
||||||
|
return configImpl{}, fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return configImpl{
|
return configImpl{
|
||||||
c: c,
|
c: c,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c configImpl) Values() []string {
|
func (c configImpl) Values() []string {
|
||||||
|
|
@ -649,17 +409,105 @@ func (c configImpl) HasCommandName(name string) bool {
|
||||||
return c.c.Command.HasName(name)
|
return c.c.Command.HasName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecuteDiffCommand(c Config, st *state.HelmState, helm helmexec.Interface, detailedExitCode, suppressSecrets bool) ([]*state.ReleaseSpec, []error) {
|
// DiffConfig
|
||||||
args := argparser.GetArgs(c.Args(), st)
|
|
||||||
if len(args) > 0 {
|
func (c configImpl) SkipDeps() bool {
|
||||||
helm.SetExtraArgs(args...)
|
return c.c.Bool("skip-deps")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) DetailedExitcode() bool {
|
||||||
|
return c.c.Bool("detailed-exitcode")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) SuppressSecrets() bool {
|
||||||
|
return c.c.Bool("suppress-secrets")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfig
|
||||||
|
|
||||||
|
func (c configImpl) Purge() bool {
|
||||||
|
return c.c.Bool("purge")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConfig
|
||||||
|
|
||||||
|
func (c configImpl) Cleanup() bool {
|
||||||
|
return c.c.Bool("cleanup")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Timeout() int {
|
||||||
|
return c.c.Int("timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalConfig
|
||||||
|
|
||||||
|
func (c configImpl) HelmBinary() string {
|
||||||
|
return c.c.GlobalString("helm-binary")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) KubeContext() string {
|
||||||
|
return c.c.GlobalString("kube-context")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Namespace() string {
|
||||||
|
return c.c.GlobalString("namespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) FileOrDir() string {
|
||||||
|
return c.c.GlobalString("file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Selectors() []string {
|
||||||
|
return c.c.GlobalStringSlice("selector")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Interactive() bool {
|
||||||
|
return c.c.GlobalBool("interactive")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Logger() *zap.SugaredLogger {
|
||||||
|
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configImpl) Env() string {
|
||||||
|
env := c.c.GlobalString("environment")
|
||||||
|
if env == "" {
|
||||||
|
env = state.DefaultEnv
|
||||||
}
|
}
|
||||||
|
return env
|
||||||
triggerCleanupEvents := c.HasCommandName("diff")
|
|
||||||
|
|
||||||
return st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, suppressSecrets, triggerCleanupEvents)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface, app.Context) []error) error {
|
func action(do func(*app.App, configImpl) error) func(*cli.Context) error {
|
||||||
return cmd.FindAndIterateOverDesiredStatesUsingFlagsWithReverse(c, false, converge)
|
return func(implCtx *cli.Context) error {
|
||||||
|
conf, err := NewUrfaveCliConfigImpl(implCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a := app.New(conf)
|
||||||
|
|
||||||
|
a.ErrorHandler = func(err error) error {
|
||||||
|
return toCliError(implCtx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return do(a, conf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCliError(c *cli.Context, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *app.NoMatchingHelmfileError:
|
||||||
|
noMatchingExitCode := 3
|
||||||
|
if c.GlobalBool("allow-no-matching-release") {
|
||||||
|
noMatchingExitCode = 0
|
||||||
|
}
|
||||||
|
return cli.NewExitError(e.Error(), noMatchingExitCode)
|
||||||
|
case *app.Error:
|
||||||
|
return cli.NewExitError(e.Error(), e.Code())
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
123
pkg/app/app.go
123
pkg/app/app.go
|
|
@ -24,6 +24,12 @@ type App struct {
|
||||||
Env string
|
Env string
|
||||||
Namespace string
|
Namespace string
|
||||||
Selectors []string
|
Selectors []string
|
||||||
|
HelmBinary string
|
||||||
|
Args string
|
||||||
|
|
||||||
|
FileOrDir string
|
||||||
|
|
||||||
|
ErrorHandler func(error) error
|
||||||
|
|
||||||
readFile func(string) ([]byte, error)
|
readFile func(string) ([]byte, error)
|
||||||
fileExists func(string) (bool, error)
|
fileExists func(string) (bool, error)
|
||||||
|
|
@ -36,6 +42,19 @@ type App struct {
|
||||||
chdir func(string) error
|
chdir func(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func New(conf ConfigProvider) *App {
|
||||||
|
return Init(&App{
|
||||||
|
KubeContext: conf.KubeContext(),
|
||||||
|
Logger: conf.Logger(),
|
||||||
|
Env: conf.Env(),
|
||||||
|
Namespace: conf.Namespace(),
|
||||||
|
Selectors: conf.Selectors(),
|
||||||
|
HelmBinary: conf.HelmBinary(),
|
||||||
|
Args: conf.Args(),
|
||||||
|
FileOrDir: conf.FileOrDir(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Init(app *App) *App {
|
func Init(app *App) *App {
|
||||||
app.readFile = ioutil.ReadFile
|
app.readFile = ioutil.ReadFile
|
||||||
app.glob = filepath.Glob
|
app.glob = filepath.Glob
|
||||||
|
|
@ -48,6 +67,84 @@ func Init(app *App) *App {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) Deps(c DepsConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Deps(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Repos(c ReposConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Repos(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) reverse() *App {
|
||||||
|
new := *a
|
||||||
|
new.Reverse = true
|
||||||
|
return &new
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.DeprecatedSyncCharts(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Diff(c DiffConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Diff(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Template(c TemplateConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Template(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Lint(c LintConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Lint(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Sync(c SyncConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Sync(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Apply(c ApplyConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Apply(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Status(c StatusesConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Status(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Delete(c DeleteConfigProvider) error {
|
||||||
|
return a.reverse().ForEachState(func(run *Run) []error {
|
||||||
|
return run.Delete(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Destroy(c DestroyConfigProvider) error {
|
||||||
|
return a.reverse().ForEachState(func(run *Run) []error {
|
||||||
|
return run.Destroy(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Test(c TestConfigProvider) error {
|
||||||
|
return a.ForEachState(func(run *Run) []error {
|
||||||
|
return run.Test(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) within(dir string, do func() error) error {
|
func (a *App) within(dir string, do func() error) error {
|
||||||
if dir == "." {
|
if dir == "." {
|
||||||
return do()
|
return do()
|
||||||
|
|
@ -140,7 +237,7 @@ func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.He
|
||||||
return ld.Load(file, op)
|
return ld.Load(file, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) VisitDesiredStates(fileOrDir string, opts LoadOpts, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
|
func (a *App) visitStates(fileOrDir string, opts LoadOpts, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
|
||||||
noMatchInHelmfiles := true
|
noMatchInHelmfiles := true
|
||||||
|
|
||||||
err := a.visitStateFiles(fileOrDir, func(f, d string) error {
|
err := a.visitStateFiles(fileOrDir, func(f, d string) error {
|
||||||
|
|
@ -195,7 +292,7 @@ func (a *App) VisitDesiredStates(fileOrDir string, opts LoadOpts, converge func(
|
||||||
} else {
|
} else {
|
||||||
optsForNestedState.Selectors = m.Selectors
|
optsForNestedState.Selectors = m.Selectors
|
||||||
}
|
}
|
||||||
if err := a.VisitDesiredStates(m.Path, optsForNestedState, converge); err != nil {
|
if err := a.visitStates(m.Path, optsForNestedState, converge); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case *NoMatchingHelmfileError:
|
case *NoMatchingHelmfileError:
|
||||||
|
|
||||||
|
|
@ -229,10 +326,26 @@ func (a *App) VisitDesiredStates(fileOrDir string, opts LoadOpts, converge func(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) ForEachState(do func(*Run) []error) error {
|
||||||
|
err := a.VisitDesiredStatesWithReleasesFiltered(a.FileOrDir, func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
ctx := NewContext()
|
||||||
|
|
||||||
|
run := NewRun(st, helm, ctx)
|
||||||
|
|
||||||
|
return do(run)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil && a.ErrorHandler != nil {
|
||||||
|
return a.ErrorHandler(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
||||||
opts := LoadOpts{Selectors: a.Selectors}
|
opts := LoadOpts{Selectors: a.Selectors}
|
||||||
|
|
||||||
err := a.VisitDesiredStates(fileOrDir, opts, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
|
err := a.visitStates(fileOrDir, opts, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
|
||||||
if len(st.Selectors) > 0 {
|
if len(st.Selectors) > 0 {
|
||||||
err := st.FilterReleases()
|
err := st.FilterReleases()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -240,6 +353,10 @@ func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.HelmBinary != "" {
|
||||||
|
helm.SetHelmBinary(a.HelmBinary)
|
||||||
|
}
|
||||||
|
|
||||||
type Key struct {
|
type Key struct {
|
||||||
TillerNamespace, Name string
|
TillerNamespace, Name string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
// Copyright (c) 2017 Roland Singer [roland.singer@desertbit.com]
|
// 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
|
// Shamelessly borrowed from @r0l1's awesome work that is available at https://gist.github.com/r0l1/3dcbb0c8f6cfe9c66ab8008f55f8f28b
|
||||||
func askForConfirmation(s string) bool {
|
func AskForConfirmation(s string) bool {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "go.uber.org/zap"
|
||||||
|
|
||||||
|
type ConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
HelmBinary() string
|
||||||
|
|
||||||
|
FileOrDir() string
|
||||||
|
KubeContext() string
|
||||||
|
Namespace() string
|
||||||
|
Selectors() []string
|
||||||
|
Env() string
|
||||||
|
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeprecatedChartsConfigProvider interface {
|
||||||
|
Values() []string
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type DepsConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReposConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplyConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Values() []string
|
||||||
|
SkipDeps() bool
|
||||||
|
|
||||||
|
SuppressSecrets() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
interactive
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Values() []string
|
||||||
|
SkipDeps() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiffConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Values() []string
|
||||||
|
SkipDeps() bool
|
||||||
|
|
||||||
|
SuppressSecrets() bool
|
||||||
|
|
||||||
|
DetailedExitcode() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Purge() bool
|
||||||
|
|
||||||
|
interactive
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type DestroyConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
interactive
|
||||||
|
loggingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Timeout() int
|
||||||
|
Cleanup() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type LintConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Values() []string
|
||||||
|
SkipDeps() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
Values() []string
|
||||||
|
SkipDeps() bool
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusesConfigProvider interface {
|
||||||
|
Args() string
|
||||||
|
|
||||||
|
concurrencyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type concurrencyConfig interface {
|
||||||
|
Concurrency() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggingConfig interface {
|
||||||
|
Logger() *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
type interactive interface {
|
||||||
|
Interactive() bool
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/roboll/helmfile/pkg/argparser"
|
||||||
|
"github.com/roboll/helmfile/pkg/helmexec"
|
||||||
|
"github.com/roboll/helmfile/pkg/state"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Run struct {
|
||||||
|
state *state.HelmState
|
||||||
|
helm helmexec.Interface
|
||||||
|
ctx Context
|
||||||
|
|
||||||
|
Ask func(string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRun(st *state.HelmState, helm helmexec.Interface, ctx Context) *Run {
|
||||||
|
return &Run{state: st, helm: helm, ctx: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) askForConfirmation(msg string) bool {
|
||||||
|
if r.Ask != nil {
|
||||||
|
return r.Ask(msg)
|
||||||
|
}
|
||||||
|
return AskForConfirmation(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Deps(c DepsConfigProvider) []error {
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
return r.state.UpdateDeps(r.helm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Repos(c ReposConfigProvider) []error {
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
return r.ctx.SyncReposOnce(r.state, r.helm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) []error {
|
||||||
|
st := r.state
|
||||||
|
helm := r.helm
|
||||||
|
|
||||||
|
affectedReleases := state.AffectedReleases{}
|
||||||
|
errs := st.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency())
|
||||||
|
affectedReleases.DisplayAffectedReleases(c.Logger())
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Status(c StatusesConfigProvider) []error {
|
||||||
|
workers := c.Concurrency()
|
||||||
|
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
return r.state.ReleaseStatuses(r.helm, workers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Delete(c DeleteConfigProvider) []error {
|
||||||
|
affectedReleases := state.AffectedReleases{}
|
||||||
|
purge := c.Purge()
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
|
||||||
|
names := make([]string, len(r.state.Releases))
|
||||||
|
for i, r := range r.state.Releases {
|
||||||
|
names[i] = fmt.Sprintf(" %s (%s)", r.Name, r.Chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(`Affected releases are:
|
||||||
|
%s
|
||||||
|
|
||||||
|
Do you really want to delete?
|
||||||
|
Helmfile will delete all your releases, as shown above.
|
||||||
|
|
||||||
|
`, strings.Join(names, "\n"))
|
||||||
|
interactive := c.Interactive()
|
||||||
|
if !interactive || interactive && r.askForConfirmation(msg) {
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
errs = r.state.DeleteReleases(&affectedReleases, r.helm, purge)
|
||||||
|
}
|
||||||
|
affectedReleases.DisplayAffectedReleases(c.Logger())
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Destroy(c DestroyConfigProvider) []error {
|
||||||
|
errs := []error{}
|
||||||
|
affectedReleases := state.AffectedReleases{}
|
||||||
|
|
||||||
|
names := make([]string, len(r.state.Releases))
|
||||||
|
for i, r := range r.state.Releases {
|
||||||
|
names[i] = fmt.Sprintf(" %s (%s)", r.Name, r.Chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(`Affected releases are:
|
||||||
|
%s
|
||||||
|
|
||||||
|
Do you really want to delete?
|
||||||
|
Helmfile will delete all your releases, as shown above.
|
||||||
|
|
||||||
|
`, strings.Join(names, "\n"))
|
||||||
|
interactive := c.Interactive()
|
||||||
|
if !interactive || interactive && r.askForConfirmation(msg) {
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
errs = r.state.DeleteReleases(&affectedReleases, r.helm, true)
|
||||||
|
}
|
||||||
|
affectedReleases.DisplayAffectedReleases(c.Logger())
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Apply(c ApplyConfigProvider) []error {
|
||||||
|
st := r.state
|
||||||
|
helm := r.helm
|
||||||
|
ctx := r.ctx
|
||||||
|
|
||||||
|
affectedReleases := state.AffectedReleases{}
|
||||||
|
if !c.SkipDeps() {
|
||||||
|
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly
|
||||||
|
detailedExitCode := true
|
||||||
|
|
||||||
|
releases, errs := st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.SuppressSecrets(), false)
|
||||||
|
|
||||||
|
releasesToBeDeleted, err := st.DetectReleasesToBeDeleted(helm)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fatalErrs := []error{}
|
||||||
|
|
||||||
|
noError := true
|
||||||
|
for _, e := range errs {
|
||||||
|
switch err := e.(type) {
|
||||||
|
case *state.ReleaseError:
|
||||||
|
if err.Code != 2 {
|
||||||
|
noError = false
|
||||||
|
fatalErrs = append(fatalErrs, e)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
noError = false
|
||||||
|
fatalErrs = append(fatalErrs, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync only when there are changes
|
||||||
|
if noError {
|
||||||
|
if len(releases) == 0 && len(releasesToBeDeleted) == 0 {
|
||||||
|
// TODO better way to get the logger
|
||||||
|
logger := c.Logger()
|
||||||
|
logger.Infof("")
|
||||||
|
logger.Infof("No affected releases")
|
||||||
|
} else {
|
||||||
|
names := []string{}
|
||||||
|
for _, r := range releases {
|
||||||
|
names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart))
|
||||||
|
}
|
||||||
|
for _, r := range releasesToBeDeleted {
|
||||||
|
names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(`Affected releases are:
|
||||||
|
%s
|
||||||
|
|
||||||
|
Do you really want to apply?
|
||||||
|
Helmfile will apply all your changes, as shown above.
|
||||||
|
|
||||||
|
`, strings.Join(names, "\n"))
|
||||||
|
interactive := c.Interactive()
|
||||||
|
if !interactive || interactive && r.askForConfirmation(msg) {
|
||||||
|
rs := []state.ReleaseSpec{}
|
||||||
|
for _, r := range releases {
|
||||||
|
rs = append(rs, *r)
|
||||||
|
}
|
||||||
|
for _, r := range releasesToBeDeleted {
|
||||||
|
rs = append(rs, *r)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
st.Releases = rs
|
||||||
|
return st.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
affectedReleases.DisplayAffectedReleases(c.Logger())
|
||||||
|
return fatalErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Diff(c DiffConfigProvider) []error {
|
||||||
|
st := r.state
|
||||||
|
helm := r.helm
|
||||||
|
ctx := r.ctx
|
||||||
|
|
||||||
|
if !c.SkipDeps() {
|
||||||
|
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errs := st.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
_, errs := st.DiffReleases(helm, c.Values(), c.Concurrency(), c.DetailedExitcode(), c.SuppressSecrets(), true)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Sync(c SyncConfigProvider) []error {
|
||||||
|
st := r.state
|
||||||
|
helm := r.helm
|
||||||
|
ctx := r.ctx
|
||||||
|
|
||||||
|
affectedReleases := state.AffectedReleases{}
|
||||||
|
if !c.SkipDeps() {
|
||||||
|
if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errs := st.PrepareReleases(helm, "sync"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
errs := st.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency())
|
||||||
|
affectedReleases.DisplayAffectedReleases(c.Logger())
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Template(c TemplateConfigProvider) []error {
|
||||||
|
state := r.state
|
||||||
|
helm := r.helm
|
||||||
|
ctx := r.ctx
|
||||||
|
|
||||||
|
if !c.SkipDeps() {
|
||||||
|
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if errs := state.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errs := state.PrepareReleases(helm, "template"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
args := argparser.GetArgs(c.Args(), state)
|
||||||
|
return state.TemplateReleases(helm, c.Values(), args, c.Concurrency())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Test(c TestConfigProvider) []error {
|
||||||
|
cleanup := c.Cleanup()
|
||||||
|
timeout := c.Timeout()
|
||||||
|
concurrency := c.Concurrency()
|
||||||
|
|
||||||
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
|
||||||
|
return r.state.TestReleases(r.helm, cleanup, timeout, concurrency)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Run) Lint(c LintConfigProvider) []error {
|
||||||
|
state := r.state
|
||||||
|
helm := r.helm
|
||||||
|
ctx := r.ctx
|
||||||
|
|
||||||
|
values := c.Values()
|
||||||
|
args := argparser.GetArgs(c.Args(), state)
|
||||||
|
workers := c.Concurrency()
|
||||||
|
if !c.SkipDeps() {
|
||||||
|
if errs := ctx.SyncReposOnce(state, helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
if errs := state.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errs := state.PrepareReleases(helm, "lint"); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return state.LintReleases(helm, values, args, workers)
|
||||||
|
}
|
||||||
|
|
@ -514,6 +514,9 @@ func (st *HelmState) downloadCharts(helm helmexec.Interface, dir string, concurr
|
||||||
|
|
||||||
// TemplateReleases wrapper for executing helm template on the releases
|
// TemplateReleases wrapper for executing helm template on the releases
|
||||||
func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
||||||
|
// Reset the extra args if already set, not to break `helm fetch` by adding the args intended for `lint`
|
||||||
|
helm.SetExtraArgs()
|
||||||
|
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
// Create tmp directory and bail immediately if it fails
|
// Create tmp directory and bail immediately if it fails
|
||||||
dir, err := ioutil.TempDir("", "")
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
|
@ -577,6 +580,9 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues
|
||||||
|
|
||||||
// LintReleases wrapper for executing helm lint on the releases
|
// LintReleases wrapper for executing helm lint on the releases
|
||||||
func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
||||||
|
// Reset the extra args if already set, not to break `helm fetch` by adding the args intended for `lint`
|
||||||
|
helm.SetExtraArgs()
|
||||||
|
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
// Create tmp directory and bail immediately if it fails
|
// Create tmp directory and bail immediately if it fails
|
||||||
dir, err := ioutil.TempDir("", "")
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue