Skip to content

Commit

Permalink
add hooks and store write functionality (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcalhoun authored Jan 8, 2025
1 parent 23507ab commit 71aa80e
Show file tree
Hide file tree
Showing 22 changed files with 1,241 additions and 442 deletions.
84 changes: 84 additions & 0 deletions cmd/terraform.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package cmd

import (
"context"
"fmt"
"strings"

"github.com/samber/lo"
"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/internal/tui/templates"
cfg "github.com/cloudposse/atmos/pkg/config"
h "github.com/cloudposse/atmos/pkg/hooks"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
cc "github.com/ivanpirog/coloredcobra"
)

type contextKey string

const atmosInfoKey contextKey = "atmos_info"

// terraformCmd represents the base command for all terraform sub-commands
var terraformCmd = &cobra.Command{
Use: "terraform",
Expand All @@ -23,6 +31,82 @@ var terraformCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
return terraformRun(cmd, cmd, args)
},
PreRun: func(cmd *cobra.Command, args []string) {
var argsAfterDoubleDash []string
var finalArgs = args

doubleDashIndex := lo.IndexOf(args, "--")
if doubleDashIndex > 0 {
finalArgs = lo.Slice(args, 0, doubleDashIndex)
argsAfterDoubleDash = lo.Slice(args, doubleDashIndex+1, len(args))
}

info, err := e.ProcessCommandLineArgs("terraform", cmd, finalArgs, argsAfterDoubleDash)
if err != nil {
u.LogErrorAndExit(schema.AtmosConfiguration{}, err)
}

ctx := context.WithValue(context.Background(), contextKey(atmosInfoKey), info)
RootCmd.SetContext(ctx)

// Check Atmos configuration
checkAtmosConfig()
},
PostRunE: func(cmd *cobra.Command, args []string) error {
info, ok := RootCmd.Context().Value(atmosInfoKey).(schema.ConfigAndStacksInfo)
if !ok {
return fmt.Errorf("failed to retrieve atmos info from context")
}
atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false)
if err != nil {
u.LogErrorAndExit(atmosConfig, err)
}

sections, err := e.ExecuteDescribeComponent(info.ComponentFromArg, info.Stack, true)
if err != nil {
u.LogErrorAndExit(atmosConfig, err)
}

if info.SubCommand == "apply" || info.SubCommand == "deploy" {
hooks := h.Hooks{}
hooks, err = hooks.ConvertToHooks(sections["hooks"].(map[string]any))
if err != nil {
u.LogErrorAndExit(atmosConfig, fmt.Errorf("invalid hooks section %v", sections["hooks"]))
}

for _, hook := range hooks {
if strings.ToLower(hook.Command) == "store" {
if len(hook.Outputs) == 0 {
u.LogInfo(atmosConfig, fmt.Sprintf("skipping hook %q: no outputs configured", hook.Name))
continue
}
u.LogInfo(atmosConfig, fmt.Sprintf("\nexecuting 'after-terraform-apply' hook '%s' with command '%s'", hook.Name, hook.Command))
for key, value := range hook.Outputs {
var outputValue any
outputKey := strings.TrimPrefix(value, ".")

if strings.Index(value, ".") == 0 {
outputValue = e.GetTerraformOutput(&atmosConfig, info.Stack, info.ComponentFromArg, outputKey, true)
} else {
outputValue = value
}

store := atmosConfig.Stores[hook.Name]
if store == nil {
return fmt.Errorf("store %q not found in configuration", hook.Name)
}
u.LogInfo(atmosConfig, fmt.Sprintf(" storing terraform output '%s' in store '%s' with key '%s' and value %v", outputKey, hook.Name, key, outputValue))

err = store.Set(info.Stack, info.ComponentFromArg, key, outputValue)
if err != nil {
return err
}
}
}
}
}
return nil
},
}

// Contains checks if a slice of strings contains an exact match for the target string.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
github.com/stretchr/testify v1.10.0
github.com/zclconf/go-cty v1.16.0
golang.org/x/term v0.28.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.10.0
)
Expand Down
255 changes: 255 additions & 0 deletions go.sum

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions internal/exec/describe_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func ExecuteDescribeStacks(
var settingsSection map[string]any
var envSection map[string]any
var providersSection map[string]any
var hooksSection map[string]any
var overridesSection map[string]any
var backendSection map[string]any
var backendTypeSection string
Expand Down Expand Up @@ -226,6 +227,10 @@ func ExecuteDescribeStacks(
providersSection = map[string]any{}
}

if hooksSection, ok = componentSection[cfg.HooksSectionName].(map[string]any); !ok {
hooksSection = map[string]any{}
}

if overridesSection, ok = componentSection[cfg.OverridesSectionName].(map[string]any); !ok {
overridesSection = map[string]any{}
}
Expand All @@ -246,6 +251,7 @@ func ExecuteDescribeStacks(
ComponentSettingsSection: settingsSection,
ComponentEnvSection: envSection,
ComponentProvidersSection: providersSection,
ComponentHooksSection: hooksSection,
ComponentOverridesSection: overridesSection,
ComponentBackendSection: backendSection,
ComponentBackendType: backendTypeSection,
Expand All @@ -255,6 +261,7 @@ func ExecuteDescribeStacks(
cfg.SettingsSectionName: settingsSection,
cfg.EnvSectionName: envSection,
cfg.ProvidersSectionName: providersSection,
cfg.HooksSectionName: hooksSection,
cfg.OverridesSectionName: overridesSection,
cfg.BackendSectionName: backendSection,
cfg.BackendTypeSectionName: backendTypeSection,
Expand Down Expand Up @@ -419,6 +426,10 @@ func ExecuteDescribeStacks(
providersSection = map[string]any{}
}

if hooksSection, ok = componentSection[cfg.HooksSectionName].(map[string]any); !ok {
hooksSection = map[string]any{}
}

if overridesSection, ok = componentSection[cfg.OverridesSectionName].(map[string]any); !ok {
overridesSection = map[string]any{}
}
Expand All @@ -439,6 +450,7 @@ func ExecuteDescribeStacks(
ComponentSettingsSection: settingsSection,
ComponentEnvSection: envSection,
ComponentProvidersSection: providersSection,
ComponentHooksSection: hooksSection,
ComponentOverridesSection: overridesSection,
ComponentBackendSection: backendSection,
ComponentBackendType: backendTypeSection,
Expand All @@ -448,6 +460,7 @@ func ExecuteDescribeStacks(
cfg.SettingsSectionName: settingsSection,
cfg.EnvSectionName: envSection,
cfg.ProvidersSectionName: providersSection,
cfg.HooksSectionName: hooksSection,
cfg.OverridesSectionName: overridesSection,
cfg.BackendSectionName: backendSection,
cfg.BackendTypeSectionName: backendTypeSection,
Expand Down
53 changes: 53 additions & 0 deletions internal/exec/stack_processor_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ func ProcessStackConfig(
terraformEnv := map[string]any{}
terraformCommand := ""
terraformProviders := map[string]any{}
terraformHooks := map[string]any{}

helmfileVars := map[string]any{}
helmfileSettings := map[string]any{}
Expand Down Expand Up @@ -660,6 +661,13 @@ func ProcessStackConfig(
}
}

if i, ok := globalTerraformSection[cfg.HooksSectionName]; ok {
terraformHooks, ok = i.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid 'terraform.hooks' section in the file '%s'", stackName)
}
}

// Global backend
globalBackendType := ""
globalBackendSection := map[string]any{}
Expand Down Expand Up @@ -796,6 +804,14 @@ func ProcessStackConfig(
}
}

componentHooks := map[string]any{}
if i, ok := componentMap[cfg.HooksSectionName]; ok {
componentHooks, ok = i.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.hooks' section in the file '%s'", component, stackName)
}
}

// Component metadata.
// This is per component, not deep-merged and not inherited from base components and globals.
componentMetadata := map[string]any{}
Expand Down Expand Up @@ -856,6 +872,7 @@ func ProcessStackConfig(
componentOverridesSettings := map[string]any{}
componentOverridesEnv := map[string]any{}
componentOverridesProviders := map[string]any{}
componentOverridesHooks := map[string]any{}
componentOverridesTerraformCommand := ""

if i, ok := componentMap[cfg.OverridesSectionName]; ok {
Expand Down Expand Up @@ -892,6 +909,12 @@ func ProcessStackConfig(
return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.providers' in the manifest '%s'", component, stackName)
}
}

if i, ok = componentOverrides[cfg.HooksSectionName]; ok {
if componentOverridesHooks, ok = i.(map[string]any); !ok {
return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.hooks' in the manifest '%s'", component, stackName)
}
}
}

// Process base component(s)
Expand All @@ -900,6 +923,7 @@ func ProcessStackConfig(
baseComponentSettings := map[string]any{}
baseComponentEnv := map[string]any{}
baseComponentProviders := map[string]any{}
baseComponentHooks := map[string]any{}
baseComponentTerraformCommand := ""
baseComponentBackendType := ""
baseComponentBackendSection := map[string]any{}
Expand Down Expand Up @@ -936,6 +960,7 @@ func ProcessStackConfig(
baseComponentSettings = baseComponentConfig.BaseComponentSettings
baseComponentEnv = baseComponentConfig.BaseComponentEnv
baseComponentProviders = baseComponentConfig.BaseComponentProviders
baseComponentHooks = baseComponentConfig.BaseComponentHooks
baseComponentName = baseComponentConfig.FinalBaseComponentName
baseComponentTerraformCommand = baseComponentConfig.BaseComponentCommand
baseComponentBackendType = baseComponentConfig.BaseComponentBackendType
Expand Down Expand Up @@ -1064,6 +1089,18 @@ func ProcessStackConfig(
return nil, err
}

finalComponentHooks, err := m.Merge(
atmosConfig,
[]map[string]any{
terraformHooks,
baseComponentHooks,
componentHooks,
componentOverridesHooks,
})
if err != nil {
return nil, err
}

// Final backend
finalComponentBackendType := globalBackendType
if len(baseComponentBackendType) > 0 {
Expand Down Expand Up @@ -1252,6 +1289,7 @@ func ProcessStackConfig(
comp[cfg.MetadataSectionName] = componentMetadata
comp[cfg.OverridesSectionName] = componentOverrides
comp[cfg.ProvidersSectionName] = finalComponentProviders
comp[cfg.HooksSectionName] = finalComponentHooks

if baseComponentName != "" {
comp[cfg.ComponentSectionName] = baseComponentName
Expand Down Expand Up @@ -1957,6 +1995,7 @@ func ProcessBaseComponentConfig(
var baseComponentSettings map[string]any
var baseComponentEnv map[string]any
var baseComponentProviders map[string]any
var baseComponentHooks map[string]any
var baseComponentCommand string
var baseComponentBackendType string
var baseComponentBackendSection map[string]any
Expand Down Expand Up @@ -2080,6 +2119,13 @@ func ProcessBaseComponentConfig(
}
}

if baseComponentHooksSection, baseComponentHooksSectionExist := baseComponentMap[cfg.HooksSectionName]; baseComponentHooksSectionExist {
baseComponentHooks, ok = baseComponentHooksSection.(map[string]any)
if !ok {
return fmt.Errorf("invalid '%s.hooks' section in the stack '%s'", baseComponent, stack)
}
}

// Base component backend
if i, ok2 := baseComponentMap["backend_type"]; ok2 {
baseComponentBackendType, ok = i.(string)
Expand Down Expand Up @@ -2150,6 +2196,13 @@ func ProcessBaseComponentConfig(
}
baseComponentConfig.BaseComponentProviders = merged

// Base component `hooks`
merged, err = m.Merge(atmosConfig, []map[string]any{baseComponentConfig.BaseComponentHooks, baseComponentHooks})
if err != nil {
return err
}
baseComponentConfig.BaseComponentHooks = merged

// Base component `command`
baseComponentConfig.BaseComponentCommand = baseComponentCommand

Expand Down
2 changes: 1 addition & 1 deletion internal/exec/template_funcs_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func componentFunc(atmosConfig schema.AtmosConfiguration, component string, stac
terraformOutputs = remoteStateBackendStaticTypeOutputs
} else {
// Execute `terraform output`
terraformOutputs, err = execTerraformOutput(atmosConfig, component, stack, sections)
terraformOutputs, err = execTerraformOutput(&atmosConfig, component, stack, sections)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 71aa80e

Please sign in to comment.