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

add hooks and store write functionality #874

Merged
merged 18 commits into from
Jan 8, 2025
Merged
76 changes: 76 additions & 0 deletions cmd/terraform.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
package cmd

import (
<<<<<<< HEAD

Check failure on line 4 in cmd/terraform.go

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, linux)

missing import path

Check failure on line 4 in cmd/terraform.go

View workflow job for this annotation

GitHub Actions / Build (windows-latest, windows)

missing import path

Check failure on line 4 in cmd/terraform.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, macos)

missing import path
"context"
"fmt"
"strings"
=======
"fmt"
>>>>>>> main

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

e "github.com/cloudposse/atmos/internal/exec"
<<<<<<< HEAD
cfg "github.com/cloudposse/atmos/pkg/config"
h "github.com/cloudposse/atmos/pkg/hooks"
=======
"github.com/cloudposse/atmos/internal/tui/templates"
>>>>>>> main
mcalhoun marked this conversation as resolved.
Show resolved Hide resolved
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
cc "github.com/ivanpirog/coloredcobra"
)

type contextKey string

// terraformCmd represents the base command for all terraform sub-commands
var terraformCmd = &cobra.Command{
Use: "terraform",
Expand All @@ -23,8 +36,71 @@
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("atmos_info"), info)
RootCmd.SetContext(ctx)

// Check Atmos configuration
checkAtmosConfig()
},
PostRunE: func(cmd *cobra.Command, args []string) {
info := RootCmd.Context().Value(contextKey("atmos_info")).(schema.ConfigAndStacksInfo)
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" {
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]
u.LogInfo(atmosConfig, fmt.Sprintf(" storing terraform output '%s' in store '%s' with key '%s' and value %v", outputKey, hook.Name, key, outputValue))

return store.Set(info.Stack, info.ComponentFromArg, key, outputValue)
}
}
}
}
},
mcalhoun marked this conversation as resolved.
Show resolved Hide resolved
}


// Contains checks if a slice of strings contains an exact match for the target string.
func Contains(slice []string, target string) bool {
for _, item := range slice {
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
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
Loading