Skip to content

Commit

Permalink
code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Luis Davim committed Dec 31, 2019
1 parent 5e3ef5a commit a5c49ed
Show file tree
Hide file tree
Showing 18 changed files with 600 additions and 708 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ To limit execution to specific application:

- **Built for CD**: Helmsman can be used as a docker image or a binary.
- **Applications as code**: describe your desired applications and manage them from a single version-controlled declarative file.
- **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS.
- **Suitable for Multitenant Clusters**: deploy Tiller in different namespaces with service accounts and TLS (versions 1.x).
- **Easy to use**: deep knowledge of Helm CLI and Kubectl is NOT mandatory to use Helmsman.
- **Plan, View, apply**: you can run Helmsman to generate and view a plan with/without executing it.
- **Portable**: Helmsman can be used to manage charts deployments on any k8s cluster.
Expand Down
219 changes: 131 additions & 88 deletions internal/app/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,49 @@ const (
slogan = "A Helm-Charts-as-Code tool.\n\n"
)

// Allow parsing of multiple string command line options into an array of strings
type stringArray []string

func (i *stringArray) String() string {
return strings.Join(*i, " ")
}

func (i *stringArray) Set(value string) error {
*i = append(*i, value)
return nil
}

type cli struct {
debug bool
files stringArray
envFiles stringArray
target stringArray
group stringArray
kubeconfig string
apply bool
destroy bool
dryRun bool
verbose bool
noBanner bool
noColors bool
noFancy bool
noNs bool
nsOverride string
skipValidation bool
keepUntrackedReleases bool
showDiff bool
suppressDiffSecrets bool
diffContext int
noEnvSubst bool
noEnvValuesSubst bool
noSSMSubst bool
noSSMValuesSubst bool
updateDeps bool
forceUpgrades bool
noDefaultRepos bool
version bool
}

func printUsage() {
fmt.Print(banner)
fmt.Printf("Helmsman version: " + appVersion + "\n")
Expand All @@ -32,71 +75,68 @@ func printUsage() {
}

// Cli parses cmd flags, validates them and performs some initializations
func Cli() {
func (c *cli) parse() {
//parsing command line flags
flag.Var(&files, "f", "desired state file name(s), may be supplied more than once to merge state files")
flag.Var(&envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once")
flag.StringVar(&kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests")
flag.BoolVar(&apply, "apply", false, "apply the plan directly")
flag.BoolVar(&debug, "debug", false, "show the execution logs")
flag.BoolVar(&dryRun, "dry-run", false, "apply the dry-run option for helm commands.")
flag.Var(&target, "target", "limit execution to specific app.")
flag.Var(&group, "group", "limit execution to specific group of apps.")
flag.BoolVar(&destroy, "destroy", false, "delete all deployed releases.")
flag.BoolVar(&v, "v", false, "show the version")
flag.BoolVar(&verbose, "verbose", false, "show verbose execution logs")
flag.BoolVar(&noBanner, "no-banner", false, "don't show the banner")
flag.BoolVar(&noColors, "no-color", false, "don't use colors")
flag.BoolVar(&noFancy, "no-fancy", false, "don't display the banner and don't use colors")
flag.BoolVar(&noNs, "no-ns", false, "don't create namespaces")
flag.StringVar(&nsOverride, "ns-override", "", "override defined namespaces with this one")
flag.BoolVar(&skipValidation, "skip-validation", false, "skip desired state validation")
flag.BoolVar(&keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.")
flag.BoolVar(&showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.")
flag.BoolVar(&suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).")
flag.IntVar(&diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output")
flag.BoolVar(&noEnvSubst, "no-env-subst", false, "turn off environment substitution globally")
flag.BoolVar(&noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).")
flag.BoolVar(&noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally")
flag.BoolVar(&noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only")
flag.BoolVar(&updateDeps, "update-deps", false, "run 'helm dep up' for local chart")
flag.BoolVar(&forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.")
flag.BoolVar(&noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'")
flag.Var(&c.files, "f", "desired state file name(s), may be supplied more than once to merge state files")
flag.Var(&c.envFiles, "e", "file(s) to load environment variables from (default .env), may be supplied more than once")
flag.Var(&c.target, "target", "limit execution to specific app.")
flag.Var(&c.group, "group", "limit execution to specific group of apps.")
flag.IntVar(&c.diffContext, "diff-context", -1, "number of lines of context to show around changes in helm diff output")
flag.StringVar(&c.kubeconfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests")
flag.StringVar(&c.nsOverride, "ns-override", "", "override defined namespaces with this one")
flag.BoolVar(&c.apply, "apply", false, "apply the plan directly")
flag.BoolVar(&c.dryRun, "dry-run", false, "apply the dry-run option for helm commands.")
flag.BoolVar(&c.destroy, "destroy", false, "delete all deployed releases.")
flag.BoolVar(&c.version, "v", false, "show the version")
flag.BoolVar(&c.debug, "debug", false, "show the execution logs")
flag.BoolVar(&c.verbose, "verbose", false, "show verbose execution logs")
flag.BoolVar(&c.noBanner, "no-banner", false, "don't show the banner")
flag.BoolVar(&c.noColors, "no-color", false, "don't use colors")
flag.BoolVar(&c.noFancy, "no-fancy", false, "don't display the banner and don't use colors")
flag.BoolVar(&c.noNs, "no-ns", false, "don't create namespaces")
flag.BoolVar(&c.skipValidation, "skip-validation", false, "skip desired state validation")
flag.BoolVar(&c.keepUntrackedReleases, "keep-untracked-releases", false, "keep releases that are managed by Helmsman from the used DSFs in the command, and are no longer tracked in your desired state.")
flag.BoolVar(&c.showDiff, "show-diff", false, "show helm diff results. Can expose sensitive information.")
flag.BoolVar(&c.suppressDiffSecrets, "suppress-diff-secrets", true, "don't show secrets in helm diff output. (default true).")
flag.BoolVar(&c.noEnvSubst, "no-env-subst", false, "turn off environment substitution globally")
flag.BoolVar(&c.noEnvValuesSubst, "no-env-values-subst", true, "turn off environment substitution in values files only. (default true).")
flag.BoolVar(&c.noSSMSubst, "no-ssm-subst", false, "turn off SSM parameter substitution globally")
flag.BoolVar(&c.noSSMValuesSubst, "no-ssm-values-subst", true, "turn off SSM parameter substitution in values files only")
flag.BoolVar(&c.updateDeps, "update-deps", false, "run 'helm dep up' for local chart")
flag.BoolVar(&c.forceUpgrades, "force-upgrades", false, "use --force when upgrading helm releases. May cause resources to be recreated.")
flag.BoolVar(&c.noDefaultRepos, "no-default-repos", false, "don't set default Helm repos from Google for 'stable' and 'incubator'")
flag.Usage = printUsage
flag.Parse()

if v {
if c.version {
fmt.Println("Helmsman version: " + appVersion)
os.Exit(0)
}

if noFancy {
noColors = true
noBanner = true
if c.noFancy {
c.noColors = true
c.noBanner = true
}
initLogs(verbose, noColors)
verbose := c.verbose || c.debug
initLogs(verbose, c.noColors)

if !noBanner {
if !c.noBanner {
fmt.Printf("%s version: %s\n%s", banner, appVersion, slogan)
}

if dryRun && apply {
if c.dryRun && c.apply {
log.Fatal("--apply and --dry-run can't be used together.")
}

if destroy && apply {
if c.destroy && c.apply {
log.Fatal("--destroy and --apply can't be used together.")
}

if len(target) > 0 && len(group) > 0 {
if len(c.target) > 0 && len(c.group) > 0 {
log.Fatal("--target and --group can't be used together.")
}

if (settings.EyamlPrivateKeyPath != "" && settings.EyamlPublicKeyPath == "") || (settings.EyamlPrivateKeyPath == "" && settings.EyamlPublicKeyPath != "") {
log.Fatal("both EyamlPrivateKeyPath and EyamlPublicKeyPath are required")
}

helmVersion = strings.TrimSpace(getHelmVersion())
helmVersion := strings.TrimSpace(getHelmVersion())
extractedHelmVersion := helmVersion
if !strings.HasPrefix(helmVersion, "v") {
extractedHelmVersion = strings.TrimSpace(strings.Split(helmVersion, ":")[1])
Expand All @@ -108,32 +148,48 @@ func Cli() {
log.Fatal("this version of Helmsman does not work with helm releases older than 3.0.0")
}

kubectlVersion = strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1])
kubectlVersion := strings.TrimSpace(strings.SplitN(getKubectlClientVersion(), ": ", 2)[1])
log.Verbose("kubectl client version: " + kubectlVersion)

if len(files) == 0 {
if len(c.files) == 0 {
log.Info("No desired state files provided.")
os.Exit(0)
}

if kubeconfig != "" {
os.Setenv("KUBECONFIG", kubeconfig)
if c.kubeconfig != "" {
os.Setenv("KUBECONFIG", c.kubeconfig)
}

if !toolExists("kubectl", debug) {
if !toolExists("kubectl") {
log.Fatal("kubectl is not installed/configured correctly. Aborting!")
}

if !toolExists(helmBin, debug) {
if !toolExists(helmBin) {
log.Fatal("" + helmBin + " is not installed/configured correctly. Aborting!")
}

if !helmPluginExists("diff") {
log.Fatal("helm diff plugin is not installed/configured correctly. Aborting!")
}

if !c.noEnvSubst {
log.Verbose("Substitution of env variables enabled")
if !c.noEnvValuesSubst {
log.Verbose("Substitution of env variables in values enabled")
}
}
if !c.noSSMSubst {
log.Verbose("Substitution of SSM variables enabled")
if !c.noSSMValuesSubst {
log.Verbose("Substitution of SSM variables in values enabled")
}
}
}

// readState gets the desired state from files
func (c *cli) readState(s *state) {
// read the env file
if len(envFiles) == 0 {
if len(c.envFiles) == 0 {
if _, err := os.Stat(".env"); err == nil {
err = godotenv.Load()
if err != nil {
Expand All @@ -142,7 +198,7 @@ func Cli() {
}
}

for _, e := range envFiles {
for _, e := range c.envFiles {
err := godotenv.Load(e)
if err != nil {
log.Fatal("Error loading " + e + " env file")
Expand All @@ -154,20 +210,8 @@ func Cli() {
_ = os.MkdirAll(tempFilesDir, 0755)

// read the TOML/YAML desired state file
if !noEnvSubst {
log.Verbose("Substitution of env variables enabled")
if !noEnvValuesSubst {
log.Verbose("Substitution of env variables in values enabled")
}
}
if !noSSMSubst {
log.Verbose("Substitution of SSM variables enabled")
if !noSSMValuesSubst {
log.Verbose("Substitution of SSM variables in values enabled")
}
}
var fileState state
for _, f := range files {
for _, f := range c.files {

result, msg := fileState.fromFile(f)
if result {
Expand All @@ -191,18 +235,33 @@ func Cli() {
// All the apps are already merged, make fileState.Apps empty to avoid conflicts in the final merge
fileState.Apps = make(map[string]*release)

if err := mergo.Merge(&s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
if err := mergo.Merge(s, &fileState, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
log.Fatal("Failed to merge desired state file" + f)
}
}

if debug {
if len(c.target) > 0 {
s.TargetMap = map[string]bool{}
for _, v := range c.target {
s.TargetMap[v] = true
}
}

if len(c.group) > 0 {
s.GroupMap = map[string]bool{}
for _, v := range c.group {
s.GroupMap[v] = true
}
}

if c.debug {
s.print()
}

if !skipValidation {
if !c.skipValidation {
// validate the desired state content
if len(files) > 0 {
if len(c.files) > 0 {
log.Info("Validating desired state definition...")
if err := s.validate(); err != nil { // syntax validation
log.Fatal(err.Error())
}
Expand All @@ -213,36 +272,20 @@ func Cli() {

if s.Settings.StorageBackend != "" {
os.Setenv("HELM_DRIVER", s.Settings.StorageBackend)
}

// set default storage background to secret if not set by user
if s.Settings.StorageBackend == "" {
} else {
// set default storage background to secret if not set by user
s.Settings.StorageBackend = "secret"
}

// if there is no user-defined context name in the DSF(s), use the default context name
if s.Context == "" {
s.Context = defaultContextName
}

if len(target) > 0 {
targetMap = map[string]bool{}
for _, v := range target {
targetMap[v] = true
}
}

if len(group) > 0 {
groupMap = map[string]bool{}
for _, v := range group {
groupMap[v] = true
}
}
}

// getDryRunFlags returns dry-run flag
func getDryRunFlags() []string {
if dryRun {
func (c *cli) getDryRunFlags() []string {
if c.dryRun {
return []string{"--dry-run", "--debug"}
}
return []string{}
Expand Down
Loading

0 comments on commit a5c49ed

Please sign in to comment.