Add support for scoped resources (#32732)

This commit is contained in:
Liam Cervante 2023-03-10 11:11:10 +01:00 committed by GitHub
parent bb71273f7a
commit af05cbb645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 78 additions and 37 deletions

View 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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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.
}
}

View File

@ -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",

View File

@ -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 {

View File

@ -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())

View File

@ -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() {

View File

@ -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)

View File

@ -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)