Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve terraform import. Add help for all atmos commands #94

Merged
merged 15 commits into from Dec 21, 2021
2 changes: 1 addition & 1 deletion LICENSE
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion cmd/describe.go
Expand Up @@ -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},
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/describe_component.go
Expand Up @@ -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 <component> -s <stack>`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true},
Run: func(cmd *cobra.Command, args []string) {
err := e.ExecuteDescribeComponent(cmd, args)
Expand All @@ -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 <component> -s <stack>")

err := describeComponentCmd.MarkPersistentFlagRequired("stack")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/describe_config.go
Expand Up @@ -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)
Expand Down
13 changes: 3 additions & 10 deletions cmd/helmfile.go
Expand Up @@ -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)
Expand All @@ -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 <helmfile_command> <component> -s <stack>")
RootCmd.AddCommand(helmfileCmd)
}
2 changes: 1 addition & 1 deletion cmd/root.go
Expand Up @@ -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.
Expand Down
13 changes: 3 additions & 10 deletions cmd/terraform.go
Expand Up @@ -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)
Expand All @@ -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 <terraform_command> <component> -s <stack>")
RootCmd.AddCommand(terraformCmd)
}
2 changes: 1 addition & 1 deletion cmd/terraform_generate.go
Expand Up @@ -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},
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/terraform_generate_backend.go
Expand Up @@ -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 <component> -s <stack>`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Run: func(cmd *cobra.Command, args []string) {
err := e.ExecuteTerraformGenerateBackend(cmd, args)
Expand All @@ -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 <component> -s <stack>")

err := terraformGenerateBackendCmd.MarkPersistentFlagRequired("stack")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform_generate_backends.go
Expand Up @@ -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) {
Expand All @@ -32,5 +32,5 @@ func init() {
os.Exit(1)
}

terraformGenerateCmd.AddCommand(terraformGenerateBackendsCmd)
// terraformGenerateCmd.AddCommand(terraformGenerateBackendsCmd)
}
2 changes: 1 addition & 1 deletion cmd/version.go
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions internal/exec/helmfile.go
Expand Up @@ -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")
}
Expand Down
61 changes: 61 additions & 0 deletions 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 <command> <component> -s <stack> [options]", componentType))
color.Cyan(fmt.Sprintf("atmos %s <command> <component> --stack <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=<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 <command> <component> -s <stack> [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 <component> -s <stack> [options]", componentType, command))
color.Cyan(fmt.Sprintf("atmos %s %s <component> --stack <stack> [options]", componentType, command))

err := execCommand(componentType, []string{command, "--help"}, "", nil)
if err != nil {
return err
}
}

fmt.Println()
return nil
}
11 changes: 11 additions & 0 deletions internal/exec/terraform.go
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)
Expand Down
69 changes: 52 additions & 17 deletions internal/exec/utils.go
Expand Up @@ -28,6 +28,8 @@ var (
g.DeployRunInitFlag,
g.AutoGenerateBackendFileFlag,
g.FromPlanFlag,
g.HelpFlag1,
g.HelpFlag2,
}
)

Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if argsAndFlagsInfo.NeedHelp == true {
if argsAndFlagsInfo.NeedHelp {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, nice find.
it's opinionated since Go is a very explicit language

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 <command> <component> -s <stack>", 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 <command> <component> <arguments_and_flags>", componentType)
return configAndStacksInfo, errors.New(message)
}

// Process and merge CLI configurations
Expand Down Expand Up @@ -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)
Expand All @@ -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 <command> <component> <arguments_and_flags>"
return info, errors.New(message)
}

return info, nil
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/schema.go
Expand Up @@ -68,6 +68,7 @@ type ArgsAndFlagsInfo struct {
DeployRunInit string
AutoGenerateBackendFile string
UseTerraformPlan bool
NeedHelp bool
}

type ConfigAndStacksInfo struct {
Expand Down Expand Up @@ -97,4 +98,5 @@ type ConfigAndStacksInfo struct {
AutoGenerateBackendFile string
UseTerraformPlan bool
ComponentInheritanceChain []string
NeedHelp bool
}
3 changes: 3 additions & 0 deletions pkg/globals/globals.go
Expand Up @@ -18,6 +18,9 @@ const (
AutoGenerateBackendFileFlag = "--auto-generate-backend-file"

FromPlanFlag = "--from-plan"

HelpFlag1 = "-h"
HelpFlag2 = "--help"
)

var (
Expand Down