diff --git a/LICENSE b/LICENSE index d89fe75a8..04155f463 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2020-2021 Cloud Posse, LLC + Copyright 2020-2022 Cloud Posse, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cmd/describe.go b/cmd/describe.go index 633decbfe..1a7a9783c 100644 --- a/cmd/describe.go +++ b/cmd/describe.go @@ -7,7 +7,7 @@ import ( // describeCmd describes configuration for stacks and components var describeCmd = &cobra.Command{ Use: "describe", - Short: "describe", + Short: "Execute 'describe' commands", Long: `This command shows configuration for CLI, stacks and components`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, } diff --git a/cmd/describe_component.go b/cmd/describe_component.go index ae2b0c353..32b543cf5 100644 --- a/cmd/describe_component.go +++ b/cmd/describe_component.go @@ -10,8 +10,8 @@ import ( // describeComponentCmd describes configuration for components var describeComponentCmd = &cobra.Command{ Use: "component", - Short: "describe component", - Long: `This command shows configuration for components`, + Short: "Execute 'describe component' command", + Long: `This command shows configuration for a component in a stack: atmos describe component -s `, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteDescribeComponent(cmd, args) @@ -24,7 +24,7 @@ var describeComponentCmd = &cobra.Command{ func init() { describeComponentCmd.DisableFlagParsing = false - describeComponentCmd.PersistentFlags().StringP("stack", "s", "", "") + describeComponentCmd.PersistentFlags().StringP("stack", "s", "", "atmos describe component -s ") err := describeComponentCmd.MarkPersistentFlagRequired("stack") if err != nil { diff --git a/cmd/describe_config.go b/cmd/describe_config.go index 30738f27e..bfc89db39 100644 --- a/cmd/describe_config.go +++ b/cmd/describe_config.go @@ -11,8 +11,8 @@ import ( // describeComponentCmd describes configuration for components var describeConfigCmd = &cobra.Command{ Use: "config", - Short: "describe config", - Long: `This command shows CLI configuration`, + Short: "Execute 'describe config' command", + Long: `This command shows the final (deep-merged) CLI configuration: atmos describe config`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteDescribeConfig(cmd, args) diff --git a/cmd/helmfile.go b/cmd/helmfile.go index f32ceda1b..55dc2d56a 100644 --- a/cmd/helmfile.go +++ b/cmd/helmfile.go @@ -10,8 +10,8 @@ import ( // terraformCmd represents the base command for all terraform sub-commands var helmfileCmd = &cobra.Command{ Use: "helmfile", - Short: "helmfile command", - Long: `This command runs helmfile sub-commands`, + Short: "Execute 'helmfile' commands", + Long: `This command runs helmfile commands`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteHelmfile(cmd, args) @@ -25,13 +25,6 @@ var helmfileCmd = &cobra.Command{ func init() { // https://github.com/spf13/cobra/issues/739 helmfileCmd.DisableFlagParsing = true - helmfileCmd.PersistentFlags().StringP("stack", "s", "", "") - - err := helmfileCmd.MarkPersistentFlagRequired("stack") - if err != nil { - color.Red("%s\n\n", err) - os.Exit(1) - } - + helmfileCmd.PersistentFlags().StringP("stack", "s", "", "atmos helmfile -s ") RootCmd.AddCommand(helmfileCmd) } diff --git a/cmd/root.go b/cmd/root.go index c1070bc1a..c0c1ca340 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,7 @@ import ( var RootCmd = &cobra.Command{ Use: "atmos", Short: "Universal Tool for DevOps and Cloud Automation", - Long: `'atmos'' is universal tool for DevOps and cloud automation used for provisioning, managing and orchestrating workflows across various toolchains`, + Long: `'atmos'' is a universal tool for DevOps and cloud automation used for provisioning, managing and orchestrating workflows across various toolchains`, } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cmd/terraform.go b/cmd/terraform.go index 348aeb452..09c790424 100644 --- a/cmd/terraform.go +++ b/cmd/terraform.go @@ -10,8 +10,8 @@ import ( // terraformCmd represents the base command for all terraform sub-commands var terraformCmd = &cobra.Command{ Use: "terraform", - Short: "terraform command", - Long: `This command runs terraform sub-commands`, + Short: "Execute 'terraform' commands", + Long: `This command runs terraform commands`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteTerraform(cmd, args) @@ -25,13 +25,6 @@ var terraformCmd = &cobra.Command{ func init() { // https://github.com/spf13/cobra/issues/739 terraformCmd.DisableFlagParsing = true - terraformCmd.PersistentFlags().StringP("stack", "s", "", "") - - err := terraformCmd.MarkPersistentFlagRequired("stack") - if err != nil { - color.Red("%s\n\n", err) - os.Exit(1) - } - + terraformCmd.PersistentFlags().StringP("stack", "s", "", "atmos terraform -s ") RootCmd.AddCommand(terraformCmd) } diff --git a/cmd/terraform_generate.go b/cmd/terraform_generate.go index 1499aff05..815d462b4 100644 --- a/cmd/terraform_generate.go +++ b/cmd/terraform_generate.go @@ -7,7 +7,7 @@ import ( // terraformGenerateCmd generates backends and variables for terraform components var terraformGenerateCmd = &cobra.Command{ Use: "generate", - Short: "generate", + Short: "Execute 'terraform generate' commands", Long: "This command generates backend configs and variable configs for terraform components", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, } diff --git a/cmd/terraform_generate_backend.go b/cmd/terraform_generate_backend.go index 99eb25347..2af501e68 100644 --- a/cmd/terraform_generate_backend.go +++ b/cmd/terraform_generate_backend.go @@ -10,8 +10,8 @@ import ( // terraformGenerateBackendCmd generates backend config for a terraform components var terraformGenerateBackendCmd = &cobra.Command{ Use: "backend", - Short: "generate backend", - Long: `This command generates the backend config for a terraform component`, + Short: "Execute 'terraform generate backend' command", + Long: `This command generates the backend config for a terraform component: atmos terraform generate backend -s `, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteTerraformGenerateBackend(cmd, args) @@ -24,7 +24,7 @@ var terraformGenerateBackendCmd = &cobra.Command{ func init() { terraformGenerateBackendCmd.DisableFlagParsing = false - terraformGenerateBackendCmd.PersistentFlags().StringP("stack", "s", "", "") + terraformGenerateBackendCmd.PersistentFlags().StringP("stack", "s", "", "atmos terraform generate backend -s ") err := terraformGenerateBackendCmd.MarkPersistentFlagRequired("stack") if err != nil { diff --git a/cmd/terraform_generate_backends.go b/cmd/terraform_generate_backends.go index 02f58b980..448a0d582 100644 --- a/cmd/terraform_generate_backends.go +++ b/cmd/terraform_generate_backends.go @@ -10,7 +10,7 @@ import ( // terraformGenerateBackendsCmd generates backend configs for all terraform components var terraformGenerateBackendsCmd = &cobra.Command{ Use: "backends", - Short: "generate backends", + Short: "Execute 'terraform generate backends' command", Long: `This command generates the backend configs for all terraform components`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { @@ -32,5 +32,5 @@ func init() { os.Exit(1) } - terraformGenerateCmd.AddCommand(terraformGenerateBackendsCmd) + // terraformGenerateCmd.AddCommand(terraformGenerateBackendsCmd) } diff --git a/cmd/version.go b/cmd/version.go index 34025482f..f0114b918 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -9,7 +9,7 @@ var Version = "0.0.1" var versionCmd = &cobra.Command{ Use: "version", - Short: "version command", + Short: "Print the CLI version", Long: `This command prints the CLI version`, Run: func(cmd *cobra.Command, args []string) { fmt.Println(Version) diff --git a/internal/exec/helmfile.go b/internal/exec/helmfile.go index cab0b0274..4d960afad 100644 --- a/internal/exec/helmfile.go +++ b/internal/exec/helmfile.go @@ -20,6 +20,10 @@ func ExecuteHelmfile(cmd *cobra.Command, args []string) error { return err } + if info.NeedHelp == true { + return nil + } + if len(info.Stack) < 1 { return errors.New("stack must be specified") } diff --git a/internal/exec/help.go b/internal/exec/help.go new file mode 100644 index 000000000..565d09d78 --- /dev/null +++ b/internal/exec/help.go @@ -0,0 +1,61 @@ +package exec + +import ( + "fmt" + "github.com/fatih/color" +) + +// processHelp processes help commands +func processHelp(componentType string, command string) error { + if len(command) == 0 { + fmt.Println(fmt.Sprintf("'atmos' supports all native '%s' commands.", componentType)) + fmt.Println(fmt.Sprintf("In addition, 'component' and 'stack' are required in order to generate variables for the component in the stack.")) + color.Cyan(fmt.Sprintf("atmos %s -s [options]", componentType)) + color.Cyan(fmt.Sprintf("atmos %s --stack [options]", componentType)) + + if componentType == "terraform" { + fmt.Println() + color.Cyan("Differences from native terraform:") + fmt.Println(" - before executing other 'terraform' commands, 'atmos' calls 'terraform init'") + fmt.Println(" - 'atmos' supports 'terraform deploy' command which calls 'terraform plan' and then 'terraform apply'") + fmt.Println(" - 'terraform deploy' command supports '--deploy-run-init=true/false' flag to enable/disable running 'terraform init' " + + "before executing the command") + fmt.Println(" - 'terraform deploy' command sets '-auto-approve' flag before running 'terraform apply'") + fmt.Println(" - 'terraform apply' and 'terraform deploy' commands support '--from-plan' flag. If the flag is specified, the commands " + + "will use the previously generated 'planfile' instead of generating a new 'varfile'") + fmt.Println(" - 'atmos' supports 'terraform clean' command which deletes the '.terraform' folder, '.terraform.lock.hcl' lock file, " + + "and the previously generated 'planfile' and 'varfile' for the specified component and stack") + fmt.Println(" - 'atmos terraform workspace' command first calls 'terraform init -reconfigure', then 'terraform workspace select', " + + "and if the workspace was not created before, it then calls 'terraform workspace new'") + fmt.Println(" - 'atmos terraform import' looks for 'region' in the variables for the specified component and stack, and if it finds it, " + + "sets 'AWS_REGION=' ENV var before executing the command") + } + + if componentType == "helmfile" { + fmt.Println() + color.Cyan("Differences from native helmfile:") + fmt.Println(" - 'atmos helmfile' commands support '[global options]' in the command-line argument '--global-options'. " + + "Usage: atmos helmfile -s [command options] [arguments...] --global-options=\"--no-color --namespace=test\"") + fmt.Println(" - before executing the 'helmfile' commands, 'atmos' calls 'aws eks update-kubeconfig' to read kubeconfig from the EKS cluster " + + "and use it to authenticate with the cluster") + } + + err := execCommand(componentType, []string{"--help"}, "", nil) + if err != nil { + return err + } + } else { + fmt.Println(fmt.Sprintf("'atmos' supports native '%s %s' command with all the options, arguments and flags.", componentType, command)) + fmt.Println(fmt.Sprintf("In addition, 'component' and 'stack' are required in order to generate variables for the component in the stack.")) + color.Cyan(fmt.Sprintf("atmos %s %s -s [options]", componentType, command)) + color.Cyan(fmt.Sprintf("atmos %s %s --stack [options]", componentType, command)) + + err := execCommand(componentType, []string{command, "--help"}, "", nil) + if err != nil { + return err + } + } + + fmt.Println() + return nil +} diff --git a/internal/exec/terraform.go b/internal/exec/terraform.go index 2ab33fe00..70075df67 100644 --- a/internal/exec/terraform.go +++ b/internal/exec/terraform.go @@ -23,6 +23,10 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error { return err } + if info.NeedHelp == true { + return nil + } + if len(info.Stack) < 1 { return errors.New("stack must be specified") } @@ -277,6 +281,13 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error { } } + // Check `region` for `terraform import` + if info.SubCommand == "import" { + if region, regionExist := info.ComponentVarsSection["region"].(string); regionExist { + info.ComponentEnvList = append(info.ComponentEnvList, fmt.Sprintf("AWS_REGION=%s", region)) + } + } + // Execute the command if info.SubCommand != "workspace" { err = execCommand(info.Command, allArgsAndFlags, componentPath, info.ComponentEnvList) diff --git a/internal/exec/utils.go b/internal/exec/utils.go index 5735bbcf6..ed0456264 100644 --- a/internal/exec/utils.go +++ b/internal/exec/utils.go @@ -28,6 +28,8 @@ var ( g.DeployRunInitFlag, g.AutoGenerateBackendFileFlag, g.FromPlanFlag, + g.HelpFlag1, + g.HelpFlag2, } ) @@ -129,12 +131,6 @@ func processConfigAndStacks(componentType string, cmd *cobra.Command, args []str if err != nil { return configAndStacksInfo, err } - flags := cmd.Flags() - - configAndStacksInfo.Stack, err = flags.GetString("stack") - if err != nil { - return configAndStacksInfo, err - } argsAndFlagsInfo, err := processArgsAndFlags(args) if err != nil { @@ -152,10 +148,33 @@ func processConfigAndStacks(componentType string, cmd *cobra.Command, args []str configAndStacksInfo.DeployRunInit = argsAndFlagsInfo.DeployRunInit configAndStacksInfo.AutoGenerateBackendFile = argsAndFlagsInfo.AutoGenerateBackendFile configAndStacksInfo.UseTerraformPlan = argsAndFlagsInfo.UseTerraformPlan + configAndStacksInfo.NeedHelp = argsAndFlagsInfo.NeedHelp + + // Check if `-h` or `--help` flags are specified + if argsAndFlagsInfo.NeedHelp == true { + err = processHelp(componentType, argsAndFlagsInfo.SubCommand) + if err != nil { + return configAndStacksInfo, err + } + return configAndStacksInfo, nil + } + + flags := cmd.Flags() + configAndStacksInfo.Stack, err = flags.GetString("stack") + if err != nil { + return configAndStacksInfo, err + } + + // Check if stack was provided + if len(configAndStacksInfo.Stack) < 1 { + message := fmt.Sprintf("'stack' is required. Usage: atmos %s -s ", componentType) + return configAndStacksInfo, errors.New(message) + } // Check if component was provided if len(configAndStacksInfo.ComponentFromArg) < 1 { - return configAndStacksInfo, errors.New("'component' is required") + message := fmt.Sprintf("'component' is required. Usage: atmos %s ", componentType) + return configAndStacksInfo, errors.New(message) } // Process and merge CLI configurations @@ -454,6 +473,10 @@ func processArgsAndFlags(inputArgsAndFlags []string) (c.ArgsAndFlagsInfo, error) info.UseTerraformPlan = true } + if arg == g.HelpFlag1 || arg == g.HelpFlag2 { + info.NeedHelp = true + } + for _, f := range commonFlags { if arg == f { indexesToRemove = append(indexesToRemove, i) @@ -479,18 +502,30 @@ func processArgsAndFlags(inputArgsAndFlags []string) (c.ArgsAndFlagsInfo, error) } } - // Handle the legacy command `terraform write varfile` - if additionalArgsAndFlags[0] == "write" && additionalArgsAndFlags[1] == "varfile" { - info.SubCommand = "write varfile" - info.ComponentFromArg = additionalArgsAndFlags[2] - info.AdditionalArgsAndFlags = additionalArgsAndFlags[3:] - } else { - info.SubCommand = additionalArgsAndFlags[0] - info.ComponentFromArg = additionalArgsAndFlags[1] - info.AdditionalArgsAndFlags = additionalArgsAndFlags[2:] + info.GlobalOptions = globalOptions + + if info.NeedHelp == true { + if len(additionalArgsAndFlags) > 0 { + info.SubCommand = additionalArgsAndFlags[0] + } + return info, nil } - info.GlobalOptions = globalOptions + if len(additionalArgsAndFlags) > 1 { + // Handle the legacy command `terraform write varfile` + if additionalArgsAndFlags[0] == "write" && additionalArgsAndFlags[1] == "varfile" { + info.SubCommand = "write varfile" + info.ComponentFromArg = additionalArgsAndFlags[2] + info.AdditionalArgsAndFlags = additionalArgsAndFlags[3:] + } else { + info.SubCommand = additionalArgsAndFlags[0] + info.ComponentFromArg = additionalArgsAndFlags[1] + info.AdditionalArgsAndFlags = additionalArgsAndFlags[2:] + } + } else { + message := "invalid number of arguments. Usage: atmos " + return info, errors.New(message) + } return info, nil } diff --git a/pkg/config/schema.go b/pkg/config/schema.go index 8b629daed..a9c49213e 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -68,6 +68,7 @@ type ArgsAndFlagsInfo struct { DeployRunInit string AutoGenerateBackendFile string UseTerraformPlan bool + NeedHelp bool } type ConfigAndStacksInfo struct { @@ -97,4 +98,5 @@ type ConfigAndStacksInfo struct { AutoGenerateBackendFile string UseTerraformPlan bool ComponentInheritanceChain []string + NeedHelp bool } diff --git a/pkg/globals/globals.go b/pkg/globals/globals.go index 3f84c4168..f25791fc6 100644 --- a/pkg/globals/globals.go +++ b/pkg/globals/globals.go @@ -18,6 +18,9 @@ const ( AutoGenerateBackendFileFlag = "--auto-generate-backend-file" FromPlanFlag = "--from-plan" + + HelpFlag1 = "-h" + HelpFlag2 = "--help" ) var (