]>
Commit | Line | Data |
---|---|---|
1 | package terraform | |
2 | ||
3 | import ( | |
4 | "context" | |
5 | "fmt" | |
6 | "log" | |
7 | "sync" | |
8 | ||
9 | "github.com/hashicorp/terraform/plans" | |
10 | "github.com/hashicorp/terraform/providers" | |
11 | "github.com/hashicorp/terraform/provisioners" | |
12 | "github.com/hashicorp/terraform/version" | |
13 | ||
14 | "github.com/hashicorp/terraform/states" | |
15 | ||
16 | "github.com/hashicorp/hcl2/hcl" | |
17 | "github.com/hashicorp/terraform/configs/configschema" | |
18 | "github.com/hashicorp/terraform/lang" | |
19 | "github.com/hashicorp/terraform/tfdiags" | |
20 | ||
21 | "github.com/hashicorp/terraform/addrs" | |
22 | "github.com/zclconf/go-cty/cty" | |
23 | ) | |
24 | ||
25 | // BuiltinEvalContext is an EvalContext implementation that is used by | |
26 | // Terraform by default. | |
27 | type BuiltinEvalContext struct { | |
28 | // StopContext is the context used to track whether we're complete | |
29 | StopContext context.Context | |
30 | ||
31 | // PathValue is the Path that this context is operating within. | |
32 | PathValue addrs.ModuleInstance | |
33 | ||
34 | // Evaluator is used for evaluating expressions within the scope of this | |
35 | // eval context. | |
36 | Evaluator *Evaluator | |
37 | ||
38 | // Schemas is a repository of all of the schemas we should need to | |
39 | // decode configuration blocks and expressions. This must be constructed by | |
40 | // the caller to include schemas for all of the providers, resource types, | |
41 | // data sources and provisioners used by the given configuration and | |
42 | // state. | |
43 | // | |
44 | // This must not be mutated during evaluation. | |
45 | Schemas *Schemas | |
46 | ||
47 | // VariableValues contains the variable values across all modules. This | |
48 | // structure is shared across the entire containing context, and so it | |
49 | // may be accessed only when holding VariableValuesLock. | |
50 | // The keys of the first level of VariableValues are the string | |
51 | // representations of addrs.ModuleInstance values. The second-level keys | |
52 | // are variable names within each module instance. | |
53 | VariableValues map[string]map[string]cty.Value | |
54 | VariableValuesLock *sync.Mutex | |
55 | ||
56 | Components contextComponentFactory | |
57 | Hooks []Hook | |
58 | InputValue UIInput | |
59 | ProviderCache map[string]providers.Interface | |
60 | ProviderInputConfig map[string]map[string]cty.Value | |
61 | ProviderLock *sync.Mutex | |
62 | ProvisionerCache map[string]provisioners.Interface | |
63 | ProvisionerLock *sync.Mutex | |
64 | ChangesValue *plans.ChangesSync | |
65 | StateValue *states.SyncState | |
66 | ||
67 | once sync.Once | |
68 | } | |
69 | ||
70 | // BuiltinEvalContext implements EvalContext | |
71 | var _ EvalContext = (*BuiltinEvalContext)(nil) | |
72 | ||
73 | func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} { | |
74 | // This can happen during tests. During tests, we just block forever. | |
75 | if ctx.StopContext == nil { | |
76 | return nil | |
77 | } | |
78 | ||
79 | return ctx.StopContext.Done() | |
80 | } | |
81 | ||
82 | func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error { | |
83 | for _, h := range ctx.Hooks { | |
84 | action, err := fn(h) | |
85 | if err != nil { | |
86 | return err | |
87 | } | |
88 | ||
89 | switch action { | |
90 | case HookActionContinue: | |
91 | continue | |
92 | case HookActionHalt: | |
93 | // Return an early exit error to trigger an early exit | |
94 | log.Printf("[WARN] Early exit triggered by hook: %T", h) | |
95 | return EvalEarlyExitError{} | |
96 | } | |
97 | } | |
98 | ||
99 | return nil | |
100 | } | |
101 | ||
102 | func (ctx *BuiltinEvalContext) Input() UIInput { | |
103 | return ctx.InputValue | |
104 | } | |
105 | ||
106 | func (ctx *BuiltinEvalContext) InitProvider(typeName string, addr addrs.ProviderConfig) (providers.Interface, error) { | |
107 | ctx.once.Do(ctx.init) | |
108 | absAddr := addr.Absolute(ctx.Path()) | |
109 | ||
110 | // If we already initialized, it is an error | |
111 | if p := ctx.Provider(absAddr); p != nil { | |
112 | return nil, fmt.Errorf("%s is already initialized", addr) | |
113 | } | |
114 | ||
115 | // Warning: make sure to acquire these locks AFTER the call to Provider | |
116 | // above, since it also acquires locks. | |
117 | ctx.ProviderLock.Lock() | |
118 | defer ctx.ProviderLock.Unlock() | |
119 | ||
120 | key := absAddr.String() | |
121 | ||
122 | p, err := ctx.Components.ResourceProvider(typeName, key) | |
123 | if err != nil { | |
124 | return nil, err | |
125 | } | |
126 | ||
127 | log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", typeName, absAddr) | |
128 | ctx.ProviderCache[key] = p | |
129 | ||
130 | return p, nil | |
131 | } | |
132 | ||
133 | func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface { | |
134 | ctx.once.Do(ctx.init) | |
135 | ||
136 | ctx.ProviderLock.Lock() | |
137 | defer ctx.ProviderLock.Unlock() | |
138 | ||
139 | return ctx.ProviderCache[addr.String()] | |
140 | } | |
141 | ||
142 | func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema { | |
143 | ctx.once.Do(ctx.init) | |
144 | ||
145 | return ctx.Schemas.ProviderSchema(addr.ProviderConfig.Type) | |
146 | } | |
147 | ||
148 | func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.ProviderConfig) error { | |
149 | ctx.once.Do(ctx.init) | |
150 | ||
151 | ctx.ProviderLock.Lock() | |
152 | defer ctx.ProviderLock.Unlock() | |
153 | ||
154 | key := addr.Absolute(ctx.Path()).String() | |
155 | provider := ctx.ProviderCache[key] | |
156 | if provider != nil { | |
157 | delete(ctx.ProviderCache, key) | |
158 | return provider.Close() | |
159 | } | |
160 | ||
161 | return nil | |
162 | } | |
163 | ||
164 | func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.ProviderConfig, cfg cty.Value) tfdiags.Diagnostics { | |
165 | var diags tfdiags.Diagnostics | |
166 | absAddr := addr.Absolute(ctx.Path()) | |
167 | p := ctx.Provider(absAddr) | |
168 | if p == nil { | |
169 | diags = diags.Append(fmt.Errorf("%s not initialized", addr)) | |
170 | return diags | |
171 | } | |
172 | ||
173 | providerSchema := ctx.ProviderSchema(absAddr) | |
174 | if providerSchema == nil { | |
175 | diags = diags.Append(fmt.Errorf("schema for %s is not available", absAddr)) | |
176 | return diags | |
177 | } | |
178 | ||
179 | req := providers.ConfigureRequest{ | |
180 | TerraformVersion: version.String(), | |
181 | Config: cfg, | |
182 | } | |
183 | ||
184 | resp := p.Configure(req) | |
185 | return resp.Diagnostics | |
186 | } | |
187 | ||
188 | func (ctx *BuiltinEvalContext) ProviderInput(pc addrs.ProviderConfig) map[string]cty.Value { | |
189 | ctx.ProviderLock.Lock() | |
190 | defer ctx.ProviderLock.Unlock() | |
191 | ||
192 | if !ctx.Path().IsRoot() { | |
193 | // Only root module provider configurations can have input. | |
194 | return nil | |
195 | } | |
196 | ||
197 | return ctx.ProviderInputConfig[pc.String()] | |
198 | } | |
199 | ||
200 | func (ctx *BuiltinEvalContext) SetProviderInput(pc addrs.ProviderConfig, c map[string]cty.Value) { | |
201 | absProvider := pc.Absolute(ctx.Path()) | |
202 | ||
203 | if !ctx.Path().IsRoot() { | |
204 | // Only root module provider configurations can have input. | |
205 | log.Printf("[WARN] BuiltinEvalContext: attempt to SetProviderInput for non-root module") | |
206 | return | |
207 | } | |
208 | ||
209 | // Save the configuration | |
210 | ctx.ProviderLock.Lock() | |
211 | ctx.ProviderInputConfig[absProvider.String()] = c | |
212 | ctx.ProviderLock.Unlock() | |
213 | } | |
214 | ||
215 | func (ctx *BuiltinEvalContext) InitProvisioner(n string) (provisioners.Interface, error) { | |
216 | ctx.once.Do(ctx.init) | |
217 | ||
218 | // If we already initialized, it is an error | |
219 | if p := ctx.Provisioner(n); p != nil { | |
220 | return nil, fmt.Errorf("Provisioner '%s' already initialized", n) | |
221 | } | |
222 | ||
223 | // Warning: make sure to acquire these locks AFTER the call to Provisioner | |
224 | // above, since it also acquires locks. | |
225 | ctx.ProvisionerLock.Lock() | |
226 | defer ctx.ProvisionerLock.Unlock() | |
227 | ||
228 | key := PathObjectCacheKey(ctx.Path(), n) | |
229 | ||
230 | p, err := ctx.Components.ResourceProvisioner(n, key) | |
231 | if err != nil { | |
232 | return nil, err | |
233 | } | |
234 | ||
235 | ctx.ProvisionerCache[key] = p | |
236 | ||
237 | return p, nil | |
238 | } | |
239 | ||
240 | func (ctx *BuiltinEvalContext) Provisioner(n string) provisioners.Interface { | |
241 | ctx.once.Do(ctx.init) | |
242 | ||
243 | ctx.ProvisionerLock.Lock() | |
244 | defer ctx.ProvisionerLock.Unlock() | |
245 | ||
246 | key := PathObjectCacheKey(ctx.Path(), n) | |
247 | return ctx.ProvisionerCache[key] | |
248 | } | |
249 | ||
250 | func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block { | |
251 | ctx.once.Do(ctx.init) | |
252 | ||
253 | return ctx.Schemas.ProvisionerConfig(n) | |
254 | } | |
255 | ||
256 | func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error { | |
257 | ctx.once.Do(ctx.init) | |
258 | ||
259 | ctx.ProvisionerLock.Lock() | |
260 | defer ctx.ProvisionerLock.Unlock() | |
261 | ||
262 | key := PathObjectCacheKey(ctx.Path(), n) | |
263 | ||
264 | prov := ctx.ProvisionerCache[key] | |
265 | if prov != nil { | |
266 | return prov.Close() | |
267 | } | |
268 | ||
269 | return nil | |
270 | } | |
271 | ||
272 | func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) { | |
273 | var diags tfdiags.Diagnostics | |
274 | scope := ctx.EvaluationScope(self, keyData) | |
275 | body, evalDiags := scope.ExpandBlock(body, schema) | |
276 | diags = diags.Append(evalDiags) | |
277 | val, evalDiags := scope.EvalBlock(body, schema) | |
278 | diags = diags.Append(evalDiags) | |
279 | return val, body, diags | |
280 | } | |
281 | ||
282 | func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) { | |
283 | scope := ctx.EvaluationScope(self, EvalDataForNoInstanceKey) | |
284 | return scope.EvalExpr(expr, wantType) | |
285 | } | |
286 | ||
287 | func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { | |
288 | data := &evaluationStateData{ | |
289 | Evaluator: ctx.Evaluator, | |
290 | ModulePath: ctx.PathValue, | |
291 | InstanceKeyData: keyData, | |
292 | Operation: ctx.Evaluator.Operation, | |
293 | } | |
294 | return ctx.Evaluator.Scope(data, self) | |
295 | } | |
296 | ||
297 | func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance { | |
298 | return ctx.PathValue | |
299 | } | |
300 | ||
301 | func (ctx *BuiltinEvalContext) SetModuleCallArguments(n addrs.ModuleCallInstance, vals map[string]cty.Value) { | |
302 | ctx.VariableValuesLock.Lock() | |
303 | defer ctx.VariableValuesLock.Unlock() | |
304 | ||
305 | childPath := n.ModuleInstance(ctx.PathValue) | |
306 | key := childPath.String() | |
307 | ||
308 | args := ctx.VariableValues[key] | |
309 | if args == nil { | |
310 | args = make(map[string]cty.Value) | |
311 | ctx.VariableValues[key] = vals | |
312 | return | |
313 | } | |
314 | ||
315 | for k, v := range vals { | |
316 | args[k] = v | |
317 | } | |
318 | } | |
319 | ||
320 | func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync { | |
321 | return ctx.ChangesValue | |
322 | } | |
323 | ||
324 | func (ctx *BuiltinEvalContext) State() *states.SyncState { | |
325 | return ctx.StateValue | |
326 | } | |
327 | ||
328 | func (ctx *BuiltinEvalContext) init() { | |
329 | } |