1package terraform
2
3import (
4	"context"
5	"fmt"
6	"log"
7	"sync"
8
9	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
10	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
11	"github.com/hashicorp/terraform-plugin-sdk/internal/provisioners"
12	"github.com/hashicorp/terraform-plugin-sdk/internal/version"
13
14	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
15
16	"github.com/hashicorp/hcl2/hcl"
17	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
18	"github.com/hashicorp/terraform-plugin-sdk/internal/lang"
19	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
20
21	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
22	"github.com/zclconf/go-cty/cty"
23)
24
25// BuiltinEvalContext is an EvalContext implementation that is used by
26// Terraform by default.
27type 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
71var _ EvalContext = (*BuiltinEvalContext)(nil)
72
73func (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
82func (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
102func (ctx *BuiltinEvalContext) Input() UIInput {
103	return ctx.InputValue
104}
105
106func (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
133func (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
142func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema {
143	ctx.once.Do(ctx.init)
144
145	return ctx.Schemas.ProviderSchema(addr.ProviderConfig.Type)
146}
147
148func (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
164func (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
188func (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
200func (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
215func (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
240func (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
250func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
251	ctx.once.Do(ctx.init)
252
253	return ctx.Schemas.ProvisionerConfig(n)
254}
255
256func (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
272func (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
282func (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
287func (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
297func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
298	return ctx.PathValue
299}
300
301func (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
320func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync {
321	return ctx.ChangesValue
322}
323
324func (ctx *BuiltinEvalContext) State() *states.SyncState {
325	return ctx.StateValue
326}
327
328func (ctx *BuiltinEvalContext) init() {
329}
330