mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add support for scoped resources (#32732)
This commit is contained in:
parent
bb71273f7a
commit
af05cbb645
16
internal/configs/container.go
Normal file
16
internal/configs/container.go
Normal file
@ -0,0 +1,16 @@
|
||||
package configs
|
||||
|
||||
import "github.com/hashicorp/terraform/internal/addrs"
|
||||
|
||||
// Container provides an interface for scoped resources.
|
||||
//
|
||||
// Any resources contained within a Container should not be accessible from
|
||||
// outside the container.
|
||||
type Container interface {
|
||||
// Accessible should return true if the resource specified by addr can
|
||||
// reference other items within this Container.
|
||||
//
|
||||
// Typically, that means that addr will either be the container itself or
|
||||
// something within the container.
|
||||
Accessible(addr addrs.Referenceable) bool
|
||||
}
|
@ -37,6 +37,13 @@ type Resource struct {
|
||||
// For all other resource modes, this field is nil.
|
||||
Managed *ManagedResource
|
||||
|
||||
// Container links a scoped resource back up to the resources that contains
|
||||
// it. This field is referenced during static analysis to check whether any
|
||||
// references are also made from within the same container.
|
||||
//
|
||||
// If this is nil, then this resource is essentially public.
|
||||
Container Container
|
||||
|
||||
DeclRange hcl.Range
|
||||
TypeRange hcl.Range
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
// cases where it's not possible to even determine a suitable result type,
|
||||
// cty.DynamicVal is returned along with errors describing the problem.
|
||||
type Data interface {
|
||||
StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics
|
||||
StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable, source addrs.Referenceable) tfdiags.Diagnostics
|
||||
|
||||
GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||
GetForEachAttr(addrs.ForEachAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
|
||||
|
@ -19,7 +19,7 @@ type dataForTests struct {
|
||||
|
||||
var _ Data = &dataForTests{}
|
||||
|
||||
func (d *dataForTests) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
|
||||
func (d *dataForTests) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable, source addrs.Referenceable) tfdiags.Diagnostics {
|
||||
return nil // does nothing in this stub implementation
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
|
||||
// First we'll do static validation of the references. This catches things
|
||||
// early that might otherwise not get caught due to unknown values being
|
||||
// present in the scope during planning.
|
||||
staticDiags := s.Data.StaticValidateReferences(refs, selfAddr)
|
||||
staticDiags := s.Data.StaticValidateReferences(refs, selfAddr, s.SourceAddr)
|
||||
diags = diags.Append(staticDiags)
|
||||
if staticDiags.HasErrors() {
|
||||
return ctx, diags
|
||||
|
@ -20,6 +20,14 @@ type Scope struct {
|
||||
// or nil if the "self" object should not be available at all.
|
||||
SelfAddr addrs.Referenceable
|
||||
|
||||
// SourceAddr is the address of the source item for the scope. This will
|
||||
// affect any scoped resources that can be accessed from within this scope.
|
||||
//
|
||||
// If nil, access is assumed to be at the module level. So, in practice this
|
||||
// only needs to be set for items that should be able to access something
|
||||
// hidden in their own scope.
|
||||
SourceAddr addrs.Referenceable
|
||||
|
||||
// BaseDir is the base directory used by any interpolation functions that
|
||||
// accept filesystem paths as arguments.
|
||||
BaseDir string
|
||||
|
@ -92,5 +92,5 @@ func (c *Context) Eval(config *configs.Config, state *states.State, moduleAddr a
|
||||
// caches its contexts, so we should get hold of the context that was
|
||||
// previously used for evaluation here, unless we skipped walking.
|
||||
evalCtx := walker.EnterPath(moduleAddr)
|
||||
return evalCtx.EvaluationScope(nil, EvalDataForNoInstanceKey), diags
|
||||
return evalCtx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey), diags
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func evalCheckRule(typ addrs.CheckType, rule *configs.CheckRule, ctx EvalContext
|
||||
panic(fmt.Sprintf("Invalid self reference type %t", self))
|
||||
}
|
||||
}
|
||||
scope := ctx.EvaluationScope(selfReference, keyData)
|
||||
scope := ctx.EvaluationScope(selfReference, nil, keyData)
|
||||
|
||||
hclCtx, moreDiags := scope.EvalContext(refs)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
@ -125,7 +125,7 @@ type EvalContext interface {
|
||||
|
||||
// EvaluationScope returns a scope that can be used to evaluate reference
|
||||
// addresses in this context.
|
||||
EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope
|
||||
EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope
|
||||
|
||||
// SetRootModuleArgument defines the value for one variable of the root
|
||||
// module. The caller must ensure that given value is a suitable
|
||||
|
@ -270,7 +270,7 @@ func (ctx *BuiltinEvalContext) CloseProvisioners() error {
|
||||
|
||||
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
scope := ctx.EvaluationScope(self, keyData)
|
||||
scope := ctx.EvaluationScope(self, nil, keyData)
|
||||
body, evalDiags := scope.ExpandBlock(body, schema)
|
||||
diags = diags.Append(evalDiags)
|
||||
val, evalDiags := scope.EvalBlock(body, schema)
|
||||
@ -279,7 +279,7 @@ func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
|
||||
scope := ctx.EvaluationScope(self, EvalDataForNoInstanceKey)
|
||||
scope := ctx.EvaluationScope(self, nil, EvalDataForNoInstanceKey)
|
||||
return scope.EvalExpr(expr, wantType)
|
||||
}
|
||||
|
||||
@ -397,7 +397,7 @@ func (ctx *BuiltinEvalContext) EvaluateReplaceTriggeredBy(expr hcl.Expression, r
|
||||
return ref, replace, diags
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData instances.RepetitionData) *lang.Scope {
|
||||
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
|
||||
if !ctx.pathSet {
|
||||
panic("context path not set")
|
||||
}
|
||||
@ -407,7 +407,7 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData
|
||||
InstanceKeyData: keyData,
|
||||
Operation: ctx.Evaluator.Operation,
|
||||
}
|
||||
scope := ctx.Evaluator.Scope(data, self)
|
||||
scope := ctx.Evaluator.Scope(data, self, source)
|
||||
|
||||
// ctx.PathValue is the path of the module that contains whatever
|
||||
// expression the caller will be trying to evaluate, so this will
|
||||
|
@ -319,7 +319,7 @@ func (c *MockEvalContext) installSimpleEval() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
|
||||
func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
|
||||
c.EvaluationScopeCalled = true
|
||||
c.EvaluationScopeSelf = self
|
||||
c.EvaluationScopeKeyData = keyData
|
||||
|
@ -43,7 +43,7 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext, allowU
|
||||
|
||||
refs, moreDiags := lang.ReferencesInExpr(expr)
|
||||
diags = diags.Append(moreDiags)
|
||||
scope := ctx.EvaluationScope(nil, EvalDataForNoInstanceKey)
|
||||
scope := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey)
|
||||
var hclCtx *hcl.EvalContext
|
||||
if scope != nil {
|
||||
hclCtx, moreDiags = scope.EvalContext(refs)
|
||||
|
@ -223,7 +223,7 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
|
||||
config.Name: val,
|
||||
}),
|
||||
},
|
||||
Functions: ctx.EvaluationScope(nil, EvalDataForNoInstanceKey).Functions(),
|
||||
Functions: ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).Functions(),
|
||||
}
|
||||
|
||||
for _, validation := range config.Validations {
|
||||
|
@ -68,12 +68,13 @@ type Evaluator struct {
|
||||
// If the "self" argument is nil then the "self" object is not available
|
||||
// in evaluated expressions. Otherwise, it behaves as an alias for the given
|
||||
// address.
|
||||
func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope {
|
||||
func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable, source addrs.Referenceable) *lang.Scope {
|
||||
return &lang.Scope{
|
||||
Data: data,
|
||||
SelfAddr: self,
|
||||
PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval,
|
||||
BaseDir: ".", // Always current working directory for now.
|
||||
Data: data,
|
||||
SelfAddr: self,
|
||||
SourceAddr: source,
|
||||
PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval,
|
||||
BaseDir: ".", // Always current working directory for now.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ func TestEvaluatorGetTerraformAttr(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
|
||||
t.Run("workspace", func(t *testing.T) {
|
||||
want := cty.StringVal("foo")
|
||||
@ -55,7 +55,7 @@ func TestEvaluatorGetPathAttr(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
|
||||
t.Run("module", func(t *testing.T) {
|
||||
want := cty.StringVal("bar/baz")
|
||||
@ -124,7 +124,7 @@ func TestEvaluatorGetInputVariable(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
|
||||
want := cty.StringVal("bar").Mark(marks.Sensitive)
|
||||
got, diags := scope.Data.GetInputVariable(addrs.InputVariable{
|
||||
@ -273,7 +273,7 @@ func TestEvaluatorGetResource(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
|
||||
want := cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
@ -438,7 +438,7 @@ func TestEvaluatorGetResource_changes(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
|
||||
want := cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
@ -473,7 +473,7 @@ func TestEvaluatorGetModule(t *testing.T) {
|
||||
data := &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope := evaluator.Scope(data, nil)
|
||||
scope := evaluator.Scope(data, nil, nil)
|
||||
want := cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("bar").Mark(marks.Sensitive)})
|
||||
got, diags := scope.Data.GetModule(addrs.ModuleCall{
|
||||
Name: "mod",
|
||||
@ -501,7 +501,7 @@ func TestEvaluatorGetModule(t *testing.T) {
|
||||
data = &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope = evaluator.Scope(data, nil)
|
||||
scope = evaluator.Scope(data, nil, nil)
|
||||
want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)})
|
||||
got, diags = scope.Data.GetModule(addrs.ModuleCall{
|
||||
Name: "mod",
|
||||
@ -519,7 +519,7 @@ func TestEvaluatorGetModule(t *testing.T) {
|
||||
data = &evaluationStateData{
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
scope = evaluator.Scope(data, nil)
|
||||
scope = evaluator.Scope(data, nil, nil)
|
||||
want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)})
|
||||
got, diags = scope.Data.GetModule(addrs.ModuleCall{
|
||||
Name: "mod",
|
||||
|
@ -28,16 +28,16 @@ import (
|
||||
//
|
||||
// The result may include warning diagnostics if, for example, deprecated
|
||||
// features are referenced.
|
||||
func (d *evaluationStateData) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
|
||||
func (d *evaluationStateData) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable, source addrs.Referenceable) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
for _, ref := range refs {
|
||||
moreDiags := d.staticValidateReference(ref, self)
|
||||
moreDiags := d.staticValidateReference(ref, self, source)
|
||||
diags = diags.Append(moreDiags)
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func (d *evaluationStateData) staticValidateReference(ref *addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
|
||||
func (d *evaluationStateData) staticValidateReference(ref *addrs.Reference, self addrs.Referenceable, source addrs.Referenceable) tfdiags.Diagnostics {
|
||||
modCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
|
||||
if modCfg == nil {
|
||||
// This is a bug in the caller rather than a problem with the
|
||||
@ -78,12 +78,12 @@ func (d *evaluationStateData) staticValidateReference(ref *addrs.Reference, self
|
||||
case addrs.Resource:
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(d.staticValidateSingleResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
|
||||
diags = diags.Append(d.staticValidateResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
|
||||
diags = diags.Append(d.staticValidateResourceReference(modCfg, addr, source, ref.Remaining, ref.SourceRange))
|
||||
return diags
|
||||
case addrs.ResourceInstance:
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(d.staticValidateMultiResourceReference(modCfg, addr, ref.Remaining, ref.SourceRange))
|
||||
diags = diags.Append(d.staticValidateResourceReference(modCfg, addr.ContainingResource(), ref.Remaining, ref.SourceRange))
|
||||
diags = diags.Append(d.staticValidateResourceReference(modCfg, addr.ContainingResource(), source, ref.Remaining, ref.SourceRange))
|
||||
return diags
|
||||
|
||||
// We also handle all module call references the same way, disregarding index.
|
||||
@ -187,7 +187,7 @@ func (d *evaluationStateData) staticValidateMultiResourceReference(modCfg *confi
|
||||
return diags
|
||||
}
|
||||
|
||||
func (d *evaluationStateData) staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
|
||||
func (d *evaluationStateData) staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource, source addrs.Referenceable, remain hcl.Traversal, rng tfdiags.SourceRange) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var modeAdjective string
|
||||
@ -223,6 +223,15 @@ func (d *evaluationStateData) staticValidateResourceReference(modCfg *configs.Co
|
||||
return diags
|
||||
}
|
||||
|
||||
if cfg.Container != nil && (source == nil || !cfg.Container.Accessible(source)) {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: `Reference to scoped resource`,
|
||||
Detail: fmt.Sprintf(`The referenced %s resource %q %q is not available from this context.`, modeAdjective, addr.Type, addr.Name),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr())
|
||||
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerFqn, addr.Mode, addr.Type)
|
||||
if err != nil {
|
||||
|
@ -100,7 +100,7 @@ For example, to correlate with indices of a referring resource, use:
|
||||
Evaluator: evaluator,
|
||||
}
|
||||
|
||||
diags = data.StaticValidateReferences(refs, nil)
|
||||
diags = data.StaticValidateReferences(refs, nil, nil)
|
||||
if diags.HasErrors() {
|
||||
if test.WantErr == "" {
|
||||
t.Fatalf("Unexpected diagnostics: %s", diags.Err())
|
||||
|
@ -214,7 +214,7 @@ func (n *nodeModuleVariable) evalModuleVariable(ctx EvalContext, validateOnly bo
|
||||
moduleInstanceRepetitionData = ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance)
|
||||
}
|
||||
|
||||
scope := ctx.EvaluationScope(nil, moduleInstanceRepetitionData)
|
||||
scope := ctx.EvaluationScope(nil, nil, moduleInstanceRepetitionData)
|
||||
val, moreDiags := scope.EvalExpr(expr, cty.DynamicPseudoType)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
|
@ -2049,7 +2049,7 @@ func (n *NodeAbstractResourceInstance) evalDestroyProvisionerConfig(ctx EvalCont
|
||||
// destroy-time provisioners.
|
||||
keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, nil)
|
||||
|
||||
evalScope := ctx.EvaluationScope(n.ResourceInstanceAddr().Resource, keyData)
|
||||
evalScope := ctx.EvaluationScope(n.ResourceInstanceAddr().Resource, nil, keyData)
|
||||
config, evalDiags := evalScope.EvalSelfBlock(body, self, schema, keyData)
|
||||
diags = diags.Append(evalDiags)
|
||||
|
||||
|
@ -465,7 +465,7 @@ func (n *NodeValidatableResource) evaluateExpr(ctx EvalContext, expr hcl.Express
|
||||
refs, refDiags := lang.ReferencesInExpr(expr)
|
||||
diags = diags.Append(refDiags)
|
||||
|
||||
scope := ctx.EvaluationScope(self, keyData)
|
||||
scope := ctx.EvaluationScope(self, nil, keyData)
|
||||
|
||||
hclCtx, moreDiags := scope.EvalContext(refs)
|
||||
diags = diags.Append(moreDiags)
|
||||
@ -581,7 +581,7 @@ func validateDependsOn(ctx EvalContext, dependsOn []hcl.Traversal) (diags tfdiag
|
||||
// we'll just eval it and count on the fact that our evaluator will
|
||||
// detect references to non-existent objects.
|
||||
if !diags.HasErrors() {
|
||||
scope := ctx.EvaluationScope(nil, EvalDataForNoInstanceKey)
|
||||
scope := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey)
|
||||
if scope != nil { // sometimes nil in tests, due to incomplete mocks
|
||||
_, refDiags = scope.EvalReference(ref, cty.DynamicPseudoType)
|
||||
diags = diags.Append(refDiags)
|
||||
|
Loading…
Reference in New Issue
Block a user