Skip to content

Commit

Permalink
Add atmos describe stacks command (#133)
Browse files Browse the repository at this point in the history
* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command

* Add `describe stacks` command
  • Loading branch information
aknysh committed Apr 12, 2022
1 parent b4786a3 commit 8a36575
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 57 deletions.
33 changes: 33 additions & 0 deletions cmd/describe_stacks.go
@@ -0,0 +1,33 @@
package cmd

import (
e "github.com/cloudposse/atmos/internal/exec"
u "github.com/cloudposse/atmos/pkg/utils"
"github.com/spf13/cobra"
)

// describeComponentCmd describes configuration for components
var describeStacksCmd = &cobra.Command{
Use: "stacks",
Short: "Execute 'describe stacks' command",
Long: `This command shows configuration for stacks and components in the stacks: atmos describe stacks <options>`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true},
Run: func(cmd *cobra.Command, args []string) {
err := e.ExecuteDescribeStacks(cmd, args)
if err != nil {
u.PrintErrorToStdErrorAndExit(err)
}
},
}

func init() {
describeStacksCmd.DisableFlagParsing = false
describeStacksCmd.PersistentFlags().String("file", "", "Write the result to file: atmos describe stacks --file=stacks.yaml")
describeStacksCmd.PersistentFlags().String("format", "yaml", "Specify output format: atmos describe stacks --format=yaml/json ('yaml' is default)")
describeStacksCmd.PersistentFlags().StringP("stack", "s", "", "Filter by a specific stack: atmos describe stacks -s <stack>")
describeStacksCmd.PersistentFlags().String("components", "", "Filter by specific components: atmos describe stacks --components=<component1>,<component2>")
describeStacksCmd.PersistentFlags().String("component-types", "", "Filter by specific component types: atmos describe stacks --component-types=terraform,helmfile. Supported component types: terraform, helmfile")
describeStacksCmd.PersistentFlags().String("sections", "", "Output only these component sections: atmos describe stacks --sections=vars,settings. Available component sections: backend, backend_type, deps, env, inheritance, metadata, remote_state_backend, remote_state_backend_type, settings, vars")

describeCmd.AddCommand(describeStacksCmd)
}
4 changes: 2 additions & 2 deletions internal/exec/describe_component.go
Expand Up @@ -30,10 +30,10 @@ func ExecuteDescribeComponent(cmd *cobra.Command, args []string) error {
configAndStacksInfo.Stack = stack

configAndStacksInfo.ComponentType = "terraform"
configAndStacksInfo, err = ProcessStacks(configAndStacksInfo)
configAndStacksInfo, err = ProcessStacks(configAndStacksInfo, true)
if err != nil {
configAndStacksInfo.ComponentType = "helmfile"
configAndStacksInfo, err = ProcessStacks(configAndStacksInfo)
configAndStacksInfo, err = ProcessStacks(configAndStacksInfo, true)
if err != nil {
return err
}
Expand Down
160 changes: 160 additions & 0 deletions internal/exec/describe_stacks.go
@@ -0,0 +1,160 @@
package exec

import (
"errors"
"fmt"
c "github.com/cloudposse/atmos/pkg/config"
u "github.com/cloudposse/atmos/pkg/utils"
"github.com/spf13/cobra"
"strings"
)

// ExecuteDescribeStacks executes `describe stacks` command
func ExecuteDescribeStacks(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()

filterByStack, err := flags.GetString("stack")
if err != nil {
return err
}

format, err := flags.GetString("format")
if err != nil {
return err
}
if format != "" && format != "yaml" && format != "json" {
return errors.New(fmt.Sprintf("Invalid '--format' flag '%s'. Valid values are 'yaml' (default) and 'json'", format))
}
if format == "" {
format = "yaml"
}

file, err := flags.GetString("file")
if err != nil {
return err
}

componentsCsv, err := flags.GetString("components")
if err != nil {
return err
}
var components []string
if componentsCsv != "" {
components = strings.Split(componentsCsv, ",")
}

componentTypesCsv, err := flags.GetString("component-types")
if err != nil {
return err
}
var componentTypes []string
if componentTypesCsv != "" {
componentTypes = strings.Split(componentTypesCsv, ",")
}

sectionsCsv, err := flags.GetString("sections")
if err != nil {
return err
}
var sections []string
if sectionsCsv != "" {
sections = strings.Split(sectionsCsv, ",")
}

var configAndStacksInfo c.ConfigAndStacksInfo
configAndStacksInfo.Stack = filterByStack
stacksMap, err := FindStacksMap(configAndStacksInfo, filterByStack != "")
if err != nil {
return err
}

finalStacksMap := make(map[string]interface{})

for stackName, stackSection := range stacksMap {
if filterByStack == "" || filterByStack == stackName {
// Delete the stack-wide imports
delete(stackSection.(map[interface{}]interface{}), "imports")

if !u.MapKeyExists(finalStacksMap, stackName) {
finalStacksMap[stackName] = make(map[string]interface{})
}

if componentsSection, ok := stackSection.(map[interface{}]interface{})["components"].(map[string]interface{}); ok {
if len(componentTypes) == 0 || u.SliceContainsString(componentTypes, "terraform") {
if terraformSection, ok2 := componentsSection["terraform"].(map[string]interface{}); ok2 {
for compName, comp := range terraformSection {
if len(components) == 0 || u.SliceContainsString(components, compName) {
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{}), "components") {
finalStacksMap[stackName].(map[string]interface{})["components"] = make(map[string]interface{})
}
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{}), "terraform") {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["terraform"] = make(map[string]interface{})
}
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["terraform"].(map[string]interface{}), compName) {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["terraform"].(map[string]interface{})[compName] = make(map[string]interface{})
}

for sectionName, section := range comp.(map[string]interface{}) {
if len(sections) == 0 || u.SliceContainsString(sections, sectionName) {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["terraform"].(map[string]interface{})[compName].(map[string]interface{})[sectionName] = section
}
}
}
}
}
}
if len(componentTypes) == 0 || u.SliceContainsString(componentTypes, "helmfile") {
if helmfileSection, ok3 := componentsSection["helmfile"].(map[string]interface{}); ok3 {
for compName, comp := range helmfileSection {
if len(components) == 0 || u.SliceContainsString(components, compName) {
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{}), "components") {
finalStacksMap[stackName].(map[string]interface{})["components"] = make(map[string]interface{})
}
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{}), "helmfile") {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["helmfile"] = make(map[string]interface{})
}
if !u.MapKeyExists(finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["helmfile"].(map[string]interface{}), compName) {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["helmfile"].(map[string]interface{})[compName] = make(map[string]interface{})
}

for sectionName, section := range comp.(map[string]interface{}) {
if len(sections) == 0 || u.SliceContainsString(sections, sectionName) {
finalStacksMap[stackName].(map[string]interface{})["components"].(map[string]interface{})["helmfile"].(map[string]interface{})[compName].(map[string]interface{})[sectionName] = section
}
}
}
}
}
}
}
}
}

if format == "yaml" {
if file == "" {
err = u.PrintAsYAML(finalStacksMap)
if err != nil {
return err
}
} else {
err = u.WriteToFileAsYAML(file, finalStacksMap, 0644)
if err != nil {
return err
}
}
} else if format == "json" {
if file == "" {
err = u.PrintAsJSON(finalStacksMap)
if err != nil {
return err
}
} else {
err = u.WriteToFileAsJSON(file, finalStacksMap, 0644)
if err != nil {
return err
}
}
}

return nil
}
2 changes: 1 addition & 1 deletion internal/exec/helmfile_generate_varfile.go
Expand Up @@ -29,7 +29,7 @@ func ExecuteHelmfileGenerateVarfile(cmd *cobra.Command, args []string) error {
info.Stack = stack
info.ComponentType = "helmfile"

info, err = ProcessStacks(info)
info, err = ProcessStacks(info, true)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/terraform_generate_backend.go
Expand Up @@ -30,7 +30,7 @@ func ExecuteTerraformGenerateBackend(cmd *cobra.Command, args []string) error {
info.Stack = stack
info.ComponentType = "terraform"

info, err = ProcessStacks(info)
info, err = ProcessStacks(info, true)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/terraform_generate_varfile.go
Expand Up @@ -29,7 +29,7 @@ func ExecuteTerraformGenerateVarfile(cmd *cobra.Command, args []string) error {
info.Stack = stack
info.ComponentType = "terraform"

info, err = ProcessStacks(info)
info, err = ProcessStacks(info, true)
if err != nil {
return err
}
Expand Down
50 changes: 30 additions & 20 deletions internal/exec/utils.go
Expand Up @@ -192,34 +192,20 @@ func processArgsConfigAndStacks(componentType string, cmd *cobra.Command, args [
return configAndStacksInfo, err
}

return ProcessStacks(configAndStacksInfo)
return ProcessStacks(configAndStacksInfo, true)
}

// ProcessStacks processes stack config
func ProcessStacks(configAndStacksInfo c.ConfigAndStacksInfo) (c.ConfigAndStacksInfo, error) {
// Check if stack was provided
if len(configAndStacksInfo.Stack) < 1 {
message := fmt.Sprintf("'stack' is required. Usage: atmos %s <command> <component> -s <stack>", configAndStacksInfo.ComponentType)
return configAndStacksInfo, errors.New(message)
}

// Check if component was provided
if len(configAndStacksInfo.ComponentFromArg) < 1 {
message := fmt.Sprintf("'component' is required. Usage: atmos %s <command> <component> <arguments_and_flags>", configAndStacksInfo.ComponentType)
return configAndStacksInfo, errors.New(message)
}

configAndStacksInfo.StackFromArg = configAndStacksInfo.Stack

// FindStacksMap processes stack config and returns a map of all stacks
func FindStacksMap(configAndStacksInfo c.ConfigAndStacksInfo, checkStack bool) (map[string]interface{}, error) {
// Process and merge CLI configurations
err := c.InitConfig()
if err != nil {
return configAndStacksInfo, err
return nil, err
}

err = c.ProcessConfig(configAndStacksInfo)
err = c.ProcessConfig(configAndStacksInfo, checkStack)
if err != nil {
return configAndStacksInfo, err
return nil, err
}

// Process stack config file(s)
Expand All @@ -229,6 +215,30 @@ func ProcessStacks(configAndStacksInfo c.ConfigAndStacksInfo) (c.ConfigAndStacks
false,
true)

if err != nil {
return nil, err
}

return stacksMap, nil
}

// ProcessStacks processes stack config
func ProcessStacks(configAndStacksInfo c.ConfigAndStacksInfo, checkStack bool) (c.ConfigAndStacksInfo, error) {
// Check if stack was provided
if checkStack && len(configAndStacksInfo.Stack) < 1 {
message := fmt.Sprintf("'stack' is required. Usage: atmos %s <command> <component> -s <stack>", configAndStacksInfo.ComponentType)
return configAndStacksInfo, errors.New(message)
}

// Check if component was provided
if len(configAndStacksInfo.ComponentFromArg) < 1 {
message := fmt.Sprintf("'component' is required. Usage: atmos %s <command> <component> <arguments_and_flags>", configAndStacksInfo.ComponentType)
return configAndStacksInfo, errors.New(message)
}

configAndStacksInfo.StackFromArg = configAndStacksInfo.Stack

stacksMap, err := FindStacksMap(configAndStacksInfo, checkStack)
if err != nil {
return configAndStacksInfo, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/component/component_processor.go
Expand Up @@ -15,10 +15,10 @@ func ProcessComponentInStack(component string, stack string) (map[string]interfa
configAndStacksInfo.Stack = stack

configAndStacksInfo.ComponentType = "terraform"
configAndStacksInfo, err := e.ProcessStacks(configAndStacksInfo)
configAndStacksInfo, err := e.ProcessStacks(configAndStacksInfo, true)
if err != nil {
configAndStacksInfo.ComponentType = "helmfile"
configAndStacksInfo, err = e.ProcessStacks(configAndStacksInfo)
configAndStacksInfo, err = e.ProcessStacks(configAndStacksInfo, true)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 8a36575

Please sign in to comment.