mirror of https://github.com/hashicorp/terraform
Each declared provider configuration is now started up and configured during planning. We don't actually do anything with them yet other than shut them down again once the plan is complete, but we'll improve on that in subsequent commits.pull/34738/head
parent
48add21996
commit
3dc3781904
@ -1,3 +1,5 @@
|
||||
package stackeval
|
||||
|
||||
type ApplyOpts struct{}
|
||||
type ApplyOpts struct {
|
||||
ProviderFactories ProviderFactories
|
||||
}
|
||||
|
||||
@ -0,0 +1,298 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Provider represents a provider configuration in a particular stack config.
|
||||
type Provider struct {
|
||||
addr stackaddrs.AbsProviderConfig
|
||||
config *stackconfig.ProviderConfig
|
||||
|
||||
main *Main
|
||||
|
||||
forEachValue perEvalPhase[promising.Once[withDiagnostics[cty.Value]]]
|
||||
instances perEvalPhase[promising.Once[withDiagnostics[map[addrs.InstanceKey]*ProviderInstance]]]
|
||||
}
|
||||
|
||||
func newProvider(main *Main, addr stackaddrs.AbsProviderConfig, config *stackconfig.ProviderConfig) *Provider {
|
||||
return &Provider{
|
||||
addr: addr,
|
||||
config: config,
|
||||
main: main,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Addr() stackaddrs.AbsProviderConfig {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *Provider) Declaration(ctx context.Context) *stackconfig.ProviderConfig {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *Provider) Config(ctx context.Context) *ProviderConfig {
|
||||
configAddr := stackaddrs.ConfigForAbs(p.Addr())
|
||||
stackConfig := p.main.StackConfig(ctx, configAddr.Stack)
|
||||
if stackConfig == nil {
|
||||
return nil
|
||||
}
|
||||
return stackConfig.Provider(ctx, configAddr.Item)
|
||||
}
|
||||
|
||||
func (p *Provider) ProviderType(ctx context.Context) *ProviderType {
|
||||
return p.main.ProviderType(ctx, p.Addr().Item.Provider)
|
||||
}
|
||||
|
||||
func (p *Provider) Stack(ctx context.Context) *Stack {
|
||||
// Unchecked because we should've been constructed from the same stack
|
||||
// object we're about to return, and so this should be valid unless
|
||||
// the original construction was from an invalid object itself.
|
||||
return p.main.StackUnchecked(ctx, p.Addr().Stack)
|
||||
}
|
||||
|
||||
// InstRefValueType returns the type of any values that represent references to
|
||||
// instances of this provider configuration.
|
||||
//
|
||||
// All configurations for the same provider share the same type.
|
||||
func (p *Provider) InstRefValueType(ctx context.Context) cty.Type {
|
||||
decl := p.Declaration(ctx)
|
||||
return providerInstanceRefType(decl.ProviderAddr)
|
||||
}
|
||||
|
||||
// ForEachValue returns the result of evaluating the "for_each" expression
|
||||
// for this provider configuration, with the following exceptions:
|
||||
// - If the provider config doesn't use "for_each" at all, returns [cty.NilVal].
|
||||
// - If the for_each expression is present but too invalid to evaluate,
|
||||
// returns [cty.DynamicVal] to represent that the for_each value cannot
|
||||
// be determined.
|
||||
//
|
||||
// A present and valid "for_each" expression produces a result that's
|
||||
// guaranteed to be:
|
||||
// - Either a set of strings, a map of any element type, or an object type
|
||||
// - Known and not null (only the top-level value)
|
||||
// - Not sensitive (only the top-level value)
|
||||
func (p *Provider) ForEachValue(ctx context.Context, phase EvalPhase) cty.Value {
|
||||
ret, _ := p.CheckForEachValue(ctx, phase)
|
||||
return ret
|
||||
}
|
||||
|
||||
// CheckForEachValue evaluates the "for_each" expression if present, validates
|
||||
// that its value is valid, and then returns that value.
|
||||
//
|
||||
// If this call does not use "for_each" then this immediately returns cty.NilVal
|
||||
// representing the absense of the value.
|
||||
//
|
||||
// If the diagnostics does not include errors and the result is not cty.NilVal
|
||||
// then callers can assume that the result value will be:
|
||||
// - Either a set of strings, a map of any element type, or an object type
|
||||
// - Known and not null (except for nested map/object element values)
|
||||
// - Not sensitive (only the top-level value)
|
||||
//
|
||||
// If the diagnostics _does_ include errors then the result might be
|
||||
// [cty.DynamicVal], which represents that the for_each expression was so invalid
|
||||
// that we cannot know the for_each value.
|
||||
func (p *Provider) CheckForEachValue(ctx context.Context, phase EvalPhase) (cty.Value, tfdiags.Diagnostics) {
|
||||
val, diags := doOnceWithDiags(
|
||||
ctx, p.forEachValue.For(phase), p.main,
|
||||
func(ctx context.Context) (cty.Value, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
cfg := p.Declaration(ctx)
|
||||
|
||||
switch {
|
||||
|
||||
case cfg.ForEach != nil:
|
||||
result, moreDiags := evaluateForEachExpr(ctx, cfg.ForEach, phase, p.Stack(ctx))
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
|
||||
if !result.Value.IsKnown() {
|
||||
// FIXME: We should somehow allow this and emit a
|
||||
// "deferred change" representing all of the as-yet-unknown
|
||||
// instances of this call and everything beneath it.
|
||||
diags = diags.Append(result.Diagnostic(
|
||||
tfdiags.Error,
|
||||
"Invalid for_each value",
|
||||
"The for_each value must not be derived from values that will be determined only during the apply phase.",
|
||||
))
|
||||
}
|
||||
|
||||
return result.Value, diags
|
||||
|
||||
default:
|
||||
// This stack config doesn't use for_each at all
|
||||
return cty.NilVal, diags
|
||||
}
|
||||
},
|
||||
)
|
||||
if val == cty.NilVal && diags.HasErrors() {
|
||||
// We use cty.DynamicVal as the placeholder for an invalid for_each,
|
||||
// to represent "unknown for_each value" as distinct from "no for_each
|
||||
// expression at all".
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
return val, diags
|
||||
}
|
||||
|
||||
// Instances returns all of the instances of the provider config known to be
|
||||
// declared by the configuration.
|
||||
//
|
||||
// Calcluating this involves evaluating the call's for_each expression if any,
|
||||
// and so this call may block on evaluation of other objects in the
|
||||
// configuration.
|
||||
//
|
||||
// If the configuration has an invalid definition of the instances then the
|
||||
// result will be nil. Callers that need to distinguish between invalid
|
||||
// definitions and valid definitions of zero instances can rely on the
|
||||
// result being a non-nil zero-length map in the latter case.
|
||||
//
|
||||
// This function doesn't return any diagnostics describing ways in which the
|
||||
// for_each expression is invalid because we assume that the main plan walk
|
||||
// will visit the stack call directly and ask it to check itself, and that
|
||||
// call will be the one responsible for returning any diagnostics.
|
||||
func (p *Provider) Instances(ctx context.Context, phase EvalPhase) map[addrs.InstanceKey]*ProviderInstance {
|
||||
ret, _ := p.CheckInstances(ctx, phase)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *Provider) CheckInstances(ctx context.Context, phase EvalPhase) (map[addrs.InstanceKey]*ProviderInstance, tfdiags.Diagnostics) {
|
||||
return doOnceWithDiags(
|
||||
ctx, p.instances.For(phase), p.main,
|
||||
func(ctx context.Context) (map[addrs.InstanceKey]*ProviderInstance, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
forEachVal := p.ForEachValue(ctx, phase)
|
||||
|
||||
switch {
|
||||
case forEachVal == cty.NilVal:
|
||||
// No for_each expression at all, then. We have exactly one instance
|
||||
// without an instance key and with no repetition data.
|
||||
return map[addrs.InstanceKey]*ProviderInstance{
|
||||
addrs.NoKey: newProviderInstance(p, addrs.NoKey, instances.RepetitionData{
|
||||
// no repetition symbols available in this case
|
||||
}),
|
||||
}, diags
|
||||
|
||||
case !forEachVal.IsKnown():
|
||||
// The for_each expression is too invalid for us to be able to
|
||||
// know which instances exist. A totally nil map (as opposed to a
|
||||
// non-nil map of length zero) signals that situation.
|
||||
return nil, diags
|
||||
|
||||
default:
|
||||
// Otherwise we should be able to assume the value is valid per the
|
||||
// definition of [CheckForEachValue]. The following will panic if
|
||||
// that other function doesn't satisfy its documented contract;
|
||||
// if that happens, prefer to correct [CheckForEachValue] than to
|
||||
// add additional complexity here.
|
||||
|
||||
// NOTE: We MUST return a non-nil map from every return path under
|
||||
// this case, even if there are zero elements in it, because a nil map
|
||||
// represents an _invalid_ for_each expression (handled above).
|
||||
|
||||
ty := forEachVal.Type()
|
||||
switch {
|
||||
case ty.IsObjectType() || ty.IsMapType():
|
||||
elems := forEachVal.AsValueMap()
|
||||
ret := make(map[addrs.InstanceKey]*ProviderInstance, len(elems))
|
||||
for k, v := range elems {
|
||||
ik := addrs.StringKey(k)
|
||||
ret[ik] = newProviderInstance(p, ik, instances.RepetitionData{
|
||||
EachKey: cty.StringVal(k),
|
||||
EachValue: v,
|
||||
})
|
||||
}
|
||||
return ret, diags
|
||||
|
||||
case ty.IsSetType():
|
||||
// ForEachValue should have already guaranteed us a set of strings,
|
||||
// but we'll check again here just so we can panic more intellgibly
|
||||
// if that function is buggy.
|
||||
if ty.ElementType() != cty.String {
|
||||
panic(fmt.Sprintf("ForEachValue returned invalid result %#v", forEachVal))
|
||||
}
|
||||
|
||||
elems := forEachVal.AsValueSlice()
|
||||
ret := make(map[addrs.InstanceKey]*ProviderInstance, len(elems))
|
||||
for _, sv := range elems {
|
||||
k := addrs.StringKey(sv.AsString())
|
||||
ret[k] = newProviderInstance(p, k, instances.RepetitionData{
|
||||
EachKey: sv,
|
||||
EachValue: sv,
|
||||
})
|
||||
}
|
||||
return ret, diags
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("ForEachValue returned invalid result %#v", forEachVal))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ExprReferenceValue implements Referenceable, returning a value containing
|
||||
// one or more values that act as references to instances of the provider.
|
||||
func (p *Provider) ExprReferenceValue(ctx context.Context, phase EvalPhase) cty.Value {
|
||||
decl := p.Declaration(ctx)
|
||||
insts := p.Instances(ctx, phase)
|
||||
refType := p.InstRefValueType(ctx)
|
||||
|
||||
switch {
|
||||
case decl.ForEach != nil:
|
||||
if insts == nil {
|
||||
return cty.UnknownVal(cty.Map(refType))
|
||||
}
|
||||
elems := make(map[string]cty.Value, len(insts))
|
||||
for instKey := range insts {
|
||||
k, ok := instKey.(addrs.StringKey)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("provider config with for_each has invalid instance key of type %T", instKey))
|
||||
}
|
||||
elems[string(k)] = cty.CapsuleVal(refType, &stackaddrs.ProviderConfigInstance{
|
||||
ProviderConfig: p.Addr().Item,
|
||||
Key: instKey,
|
||||
})
|
||||
}
|
||||
return cty.MapVal(elems)
|
||||
default:
|
||||
if insts == nil {
|
||||
return cty.UnknownVal(refType)
|
||||
}
|
||||
return cty.CapsuleVal(refType, &stackaddrs.ProviderConfigInstance{
|
||||
ProviderConfig: p.Addr().Item,
|
||||
Key: addrs.NoKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// PlanChanges implements Plannable.
|
||||
func (p *Provider) PlanChanges(ctx context.Context) ([]stackplan.PlannedChange, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
_, moreDiags := p.CheckForEachValue(ctx, PlanPhase)
|
||||
diags = diags.Append(moreDiags)
|
||||
_, moreDiags = p.CheckInstances(ctx, PlanPhase)
|
||||
diags = diags.Append(moreDiags)
|
||||
// Everything else is instance-specific and so the plan walk driver must
|
||||
// call p.Instances and ask each instance to plan itself.
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// tracingName implements Plannable.
|
||||
func (p *Provider) tracingName() string {
|
||||
return p.Addr().String()
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"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"
|
||||
"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.
|
||||
type ProviderConfig struct {
|
||||
addr stackaddrs.ConfigProviderConfig
|
||||
config *stackconfig.ProviderConfig
|
||||
|
||||
main *Main
|
||||
|
||||
providerArgs promising.Once[withDiagnostics[cty.Value]]
|
||||
}
|
||||
|
||||
func newProviderConfig(main *Main, addr stackaddrs.ConfigProviderConfig, config *stackconfig.ProviderConfig) *ProviderConfig {
|
||||
return &ProviderConfig{
|
||||
addr: addr,
|
||||
config: config,
|
||||
main: main,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) Addr() stackaddrs.ConfigProviderConfig {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) Declaration(ctx context.Context) *stackconfig.ProviderConfig {
|
||||
return p.config
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) ProviderType(ctx context.Context) *ProviderType {
|
||||
return p.main.ProviderType(ctx, p.Addr().Item.Provider)
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) InstRefValueType(ctx context.Context) cty.Type {
|
||||
decl := p.Declaration(ctx)
|
||||
return providerInstanceRefType(decl.ProviderAddr)
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) ProviderArgsDecoderSpec(ctx context.Context) (hcldec.Spec, error) {
|
||||
providerType := p.ProviderType(ctx)
|
||||
schema, err := providerType.Schema(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if schema.Provider.Block == nil {
|
||||
return hcldec.ObjectSpec{}, nil
|
||||
}
|
||||
return schema.Provider.Block.DecoderSpec(), nil
|
||||
}
|
||||
|
||||
// ProviderArgs returns an object value representing an approximation of all
|
||||
// provider instances declared by this provider configuration, or
|
||||
// an unknown value (possibly [cty.DynamicVal]) if the configuration is too
|
||||
// invalid to produce any answer at all.
|
||||
func (p *ProviderConfig) ProviderArgs(ctx context.Context) cty.Value {
|
||||
v, _ := p.CheckProviderArgs(ctx)
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) CheckProviderArgs(ctx context.Context) (cty.Value, tfdiags.Diagnostics) {
|
||||
return doOnceWithDiags(
|
||||
ctx, &p.providerArgs, p.main,
|
||||
func(ctx context.Context) (cty.Value, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
providerType := p.ProviderType(ctx)
|
||||
decl := p.Declaration(ctx)
|
||||
spec, err := p.ProviderArgsDecoderSpec(ctx)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to read provider schema",
|
||||
Detail: fmt.Sprintf(
|
||||
"Error while reading the schema for %q: %s.",
|
||||
providerType.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
|
||||
client, err := providerType.UnconfiguredClient(ctx)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to initialize provider",
|
||||
Detail: fmt.Sprintf(
|
||||
"Error initializing %q to validate %s: %s.",
|
||||
providerType.Addr(), p.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
return cty.UnknownVal(hcldec.ImpliedType(spec)), diags
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
configVal, moreDiags := EvalBody(ctx, decl.Config, spec, ValidatePhase, p)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
return cty.UnknownVal(hcldec.ImpliedType(spec)), diags
|
||||
}
|
||||
validateResp := client.ValidateProviderConfig(providers.ValidateProviderConfigRequest{
|
||||
Config: configVal,
|
||||
})
|
||||
diags = diags.Append(validateResp.Diagnostics)
|
||||
if validateResp.Diagnostics.HasErrors() {
|
||||
return cty.UnknownVal(hcldec.ImpliedType(spec)), diags
|
||||
}
|
||||
|
||||
return configVal, diags
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ResolveExpressionReference implements ExpressionScope for the purposes
|
||||
// of validating the static provider configuration before it has been expanded
|
||||
// into multiple instances.
|
||||
func (p *ProviderConfig) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) {
|
||||
repetition := instances.RepetitionData{}
|
||||
if p.Declaration(ctx).ForEach != nil {
|
||||
// We're producing an approximation across all eventual instances
|
||||
// of this call, so we'll set each.key and each.value to unknown
|
||||
// values.
|
||||
repetition.EachKey = cty.UnknownVal(cty.String).RefineNotNull()
|
||||
repetition.EachValue = cty.DynamicVal
|
||||
}
|
||||
return p.main.
|
||||
mustStackConfig(ctx, p.Addr().Stack).
|
||||
resolveExpressionReference(ctx, ref, repetition, nil)
|
||||
}
|
||||
|
||||
var providerInstanceRefTypes = map[addrs.Provider]cty.Type{}
|
||||
var providerInstanceRefTypesMu sync.Mutex
|
||||
|
||||
// providerInstanceRefType returns the singleton cty capsule type for a given
|
||||
// provider source address, creating a new type if a particular source address
|
||||
// was not requested before.
|
||||
func providerInstanceRefType(sourceAddr addrs.Provider) cty.Type {
|
||||
providerInstanceRefTypesMu.Lock()
|
||||
defer providerInstanceRefTypesMu.Unlock()
|
||||
|
||||
ret, ok := providerInstanceRefTypes[sourceAddr]
|
||||
if ok {
|
||||
return ret
|
||||
}
|
||||
providerInstanceRefTypes[sourceAddr] = stackconfigtypes.ProviderConfigType(sourceAddr)
|
||||
return providerInstanceRefTypes[sourceAddr]
|
||||
}
|
||||
|
||||
// Validate implements Validatable.
|
||||
func (p *ProviderConfig) Validate(ctx context.Context) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// TODO: Actually validate the configuration against the schema.
|
||||
// Currently we're doing that only during the plan phase, but
|
||||
// it would be better to catch statically-detectable problems
|
||||
// earlier and only once per provider block, rather than repeatedly
|
||||
// for each instance of a provider.
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// tracingName implements Validatable.
|
||||
func (p *ProviderConfig) tracingName() string {
|
||||
return p.Addr().String()
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
)
|
||||
|
||||
// ProviderFactories is a collection of factory functions for starting new
|
||||
// instances of various providers.
|
||||
type ProviderFactories map[addrs.Provider]providers.Factory
|
||||
|
||||
func (pf ProviderFactories) ProviderAvailable(providerAddr addrs.Provider) bool {
|
||||
_, available := pf[providerAddr]
|
||||
return available
|
||||
}
|
||||
|
||||
// NewUnconfiguredClient launches a new instance of the requested provider,
|
||||
// if available, and returns it in an unconfigured state.
|
||||
//
|
||||
// Callers that need a _configured_ provider can then call
|
||||
// [providers.Interface.Configure] on the result to configure it, making it
|
||||
// ready for the majority of operations that require a configured provider.
|
||||
func (pf ProviderFactories) NewUnconfiguredClient(providerAddr addrs.Provider) (providers.Interface, error) {
|
||||
f, ok := pf[providerAddr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("provider is not available in this execution context")
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
// rcProviderClient is a reference-counting abstraction to help with sharing
|
||||
// instances of providers between multiple callers and shutting them down
|
||||
// once all callers have finished with them.
|
||||
//
|
||||
// It encapsulates the problem of tracking the number of callers, instantiating
|
||||
// a provider when necessary, and closing that provider once there are no
|
||||
// active references remaining.
|
||||
type rcProviderClient struct {
|
||||
// Factory is the function to use to create a new instance of the provider
|
||||
// when needed.
|
||||
//
|
||||
// This function should perform all of the steps that ought to happen
|
||||
// exactly once before the provider becomes useful to its possibly-many
|
||||
// constituents. In particular, if multiple callers are hoping to share
|
||||
// a single _configured_ provider then the factory function must be the
|
||||
// one to configure it, so that the callers don't all need to race to
|
||||
// be the one to do the one-time configuration themselves.
|
||||
Factory providers.Factory
|
||||
|
||||
// must hold mu when interacting with the other fields below
|
||||
mu sync.Mutex
|
||||
|
||||
callers int
|
||||
client providers.Interface
|
||||
}
|
||||
|
||||
func (rcpc *rcProviderClient) Borrow(ctx context.Context) (providers.Interface, error) {
|
||||
rcpc.mu.Lock()
|
||||
defer rcpc.mu.Unlock()
|
||||
|
||||
rcpc.callers++
|
||||
|
||||
var client providers.Interface
|
||||
if rcpc.client != nil {
|
||||
client = rcpc.client
|
||||
} else {
|
||||
var err error
|
||||
client, err = rcpc.Factory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// each caller gets its own "closed" flag captured into its "close" closure,
|
||||
// so we can silently ignore duplicate calls to "close".
|
||||
var closed atomic.Bool
|
||||
close := func() error {
|
||||
if !closed.CompareAndSwap(false, true) {
|
||||
// We silently ignore redundant calls to close.
|
||||
// We intentionally don't panic here because we want to encourage
|
||||
// callers to call Close liberally in every possible return
|
||||
// path, rather than working hard to ensure only one call and
|
||||
// potentially ending up not calling it at all in some edge cases.
|
||||
return nil
|
||||
}
|
||||
|
||||
// To reduce churn of provider clients when different callers are
|
||||
// taking turns to use them, we'll decrement the caller count immediately
|
||||
// but pause briefly before we check if the count has reached zero
|
||||
// so that another caller has a chance to acquire the same client
|
||||
// and thus avoid the overhead of starting it up again.
|
||||
rcpc.mu.Lock()
|
||||
rcpc.callers--
|
||||
rcpc.mu.Unlock()
|
||||
|
||||
// We'll wait either for our anti-churn delay or until the context is
|
||||
// cancelled before we test whether we should shut down the client,
|
||||
// but that's an implementation detail the caller doesn't need to know
|
||||
// about and so we'll deal with that in a separate goroutine.
|
||||
go func() {
|
||||
// This time selection is essentially arbitrary; we're aiming to
|
||||
// find a happy compromise between using more RAM by keeping a
|
||||
// provider active a little longer vs. spending less time on
|
||||
// startup and shutdown overhead as usage of a provider passes
|
||||
// between different callers that are not necessarily synchronized
|
||||
// with one another.
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
rcpc.mu.Lock()
|
||||
if rcpc.callers > 0 {
|
||||
rcpc.mu.Unlock()
|
||||
return // someone else requested the client in the meantime
|
||||
}
|
||||
if rcpc.client == nil {
|
||||
rcpc.mu.Unlock()
|
||||
return // someone else already closed the client in the meantime
|
||||
}
|
||||
|
||||
// We'll take our own private copy of the client and nil out the
|
||||
// shared one so that we don't need to hold the mutex for the
|
||||
// entire (possibly-time-consuming) shutdown procedure.
|
||||
oldClient := rcpc.client
|
||||
rcpc.client = nil
|
||||
rcpc.mu.Unlock()
|
||||
// NOTE: MUST NOT access p.client or p.callerCount after this point
|
||||
|
||||
err := oldClient.Close()
|
||||
if err != nil {
|
||||
// We don't really have any way to properly handle an error here,
|
||||
// but "Close" is typically just sending the child process a
|
||||
// kill signal and so if that fails there wouldn't be much we could
|
||||
// do to recover anyway.
|
||||
log.Printf("[ERROR] failed to shut down provider instance: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// To honor the providers.Interface abstraction while still allowing
|
||||
// multiple callers to share a single client we wrap the real client
|
||||
// to intercept the Close method and treat it as decrementing the
|
||||
// reference count rather than closing the client directly.
|
||||
return providerClose{
|
||||
Interface: client,
|
||||
close: close,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// providerClose is an implementation of providers.Interface that intercepts
|
||||
// the "Close" operation and diverts it into a callback function. We use this
|
||||
// so that multiple callers can share a single client in a coordinated way,
|
||||
// so they can all call Close as normal without interfering with one another.
|
||||
type providerClose struct {
|
||||
providers.Interface
|
||||
close func() error
|
||||
}
|
||||
|
||||
var _ providers.Interface = providerClose{}
|
||||
|
||||
func (p providerClose) Close() error {
|
||||
return p.close()
|
||||
}
|
||||
@ -0,0 +1,461 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"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"
|
||||
"github.com/hashicorp/terraform/internal/stacks/stackplan"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ProviderInstance represents one instance of a provider.
|
||||
//
|
||||
// A provider configuration block with the for_each argument appears as a
|
||||
// single [ProviderConfig], then one [Provider] for each stack config instance
|
||||
// the provider belongs to, and then one [ProviderInstance] for each
|
||||
// element of for_each for each [Provider].
|
||||
type ProviderInstance struct {
|
||||
provider *Provider
|
||||
key addrs.InstanceKey
|
||||
repetition instances.RepetitionData
|
||||
|
||||
main *Main
|
||||
|
||||
providerArgs perEvalPhase[promising.Once[withDiagnostics[cty.Value]]]
|
||||
client perEvalPhase[promising.Once[withDiagnostics[providers.Interface]]]
|
||||
}
|
||||
|
||||
func newProviderInstance(provider *Provider, key addrs.InstanceKey, repetition instances.RepetitionData) *ProviderInstance {
|
||||
return &ProviderInstance{
|
||||
provider: provider,
|
||||
key: key,
|
||||
main: provider.main,
|
||||
repetition: repetition,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProviderInstance) Addr() stackaddrs.AbsProviderConfigInstance {
|
||||
providerAddr := p.provider.Addr()
|
||||
return stackaddrs.AbsProviderConfigInstance{
|
||||
Stack: providerAddr.Stack,
|
||||
Item: stackaddrs.ProviderConfigInstance{
|
||||
ProviderConfig: providerAddr.Item,
|
||||
Key: p.key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProviderInstance) ProviderType(ctx context.Context) *ProviderType {
|
||||
return p.main.ProviderType(ctx, p.Addr().Item.ProviderConfig.Provider)
|
||||
}
|
||||
|
||||
func (p *ProviderInstance) ProviderArgsDecoderSpec(ctx context.Context) (hcldec.Spec, error) {
|
||||
return p.provider.Config(ctx).ProviderArgsDecoderSpec(ctx)
|
||||
}
|
||||
|
||||
// ProviderArgs returns an object value representing an approximation of all
|
||||
// provider instances declared by this provider configuration, or
|
||||
// an unknown value (possibly [cty.DynamicVal]) if the configuration is too
|
||||
// invalid to produce any answer at all.
|
||||
func (p *ProviderInstance) ProviderArgs(ctx context.Context, phase EvalPhase) cty.Value {
|
||||
v, _ := p.CheckProviderArgs(ctx, phase)
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *ProviderInstance) CheckProviderArgs(ctx context.Context, phase EvalPhase) (cty.Value, tfdiags.Diagnostics) {
|
||||
return doOnceWithDiags(
|
||||
ctx, p.providerArgs.For(phase), p.main,
|
||||
func(ctx context.Context) (cty.Value, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
providerType := p.ProviderType(ctx)
|
||||
decl := p.provider.Declaration(ctx)
|
||||
spec, err := p.ProviderArgsDecoderSpec(ctx)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to read provider schema",
|
||||
Detail: fmt.Sprintf(
|
||||
"Error while reading the schema for %q: %s.",
|
||||
providerType.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
|
||||
configVal, moreDiags := EvalBody(ctx, decl.Config, spec, phase, p)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
return cty.UnknownVal(hcldec.ImpliedType(spec)), diags
|
||||
}
|
||||
|
||||
unconfClient, err := providerType.UnconfiguredClient(ctx)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to start provider plugin",
|
||||
Detail: fmt.Sprintf(
|
||||
"Error while instantiating %q: %s.",
|
||||
providerType.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
defer unconfClient.Close()
|
||||
validateResp := unconfClient.ValidateProviderConfig(providers.ValidateProviderConfigRequest{
|
||||
Config: configVal,
|
||||
})
|
||||
diags = diags.Append(validateResp.Diagnostics)
|
||||
if validateResp.Diagnostics.HasErrors() {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
|
||||
return configVal, diags
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Client returns a client object for the provider instance, already configured
|
||||
// per the provider configuration arguments and ready to use.
|
||||
//
|
||||
// If the configured arguments are invalid then this might return a stub
|
||||
// provider client that implements all methods either as silent no-ops or as
|
||||
// returning error diagnostics, so callers can just treat the returned client
|
||||
// as always valid.
|
||||
//
|
||||
// Callers must call Close on the returned client once they have finished using
|
||||
// the client.
|
||||
func (p *ProviderInstance) Client(ctx context.Context, phase EvalPhase) providers.Interface {
|
||||
ret, _ := p.CheckClient(ctx, phase)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *ProviderInstance) CheckClient(ctx context.Context, phase EvalPhase) (providers.Interface, tfdiags.Diagnostics) {
|
||||
return doOnceWithDiags(
|
||||
ctx, p.client.For(phase), p.main,
|
||||
func(ctx context.Context) (providers.Interface, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if p.repetition.EachKey != cty.NilVal && !p.repetition.EachKey.IsKnown() {
|
||||
// If we're a placeholder standing in for all instances of
|
||||
// a provider block whose for_each is unknown then we
|
||||
// can't configure.
|
||||
return stubConfiguredProvider{unknown: true}, diags
|
||||
}
|
||||
if p.repetition.CountIndex != cty.NilVal && !p.repetition.CountIndex.IsKnown() {
|
||||
// If we're a placeholder standing in for all instances of
|
||||
// a provider block whose count is unknown then we
|
||||
// can't configure.
|
||||
return stubConfiguredProvider{unknown: true}, diags
|
||||
}
|
||||
|
||||
args := p.ProviderArgs(ctx, phase)
|
||||
if !args.IsKnown() {
|
||||
// If we don't know the provider configuration at all then
|
||||
// we'll just immediately return a stub client, since
|
||||
// no provider can accept a wholly-unknown configuration.
|
||||
// (Known objects with unknown attribute values inside are
|
||||
// okay to try and so don't return immediately here.)
|
||||
return stubConfiguredProvider{unknown: true}, diags
|
||||
}
|
||||
|
||||
providerType := p.ProviderType(ctx)
|
||||
decl := p.provider.Declaration(ctx)
|
||||
|
||||
client, err := p.main.ProviderFactories().NewUnconfiguredClient(providerType.Addr())
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to start provider plugin",
|
||||
Detail: fmt.Sprintf(
|
||||
"Could not create an instance of %s for %s: %s.",
|
||||
providerType.Addr(), p.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
return stubConfiguredProvider{unknown: false}, diags
|
||||
}
|
||||
|
||||
// If this provider is implemented as a separate plugin then we
|
||||
// must terminate its child process once evaluation is complete.
|
||||
p.main.RegisterCleanup(func(ctx context.Context) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
err := client.Close()
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to terminate provider plugin",
|
||||
Detail: fmt.Sprintf(
|
||||
"Error closing the instance of %s for %s: %s.",
|
||||
providerType.Addr(), p.Addr(), err,
|
||||
),
|
||||
Subject: decl.DeclRange.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
return diags
|
||||
})
|
||||
|
||||
resp := client.ConfigureProvider(providers.ConfigureProviderRequest{
|
||||
TerraformVersion: version.SemVer.String(),
|
||||
Config: args,
|
||||
})
|
||||
diags = diags.Append(resp.Diagnostics)
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
// If the provider didn't configure successfully then it won't
|
||||
// meet the expectations of our callers and so we'll return a
|
||||
// stub instead. (The real provider stays running until it
|
||||
// gets cleaned up by the cleanup function above, despite being
|
||||
// inaccessible to the caller.)
|
||||
return stubConfiguredProvider{unknown: false}, diags
|
||||
}
|
||||
|
||||
return providerClose{
|
||||
close: func() error {
|
||||
// We just totally ignore close for configured providers,
|
||||
// because we'll deal with them in the cleanup phase instead.
|
||||
return nil
|
||||
},
|
||||
Interface: client,
|
||||
}, diags
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ResolveExpressionReference implements ExpressionScope for expressions other
|
||||
// than the for_each argument inside a provider block, which get evaluated
|
||||
// once per provider instance.
|
||||
func (p *ProviderInstance) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) {
|
||||
stack := p.provider.Stack(ctx)
|
||||
return stack.resolveExpressionReference(ctx, ref, nil, p.repetition)
|
||||
}
|
||||
|
||||
// PlanChanges implements Plannable.
|
||||
func (p *ProviderInstance) PlanChanges(ctx context.Context) ([]stackplan.PlannedChange, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
_, moreDiags := p.CheckProviderArgs(ctx, PlanPhase)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
// NOTE: CheckClient starts and configures the provider as a side-effect.
|
||||
// If this is a plugin-based provider then the plugin process will stay
|
||||
// running for the remainder of the planning phase.
|
||||
_, moreDiags = p.CheckClient(ctx, PlanPhase)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// tracingName implements Plannable.
|
||||
func (p *ProviderInstance) tracingName() string {
|
||||
return p.Addr().String()
|
||||
}
|
||||
|
||||
// stubConfiguredProvider is a placeholder provider used when ConfigureProvider
|
||||
// on a real provider fails, so that callers can still receieve a usable client
|
||||
// that will just produce placeholder values from its operations.
|
||||
//
|
||||
// This is essentially the cty.DynamicVal equivalent for providers.Interface,
|
||||
// allowing us to follow our usual pattern that only one return path carries
|
||||
// diagnostics up to the caller and all other codepaths just do their best
|
||||
// to unwind with placeholder values. It's intended only for use in situations
|
||||
// that would expect an already-configured provider, so it's incorrect to call
|
||||
// [ConfigureProvider] on a value of this type.
|
||||
//
|
||||
// Some methods of this type explicitly return errors saying that the provider
|
||||
// configuration was invalid, while others just optimistically do nothing at
|
||||
// all. The general rule is that anything that would for a normal provider
|
||||
// be expected to perform externally-visible side effects must return an error
|
||||
// to be explicit that those side effects did not occur, but we can silently
|
||||
// skip anything that is a Terraform-only detail.
|
||||
//
|
||||
// As usual with provider calls, the returned diagnostics must be annotated
|
||||
// using [tfdiags.Diagnostics.InConfigBody] with the relevant configuration body
|
||||
// so that they can be attributed to the appropriate configuration element.
|
||||
type stubConfiguredProvider struct {
|
||||
// If unknown is true then the implementation will assume it's acting
|
||||
// as a placeholder for a provider whose configuration isn't yet
|
||||
// sufficiently known to be properly instantiated, which means that
|
||||
// plan-time operations will return totally-unknown values.
|
||||
// Otherwise any operation that is supposed to perform a side-effect
|
||||
// will fail with an error saying that the provider configuration
|
||||
// is invalid.
|
||||
unknown bool
|
||||
}
|
||||
|
||||
var _ providers.Interface = stubConfiguredProvider{}
|
||||
|
||||
// ApplyResourceChange implements providers.Interface.
|
||||
func (stubConfiguredProvider) ApplyResourceChange(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is invalid",
|
||||
"Cannot apply changes because this resource's associated provider configuration is invalid.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
return providers.ApplyResourceChangeResponse{
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements providers.Interface.
|
||||
func (stubConfiguredProvider) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureProvider implements providers.Interface.
|
||||
func (stubConfiguredProvider) ConfigureProvider(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
|
||||
// This provider is used only in situations where ConfigureProvider on
|
||||
// a real provider fails and the recipient was expecting a configured
|
||||
// provider, so it doesn't make sense to configure it.
|
||||
panic("can't configure the stub provider")
|
||||
}
|
||||
|
||||
// GetProviderSchema implements providers.Interface.
|
||||
func (stubConfiguredProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
|
||||
return providers.GetProviderSchemaResponse{}
|
||||
}
|
||||
|
||||
// ImportResourceState implements providers.Interface.
|
||||
func (p stubConfiguredProvider) ImportResourceState(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
|
||||
var diags tfdiags.Diagnostics
|
||||
if p.unknown {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is deferred",
|
||||
"Cannot import an existing object into this resource because its associated provider configuration is deferred to a later operation due to unknown expansion.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is invalid",
|
||||
"Cannot import an existing object into this resource because its associated provider configuration is invalid.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
}
|
||||
return providers.ImportResourceStateResponse{
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// PlanResourceChange implements providers.Interface.
|
||||
func (p stubConfiguredProvider) PlanResourceChange(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
if p.unknown {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: cty.DynamicVal,
|
||||
}
|
||||
}
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is invalid",
|
||||
"Cannot plan changes for this resource because its associated provider configuration is invalid.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
return providers.PlanResourceChangeResponse{
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDataSource implements providers.Interface.
|
||||
func (p stubConfiguredProvider) ReadDataSource(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
if p.unknown {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.DynamicVal,
|
||||
}
|
||||
}
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is invalid",
|
||||
"Cannot read from this data source because its associated provider configuration is invalid.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
return providers.ReadDataSourceResponse{
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadResource implements providers.Interface.
|
||||
func (stubConfiguredProvider) ReadResource(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
// For this one we'll just optimistically assume that the remote object
|
||||
// hasn't changed. In many cases we'll fail calling PlanResourceChange
|
||||
// right afterwards anyway, and even if not we'll get another opportunity
|
||||
// to refresh on a future run once the provider configuration is fixed.
|
||||
return providers.ReadResourceResponse{
|
||||
NewState: req.PriorState,
|
||||
Private: req.Private,
|
||||
}
|
||||
}
|
||||
|
||||
// Stop implements providers.Interface.
|
||||
func (stubConfiguredProvider) Stop() error {
|
||||
// This stub provider never actually does any real work, so there's nothing
|
||||
// for us to stop.
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpgradeResourceState implements providers.Interface.
|
||||
func (p stubConfiguredProvider) UpgradeResourceState(req providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
|
||||
if p.unknown {
|
||||
return providers.UpgradeResourceStateResponse{
|
||||
UpgradedState: cty.DynamicVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally we'd just skip this altogether and echo back what the caller
|
||||
// provided, but the request is in a different serialization format than
|
||||
// the response and so only the real provider can deal with this one.
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Provider configuration is invalid",
|
||||
"Cannot decode the prior state for this resource instance because its provider configuration is invalid.",
|
||||
nil, // nil attribute path means the overall configuration block
|
||||
))
|
||||
return providers.UpgradeResourceStateResponse{
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateDataResourceConfig implements providers.Interface.
|
||||
func (stubConfiguredProvider) ValidateDataResourceConfig(req providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
|
||||
// We'll just optimistically assume the configuration is valid, so that
|
||||
// we can progress to planning and return an error there instead.
|
||||
return providers.ValidateDataResourceConfigResponse{
|
||||
Diagnostics: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateProviderConfig implements providers.Interface.
|
||||
func (stubConfiguredProvider) ValidateProviderConfig(req providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
|
||||
// It doesn't make sense to call this one on stubProvider, because
|
||||
// we only use stubProvider for situations where ConfigureProvider failed
|
||||
// on a real provider and we should already have called
|
||||
// ValidateProviderConfig on that provider by then anyway.
|
||||
return providers.ValidateProviderConfigResponse{
|
||||
PreparedConfig: req.Config,
|
||||
Diagnostics: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateResourceConfig implements providers.Interface.
|
||||
func (stubConfiguredProvider) ValidateResourceConfig(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
|
||||
// We'll just optimistically assume the configuration is valid, so that
|
||||
// we can progress to reading and return an error there instead.
|
||||
return providers.ValidateResourceConfigResponse{
|
||||
Diagnostics: nil,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package stackeval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/promising"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
)
|
||||
|
||||
type ProviderType struct {
|
||||
addr addrs.Provider
|
||||
|
||||
main *Main
|
||||
|
||||
schema promising.Once[providers.GetProviderSchemaResponse]
|
||||
unconfiguredClient rcProviderClient
|
||||
}
|
||||
|
||||
func newProviderType(main *Main, addr addrs.Provider) *ProviderType {
|
||||
return &ProviderType{
|
||||
addr: addr,
|
||||
main: main,
|
||||
unconfiguredClient: rcProviderClient{
|
||||
Factory: func() (providers.Interface, error) {
|
||||
return main.ProviderFactories().NewUnconfiguredClient(addr)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *ProviderType) Addr() addrs.Provider {
|
||||
return pt.addr
|
||||
}
|
||||
|
||||
// UnconfiguredClient returns the client for the singleton unconfigured
|
||||
// provider of this type, initializing the provider first if necessary.
|
||||
//
|
||||
// Callers must call Close on the returned client once they are finished
|
||||
// with it, which will internally decrement a reference count so that
|
||||
// the shared provider can be eventually closed once no longer needed.
|
||||
func (pt *ProviderType) UnconfiguredClient(ctx context.Context) (providers.Interface, error) {
|
||||
return pt.unconfiguredClient.Borrow(ctx)
|
||||
}
|
||||
|
||||
func (pt *ProviderType) Schema(ctx context.Context) (providers.GetProviderSchemaResponse, error) {
|
||||
return pt.schema.Do(ctx, func(ctx context.Context) (providers.GetProviderSchemaResponse, error) {
|
||||
client, err := pt.UnconfiguredClient(ctx)
|
||||
if err != nil {
|
||||
return providers.GetProviderSchemaResponse{}, fmt.Errorf("provider startup failed: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ret := client.GetProviderSchema()
|
||||
if ret.Diagnostics.HasErrors() {
|
||||
return providers.GetProviderSchemaResponse{}, fmt.Errorf("provider failed to return its schema")
|
||||
}
|
||||
return ret, nil
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
|
||||
required_providers {
|
||||
test = {
|
||||
source = "example.com/test/test"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
}
|
||||
|
||||
provider "test" "foo" {
|
||||
config {
|
||||
name = var.name
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue