-
-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathterraform_state_utils.go
More file actions
147 lines (129 loc) · 5.62 KB
/
terraform_state_utils.go
File metadata and controls
147 lines (129 loc) · 5.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package exec
import (
"fmt"
"sync"
"github.com/cloudposse/atmos/pkg/perf"
log "github.com/cloudposse/atmos/pkg/logger"
errUtils "github.com/cloudposse/atmos/errors"
tb "github.com/cloudposse/atmos/internal/terraform_backend"
"github.com/cloudposse/atmos/pkg/auth"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
)
var terraformStateCache = sync.Map{}
// GetTerraformState retrieves a specified Terraform output variable for a given component within a stack.
// It optionally uses a cache to avoid redundant state retrievals and supports both static and dynamic backends.
// Parameters:
// - atmosConfig: Atmos configuration pointer
// - yamlFunc: Name of the calling YAML function for error context
// - stack: Stack identifier
// - component: Component identifier
// - output: Output variable key to retrieve
// - skipCache: Flag to bypass cache lookup
// - authContext: Optional auth context containing Atmos-managed credentials
// - authManager: Optional auth manager for nested operations that need authentication
//
// Returns the output value or nil if the component is not provisioned.
func GetTerraformState(
atmosConfig *schema.AtmosConfiguration,
yamlFunc string,
stack string,
component string,
output string,
skipCache bool,
authContext *schema.AuthContext,
authManager any,
) (any, error) {
defer perf.Track(atmosConfig, "exec.GetTerraformState")()
stackSlug := fmt.Sprintf("%s-%s", stack, component)
// If the result for the component in the stack already exists in the cache, return it.
if !skipCache {
backend, found := terraformStateCache.Load(stackSlug)
if found && backend != nil {
log.Debug("Cache hit",
"function", yamlFunc,
cfg.ComponentStr, component,
cfg.StackStr, stack,
"output", output,
)
result, err := tb.GetTerraformBackendVariable(atmosConfig, backend.(map[string]any), output)
if err != nil {
er := fmt.Errorf("%w %s for component `%s` in stack `%s`\nin YAML function: `%s`\n%v", errUtils.ErrEvaluateTerraformBackendVariable, output, component, stack, yamlFunc, err)
return nil, er
}
return result, nil
}
}
// Cast authManager from 'any' to auth.AuthManager if provided.
var parentAuthMgr auth.AuthManager
if authManager != nil {
var ok bool
parentAuthMgr, ok = authManager.(auth.AuthManager)
if !ok {
return nil, fmt.Errorf("%w: expected auth.AuthManager", errUtils.ErrInvalidAuthManagerType)
}
}
// Resolve AuthManager for this nested component.
// Checks if component has auth config defined:
// - If yes: creates component-specific AuthManager with merged auth config
// - If no: uses parent AuthManager (inherits authentication)
// This enables each nested level to optionally override auth settings.
resolvedAuthMgr, err := resolveAuthManagerForNestedComponent(atmosConfig, component, stack, parentAuthMgr)
if err != nil {
log.Debug("Auth does not exist for nested component, using parent AuthManager",
"component", component,
"stack", stack,
"error", err,
)
resolvedAuthMgr = parentAuthMgr
}
componentSections, err := ExecuteDescribeComponent(&ExecuteDescribeComponentParams{
Component: component,
Stack: stack,
ProcessTemplates: true,
ProcessYamlFunctions: true,
Skip: nil,
AuthManager: resolvedAuthMgr, // Use resolved AuthManager (may be component-specific or inherited)
})
if err != nil {
er := fmt.Errorf("%w `%s` in stack `%s`\nin YAML function: `%s`\n%v", errUtils.ErrDescribeComponent, component, stack, yamlFunc, err)
return nil, er
}
// 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 := GetComponentRemoteStateBackendStaticType(&componentSections)
// Read static remote state backend outputs.
if remoteStateBackendStaticTypeOutputs != nil {
// Cache the result
terraformStateCache.Store(stackSlug, remoteStateBackendStaticTypeOutputs)
result, exists, err := GetStaticRemoteStateOutput(atmosConfig, component, stack, remoteStateBackendStaticTypeOutputs, output)
if err != nil {
return nil, fmt.Errorf("%w for component `%s` in stack `%s`\nin YAML function: `%s`\n%v", errUtils.ErrReadTerraformState, component, stack, yamlFunc, err)
}
if !exists {
return nil, fmt.Errorf("%w: output `%s` does not exist for component `%s` in stack `%s`\nin YAML function: `%s`", errUtils.ErrReadTerraformState, output, component, stack, yamlFunc)
}
// result may be nil if the output is legitimately null
return result, nil
}
// Read Terraform backend.
backend, err := tb.GetTerraformBackend(atmosConfig, &componentSections, authContext)
if err != nil {
er := fmt.Errorf("%w for component `%s` in stack `%s`\nin YAML function: `%s`\n%v", errUtils.ErrReadTerraformState, component, stack, yamlFunc, err)
return nil, er
}
// Cache the result.
terraformStateCache.Store(stackSlug, backend)
// If `backend` is `nil`, return a recoverable error (the component in the stack has not been provisioned yet).
// This allows callers to use YQ defaults if available.
if backend == nil {
return nil, fmt.Errorf("%w for component `%s` in stack `%s`", errUtils.ErrTerraformStateNotProvisioned, component, stack)
}
// Get the output.
result, err := tb.GetTerraformBackendVariable(atmosConfig, backend, output)
if err != nil {
er := fmt.Errorf("%w %s for component `%s` in stack `%s`\nin YAML function: `%s`\n%v", errUtils.ErrEvaluateTerraformBackendVariable, output, component, stack, yamlFunc, err)
return nil, er
}
return result, nil
}