Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e3bd6b7
updates
aknysh Mar 20, 2025
f3442b1
updates
aknysh Mar 20, 2025
a3fa15f
updates
aknysh Mar 20, 2025
aa01305
updates
aknysh Mar 20, 2025
c1caa04
updates
aknysh Mar 20, 2025
560b313
Merge remote-tracking branch 'origin/main' into update-terraform-init
aknysh Mar 21, 2025
db03a25
updates
aknysh Mar 21, 2025
de41c73
updates
aknysh Mar 21, 2025
525ad6c
updates
aknysh Mar 22, 2025
77b9ac8
updates
aknysh Mar 22, 2025
b067d5a
updates
aknysh Mar 22, 2025
2f1c103
update `atmos.Component` template function
aknysh Mar 22, 2025
d4d71c6
updates
aknysh Mar 22, 2025
44d9826
updates
aknysh Mar 22, 2025
27dcc97
updates
aknysh Mar 22, 2025
8d403c0
updates
aknysh Mar 22, 2025
72e6b1b
updates
aknysh Mar 22, 2025
3ab3adc
add unit tests
aknysh Mar 22, 2025
fb601f2
add unit tests
aknysh Mar 23, 2025
4402a68
add unit tests
aknysh Mar 23, 2025
c7aec28
add unit tests
aknysh Mar 23, 2025
7fb50fd
add unit tests
aknysh Mar 23, 2025
38c5262
add unit tests
aknysh Mar 23, 2025
ae71bc1
Apply suggestions from code review
aknysh Mar 23, 2025
0fc939e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 23, 2025
32be813
updates
aknysh Mar 23, 2025
73494fb
Merge remote-tracking branch 'origin/update-terraform-init' into upda…
aknysh Mar 23, 2025
67861ee
Update internal/exec/template_funcs_component.go
aknysh Mar 24, 2025
e0c45fc
updates
aknysh Mar 24, 2025
a384a0f
updates
aknysh Mar 24, 2025
123dfbb
updates
aknysh Mar 24, 2025
1789f14
Merge remote-tracking branch 'origin/main' into update-terraform-init
aknysh Mar 24, 2025
252b969
updates
aknysh Mar 24, 2025
9eeffef
updates
aknysh Mar 24, 2025
d2e708a
updates
aknysh Mar 24, 2025
783717f
updates
aknysh Mar 24, 2025
842ef07
updates
aknysh Mar 24, 2025
8c12e55
Merge remote-tracking branch 'origin/main' into update-terraform-init
aknysh Mar 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ components:
init_run_reconfigure: true
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE' ENV var, or '--auto-generate-backend-file' command-line argument
auto_generate_backend_file: false
init:
# Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_INIT_PASS_VARS' ENV var, or '--init-pass-vars' command-line argument
pass_vars: false
helmfile:
# Can also be set using 'ATMOS_COMPONENTS_HELMFILE_BASE_PATH' ENV var, or '--helmfile-dir' command-line argument
# Supports both absolute and relative paths
Expand Down
12 changes: 6 additions & 6 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion internal/exec/atmos.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func ExecuteAtmosCmd() error {

// Get a map of stacks and components in the stacks
// Don't process `Go` templates and YAML functions in Atmos stack manifests since we just need to display the stack and component names in the TUI
stacksMap, err := ExecuteDescribeStacks(atmosConfig, "", nil, nil, nil, false, false, false, false, nil)
stacksMap, err := ExecuteDescribeStacks(atmosConfig, "", nil, nil, nil, false, true, true, false, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -147,6 +147,8 @@ func ExecuteAtmosCmd() error {
configAndStacksInfo.ComponentFromArg = selectedComponent
configAndStacksInfo.Stack = selectedStack
configAndStacksInfo.SubCommand = subcommand
configAndStacksInfo.ProcessTemplates = true
configAndStacksInfo.ProcessFunctions = true
err = ExecuteTerraform(configAndStacksInfo)
if err != nil {
return err
Expand Down
6 changes: 4 additions & 2 deletions internal/exec/describe_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ func ExecuteDescribeStacks(
}

componentSectionProcessed, err := ProcessTmplWithDatasources(
atmosConfig,
&atmosConfig,
&configAndStacksInfo,
settingsSectionStruct,
"describe-stacks-all-sections",
componentSectionStr,
Expand Down Expand Up @@ -576,7 +577,8 @@ func ExecuteDescribeStacks(
}

componentSectionProcessed, err := ProcessTmplWithDatasources(
atmosConfig,
&atmosConfig,
&configAndStacksInfo,
settingsSectionStruct,
"templates-describe-stacks-all-atmos-sections",
componentSectionStr,
Expand Down
20 changes: 13 additions & 7 deletions internal/exec/template_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,30 @@ import (
)

// FuncMap creates and returns a map of template functions
func FuncMap(atmosConfig schema.AtmosConfiguration, ctx context.Context, gomplateData *data.Data) template.FuncMap {
atmosFuncs := &AtmosFuncs{atmosConfig, ctx, gomplateData}
func FuncMap(
atmosConfig *schema.AtmosConfiguration,
configAndStacksInfo *schema.ConfigAndStacksInfo,
ctx context.Context,
gomplateData *data.Data,
) template.FuncMap {
atmosFuncs := &AtmosFuncs{atmosConfig, configAndStacksInfo, ctx, gomplateData}

return map[string]any{
"atmos": func() any { return atmosFuncs },
}
}

type AtmosFuncs struct {
atmosConfig schema.AtmosConfiguration
ctx context.Context
gomplateData *data.Data
atmosConfig *schema.AtmosConfiguration
configAndStacksInfo *schema.ConfigAndStacksInfo
ctx context.Context
gomplateData *data.Data
}

func (f AtmosFuncs) Component(component string, stack string) (any, error) {
return componentFunc(f.atmosConfig, component, stack)
return componentFunc(f.atmosConfig, f.configAndStacksInfo, component, stack)
}

func (f AtmosFuncs) GomplateDatasource(alias string, args ...string) (any, error) {
return gomplateDatasourceFunc(f.atmosConfig, alias, f.gomplateData, args...)
return gomplateDatasourceFunc(alias, f.gomplateData, args...)
}
81 changes: 45 additions & 36 deletions internal/exec/template_funcs_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,38 @@
"fmt"
"sync"

log "github.com/charmbracelet/log"
"github.com/samber/lo"

cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
)

var componentFuncSyncMap = sync.Map{}

func componentFunc(atmosConfig schema.AtmosConfiguration, component string, stack string) (any, error) {
u.LogTrace(fmt.Sprintf("Executing template function 'atmos.Component(%s, %s)'", component, stack))

func componentFunc(
atmosConfig *schema.AtmosConfiguration,
configAndStacksInfo *schema.ConfigAndStacksInfo,
component string,
stack string,
) (any, error) {
functionName := fmt.Sprintf("atmos.Component(%s, %s)", component, stack)
stackSlug := fmt.Sprintf("%s-%s", stack, component)

log.Debug("Executing template function", "function", functionName)

// If the result for the component in the stack already exists in the cache, return it
existingSections, found := componentFuncSyncMap.Load(stackSlug)
if found && existingSections != nil {
if atmosConfig.Logs.Level == u.LogLevelTrace {
u.LogTrace(fmt.Sprintf("Found the result of the template function 'atmos.Component(%s, %s)' in the cache", component, stack))

if outputsSection, ok := existingSections.(map[string]any)["outputs"]; ok {
u.LogTrace("'outputs' section:")
y, err2 := u.ConvertToYAML(outputsSection)
if err2 != nil {
u.LogError(err2)
} else {
u.LogTrace(y)
}
log.Debug("Cache hit for template function", "function", functionName)

if outputsSection, ok := existingSections.(map[string]any)["outputs"]; ok {
y, err2 := u.ConvertToYAML(outputsSection)
if err2 != nil {
log.Error(err2)
} else {
log.Debug("Result of the template function", "function", functionName, "outputs", y)
}
}

Expand All @@ -42,41 +47,45 @@
return nil, err
}

// Process Terraform remote state
var terraformOutputs map[string]any

// Check if the component in the stack is configured with the 'static' remote state backend,
// in which case get the `output` from the static remote state instead of executing `terraform output`
remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
if err != nil {
return nil, err
}

if remoteStateBackendStaticTypeOutputs != nil {
terraformOutputs = remoteStateBackendStaticTypeOutputs
} else {
// Execute `terraform output`
terraformOutputs, err = execTerraformOutput(&atmosConfig, component, stack, sections)
if configAndStacksInfo.ComponentType == cfg.TerraformComponentType {

Check warning

Code scanning / golangci-lint

if configAndStacksInfo.ComponentType == cfg.TerraformComponentType has complex nested blocks (complexity: 5) Warning

if configAndStacksInfo.ComponentType == cfg.TerraformComponentType has complex nested blocks (complexity: 5)
// Check if the component in the stack is configured with the 'static' remote state backend,
// in which case get the `output` from the static remote state instead of executing `terraform output`
remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
if err != nil {
return nil, err
}
}

outputs := map[string]any{
"outputs": terraformOutputs,
}
if remoteStateBackendStaticTypeOutputs != nil {
// Return the static backend outputs
terraformOutputs = remoteStateBackendStaticTypeOutputs
} else {
// Execute `terraform output`
terraformOutputs, err = execTerraformOutput(atmosConfig, component, stack, sections)
if err != nil {
return nil, err
}
}

sections = lo.Assign(sections, outputs)
outputs := map[string]any{
"outputs": terraformOutputs,
}

sections = lo.Assign(sections, outputs)
}

// Cache the result
componentFuncSyncMap.Store(stackSlug, sections)

if atmosConfig.Logs.Level == u.LogLevelTrace {
u.LogTrace(fmt.Sprintf("Executed template function 'atmos.Component(%s, %s)'\n\n'outputs' section:", component, stack))
log.Debug("Executed template function", "function", functionName)

if configAndStacksInfo.ComponentType == cfg.TerraformComponentType {
y, err2 := u.ConvertToYAML(terraformOutputs)
if err2 != nil {
u.LogError(err2)
log.Error(err2)
} else {
u.LogTrace(y)
log.Debug("Result of the template function", "function", functionName, "outputs", y)
}
}

Expand Down
74 changes: 74 additions & 0 deletions internal/exec/template_funcs_component_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package exec

import (
"os"
"path/filepath"
"testing"

log "github.com/charmbracelet/log"
"github.com/stretchr/testify/assert"

cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
)

func TestComponentFunc(t *testing.T) {
log.SetLevel(log.DebugLevel)
log.SetOutput(os.Stdout)

// Capture the starting working directory
startingDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get the current working directory: %v", err)
}

defer func() {
// Delete the generated files and folders after the test
err := os.RemoveAll(filepath.Join("components", "terraform", "mock", ".terraform"))
assert.NoError(t, err)

err = os.RemoveAll(filepath.Join("components", "terraform", "mock", "terraform.tfstate.d"))
assert.NoError(t, err)

// Change back to the original working directory after the test
if err = os.Chdir(startingDir); err != nil {
t.Fatalf("Failed to change back to the starting directory: %v", err)
}
}()

// Define the working directory
workDir := "../../tests/fixtures/scenarios/stack-templates-3"
if err := os.Chdir(workDir); err != nil {
t.Fatalf("Failed to change directory to %q: %v", workDir, err)
}

info := schema.ConfigAndStacksInfo{
StackFromArg: "",
Stack: "nonprod",
StackFile: "",
ComponentType: "terraform",
ComponentFromArg: "component-1",
SubCommand: "deploy",
ProcessTemplates: true,
ProcessFunctions: true,
}

err = ExecuteTerraform(info)
if err != nil {
t.Fatalf("Failed to execute 'ExecuteTerraform': %v", err)
}

atmosConfig, err := cfg.InitCliConfig(info, true)
assert.NoError(t, err)

d, err := componentFunc(&atmosConfig, &info, "component-2", "nonprod")
assert.NoError(t, err)

y, err := u.ConvertToYAML(d)
assert.NoError(t, err)

assert.Contains(t, y, "foo: component-1-a")
assert.Contains(t, y, "bar: component-1-b")
assert.Contains(t, y, "baz: component-1-b--component-1-c")
}
Loading
Loading