stacks: expand reference scope of static validations (#34721)

pull/34722/head
Liam Cervante 2 years ago committed by GitHub
parent 831630fabe
commit 68fd15dfdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
@ -55,6 +56,10 @@ func (c *ComponentConfig) Declaration(ctx context.Context) *stackconfig.Componen
return c.config
}
func (c *ComponentConfig) StackConfig(ctx context.Context) *StackConfig {
return c.main.mustStackConfig(ctx, c.addr.Stack)
}
// ModuleTree returns the static representation of the tree of modules starting
// at the component's configured source address, or nil if any of the
// modules have errors that prevent even static decoding.
@ -285,7 +290,7 @@ func (c *ComponentConfig) RequiredProviderInstances(ctx context.Context) addrs.S
func (c *ComponentConfig) CheckProviders(ctx context.Context, phase EvalPhase) (addrs.Set[addrs.RootProviderConfig], tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
stackConfig := c.main.StackConfig(ctx, c.Addr().Stack)
stackConfig := c.StackConfig(ctx)
declConfigs := c.Declaration(ctx).ProviderConfigs
neededProviders := c.RequiredProviderInstances(ctx)
@ -408,6 +413,16 @@ func (c *ComponentConfig) ExprReferenceValue(ctx context.Context, phase EvalPhas
return cty.DynamicVal
}
func (c *ComponentConfig) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) {
repetition := instances.RepetitionData{}
if c.Declaration(ctx).ForEach != nil {
// For validation, we'll return unknown for the instance data.
repetition.EachKey = cty.UnknownVal(cty.String).RefineNotNull()
repetition.EachValue = cty.DynamicVal
}
return c.StackConfig(ctx).resolveExpressionReference(ctx, ref, nil, repetition)
}
func (c *ComponentConfig) checkValid(ctx context.Context, phase EvalPhase) tfdiags.Diagnostics {
diags, err := c.validate.Do(ctx, func(ctx context.Context) (tfdiags.Diagnostics, error) {
var diags tfdiags.Diagnostics

@ -10,6 +10,8 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/promising"
@ -18,7 +20,6 @@ import (
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
"github.com/hashicorp/terraform/internal/stacks/stackconfig/stackconfigtypes"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// ProviderConfig represents a single "provider" block in a stack configuration.
@ -147,9 +148,34 @@ func (p *ProviderConfig) ResolveExpressionReference(ctx context.Context, ref sta
repetition.EachKey = cty.UnknownVal(cty.String).RefineNotNull()
repetition.EachValue = cty.DynamicVal
}
return p.main.
ret, diags := p.main.
mustStackConfig(ctx, p.Addr().Stack).
resolveExpressionReference(ctx, ref, repetition, nil)
resolveExpressionReference(ctx, ref, nil, repetition)
if _, ok := ret.(*ProviderConfig); ok {
// We can't reference other providers from anywhere inside a provider
// configuration block.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: fmt.Sprintf("The object %s is not in scope at this location.", ref.Target.String()),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
}
return ret, diags
}
// ExprReferenceValue implements Referenceable.
func (p *ProviderConfig) ExprReferenceValue(ctx context.Context, phase EvalPhase) cty.Value {
// We don't say anything about the contents of a provider during the
// static evaluation phase. We still return the type of the provider so
// we can use it to verify type constraints, but we don't return any
// actual values.
if p.config.ForEach != nil {
return cty.UnknownVal(cty.Map(p.InstRefValueType(ctx)))
}
return cty.UnknownVal(p.InstRefValueType(ctx))
}
var providerInstanceRefTypes = map[addrs.Provider]cty.Type{}

@ -8,14 +8,15 @@ import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// StackCallConfig represents a "stack" block in a stack configuration,
@ -320,9 +321,22 @@ func (s *StackCallConfig) ResolveExpressionReference(ctx context.Context, ref st
repetition.EachKey = cty.UnknownVal(cty.String).RefineNotNull()
repetition.EachValue = cty.DynamicVal
}
return s.main.
ret, diags := s.main.
mustStackConfig(ctx, s.Addr().Stack).
resolveExpressionReference(ctx, ref, instances.RepetitionData{}, nil)
resolveExpressionReference(ctx, ref, nil, repetition)
if _, ok := ret.(*ProviderConfig); ok {
// We can't reference other providers from anywhere inside an embedded
// stack call - they should define their own providers.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: fmt.Sprintf("The object %s is not in scope at this location.", ref.Target.String()),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
}
return ret, diags
}
func (s *StackCallConfig) checkValid(ctx context.Context, phase EvalPhase) tfdiags.Diagnostics {

@ -224,6 +224,42 @@ func (s *StackConfig) Provider(ctx context.Context, addr stackaddrs.ProviderConf
return ret
}
// ProviderByLocalAddr returns a [ProviderConfig] representing the provider
// configuration block within the stack configuration that matches the given
// local address, or nil if there is no such declaration.
//
// This is equivalent to calling [Provider] just using a reference address
// instead of a config address.
func (s *StackConfig) ProviderByLocalAddr(ctx context.Context, localAddr stackaddrs.ProviderConfigRef) *ProviderConfig {
s.mu.Lock()
defer s.mu.Unlock()
provider, ok := s.config.Stack.RequiredProviders.ProviderForLocalName(localAddr.ProviderLocalName)
if !ok {
return nil
}
addr := stackaddrs.ProviderConfig{
Provider: provider,
Name: localAddr.Name,
}
ret, ok := s.providers[addr]
if !ok {
configAddr := addrs.LocalProviderConfig{
LocalName: localAddr.ProviderLocalName,
Alias: localAddr.Name,
}
cfg, ok := s.config.Stack.ProviderConfigs[configAddr]
if !ok {
return nil
}
cfgAddr := stackaddrs.Config(s.Addr(), addr)
ret = newProviderConfig(s.main, cfgAddr, cfg)
s.providers[addr] = ret
}
return ret
}
// ProviderLocalName returns the local name used for the given provider
// in this particular stack configuration, based on the declarations in
// the required_providers configuration block.
@ -315,13 +351,13 @@ func (s *StackConfig) Components(ctx context.Context) map[stackaddrs.Component]*
// global scope for evaluation within an unexpanded stack during the validate
// phase.
func (s *StackConfig) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) {
return s.resolveExpressionReference(ctx, ref, instances.RepetitionData{}, nil)
return s.resolveExpressionReference(ctx, ref, nil, instances.RepetitionData{})
}
// resolveExpressionReference is the shared implementation of various
// validation-time ResolveExpressionReference methods, factoring out all
// of the common parts into one place.
func (s *StackConfig) resolveExpressionReference(ctx context.Context, ref stackaddrs.Reference, repetition instances.RepetitionData, selfAddr stackaddrs.Referenceable) (Referenceable, tfdiags.Diagnostics) {
func (s *StackConfig) resolveExpressionReference(ctx context.Context, ref stackaddrs.Reference, selfAddr stackaddrs.Referenceable, repetition instances.RepetitionData) (Referenceable, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// "Test-only globals" is a special affordance we have only when running
@ -371,6 +407,72 @@ func (s *StackConfig) resolveExpressionReference(ctx context.Context, ref stacka
return nil, diags
}
return ret, diags
case stackaddrs.ProviderConfigRef:
ret := s.ProviderByLocalAddr(ctx, addr)
if ret == nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to undeclared provider configuration",
Detail: fmt.Sprintf("There is no provider %q %q block declared this stack.", addr.ProviderLocalName, addr.Name),
Subject: ref.SourceRange.ToHCL().Ptr(),
})
return nil, diags
}
return ret, diags
case stackaddrs.ContextualRef:
switch addr {
case stackaddrs.EachKey:
if repetition.EachKey == cty.NilVal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid 'each' reference",
Detail: "The special symbol 'each' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'for_each' argument.",
Subject: ref.SourceRange.ToHCL().Ptr(),
})
return nil, diags
}
return JustValue{repetition.EachKey}, diags
case stackaddrs.EachValue:
if repetition.EachValue == cty.NilVal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid 'each' reference",
Detail: "The special symbol 'each' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'for_each' argument.",
Subject: ref.SourceRange.ToHCL().Ptr(),
})
return nil, diags
}
return JustValue{repetition.EachValue}, diags
case stackaddrs.CountIndex:
if repetition.CountIndex == cty.NilVal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid 'count' reference",
Detail: "The special symbol 'count' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'count' argument.",
Subject: ref.SourceRange.ToHCL().Ptr(),
})
return nil, diags
}
return JustValue{repetition.CountIndex}, diags
case stackaddrs.Self:
if selfAddr != nil {
// We'll just pretend the reference was to whatever "self"
// is referring to, then.
ref.Target = selfAddr
return s.resolveExpressionReference(ctx, ref, nil, repetition)
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid 'self' reference",
Detail: "The special symbol 'self' is not defined in this location.",
Context: ref.SourceRange.ToHCL().Ptr(),
})
return nil, diags
}
default:
// The above should be exhaustive for all defined values of this type.
panic(fmt.Sprintf("unsupported ContextualRef %#v", addr))
}
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,

Loading…
Cancel
Save