mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
319 lines
12 KiB
Go
319 lines
12 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
"github.com/opentofu/opentofu/internal/tracing"
|
|
"github.com/opentofu/opentofu/internal/tracing/traceattrs"
|
|
)
|
|
|
|
// traceAttrProviderAddress is a standardized trace span attribute name that we
|
|
// use for recording the address of the main provider that a span is associated
|
|
// with.
|
|
//
|
|
// The value of this should be populated by calling the String method on
|
|
// a value of type [addrs.Provider].
|
|
const traceAttrProviderAddr = "opentofu.provider.source"
|
|
|
|
// traceAttrProviderConfigAddress is a standardized trace span attribute
|
|
// name that we use for recording the address of the main provider configuration
|
|
// block that a span is associated with.
|
|
//
|
|
// The value of this should be populated by calling the String method on
|
|
// a value of type [addrs.AbsProviderConfig].
|
|
const traceAttrProviderConfigAddr = "opentofu.provider_config.address"
|
|
|
|
// traceAttrProviderInstanceAddr is a standardized trace span attribute
|
|
// name that we use for recording the address of the main provider instance
|
|
// that a span is associated with.
|
|
//
|
|
// The value of this should be populated by calling traceProviderInstanceAddr
|
|
// with the [addrs.AbsProviderConfig] and [addrs.InstanceKey] value that
|
|
// together uniquely identify the provider instance.
|
|
const traceAttrProviderInstanceAddr = "opentofu.provider_instance.address"
|
|
|
|
// NodeApplyableProvider represents a configured provider.
|
|
type NodeApplyableProvider struct {
|
|
*NodeAbstractProvider
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeExecutable = (*NodeApplyableProvider)(nil)
|
|
)
|
|
|
|
// GraphNodeExecutable
|
|
func (n *NodeApplyableProvider) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
|
instances, diags := n.initInstances(ctx, evalCtx, op)
|
|
|
|
for key, provider := range instances {
|
|
diags = diags.Append(n.executeInstance(ctx, evalCtx, op, key, provider))
|
|
}
|
|
|
|
return diags
|
|
}
|
|
func (n *NodeApplyableProvider) initInstances(ctx context.Context, evalCtx EvalContext, op walkOperation) (map[addrs.InstanceKey]providers.Interface, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
var initKeys []addrs.InstanceKey
|
|
// config -> init (different due to validate skipping most for_each logic)
|
|
instanceKeys := make(map[addrs.InstanceKey]addrs.InstanceKey)
|
|
if n.Config == nil || n.Config.Instances == nil {
|
|
initKeys = append(initKeys, addrs.NoKey)
|
|
instanceKeys[addrs.NoKey] = addrs.NoKey
|
|
} else if op == walkValidate {
|
|
// Instances are set AND we are validating
|
|
initKeys = append(initKeys, addrs.NoKey)
|
|
for key := range n.Config.Instances {
|
|
instanceKeys[key] = addrs.NoKey
|
|
}
|
|
} else {
|
|
// Instances are set AND we are not validating
|
|
for key := range n.Config.Instances {
|
|
initKeys = append(initKeys, key)
|
|
instanceKeys[key] = key
|
|
}
|
|
}
|
|
|
|
for _, key := range initKeys {
|
|
_, err := evalCtx.InitProvider(ctx, n.Addr, key)
|
|
diags = diags.Append(err)
|
|
}
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
instances := make(map[addrs.InstanceKey]providers.Interface)
|
|
for configKey, initKey := range instanceKeys {
|
|
provider, _, err := getProvider(ctx, evalCtx, n.Addr, initKey)
|
|
diags = diags.Append(err)
|
|
instances[configKey] = provider
|
|
}
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
return instances, diags
|
|
}
|
|
func (n *NodeApplyableProvider) executeInstance(ctx context.Context, evalCtx EvalContext, op walkOperation, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics {
|
|
switch op {
|
|
case walkValidate:
|
|
log.Printf("[TRACE] NodeApplyableProvider: validating configuration for %s", n.Addr)
|
|
return n.ValidateProvider(ctx, evalCtx, providerKey, provider)
|
|
case walkPlan, walkPlanDestroy, walkApply, walkDestroy:
|
|
log.Printf("[TRACE] NodeApplyableProvider: configuring %s", n.Addr)
|
|
return n.ConfigureProvider(ctx, evalCtx, providerKey, provider, false)
|
|
case walkImport:
|
|
log.Printf("[TRACE] NodeApplyableProvider: configuring %s (requiring that configuration is wholly known)", n.Addr)
|
|
return n.ConfigureProvider(ctx, evalCtx, providerKey, provider, true)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *NodeApplyableProvider) ValidateProvider(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics {
|
|
_, span := tracing.Tracer().Start(
|
|
ctx, "Validate provider configuration",
|
|
tracing.SpanAttributes(
|
|
traceattrs.String(traceAttrProviderAddr, n.Addr.Provider.String()),
|
|
traceattrs.String(traceAttrProviderConfigAddr, n.Addr.String()),
|
|
traceattrs.String(traceAttrProviderInstanceAddr, traceProviderInstanceAddr(n.Addr, providerKey)),
|
|
),
|
|
)
|
|
defer span.End()
|
|
|
|
if n.Config != nil && n.Config.IsMocked {
|
|
// Mocked for testing
|
|
return nil
|
|
}
|
|
|
|
configBody := buildProviderConfig(ctx, evalCtx, n.Addr, n.ProviderConfig())
|
|
|
|
// if a provider config is empty (only an alias), return early and don't continue
|
|
// validation. validate doesn't need to fully configure the provider itself, so
|
|
// skipping a provider with an implied configuration won't prevent other validation from completing.
|
|
_, noConfigDiags := configBody.Content(&hcl.BodySchema{})
|
|
if !noConfigDiags.HasErrors() {
|
|
return nil
|
|
}
|
|
|
|
schemaResp := provider.GetProviderSchema(ctx)
|
|
diags := schemaResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey))
|
|
if diags.HasErrors() {
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
configSchema := schemaResp.Provider.Block
|
|
if configSchema == nil {
|
|
// Should never happen in real code, but often comes up in tests where
|
|
// mock schemas are being used that tend to be incomplete.
|
|
log.Printf("[WARN] ValidateProvider: no config schema is available for %s, so using empty schema", n.Addr)
|
|
configSchema = &configschema.Block{}
|
|
}
|
|
|
|
data := EvalDataForNoInstanceKey
|
|
if n.Config != nil && n.Config.Instances != nil {
|
|
data = n.Config.Instances[providerKey]
|
|
}
|
|
|
|
configVal, _, evalDiags := evalCtx.EvaluateBlock(ctx, configBody, configSchema, nil, data)
|
|
if evalDiags.HasErrors() {
|
|
tracing.SetSpanError(span, diags)
|
|
return diags.Append(evalDiags)
|
|
}
|
|
diags = diags.Append(evalDiags)
|
|
|
|
// If our config value contains any marked values, ensure those are
|
|
// stripped out before sending this to the provider
|
|
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
|
|
|
req := providers.ValidateProviderConfigRequest{
|
|
Config: unmarkedConfigVal,
|
|
}
|
|
|
|
validateResp := provider.ValidateProviderConfig(ctx, req)
|
|
diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
|
|
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
// ConfigureProvider configures a provider that is already initialized and retrieved.
|
|
// If verifyConfigIsKnown is true, ConfigureProvider will return an error if the
|
|
// provider configVal is not wholly known and is meant only for use during import.
|
|
func (n *NodeApplyableProvider) ConfigureProvider(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface, verifyConfigIsKnown bool) tfdiags.Diagnostics {
|
|
_, span := tracing.Tracer().Start(
|
|
ctx, "Configure provider",
|
|
tracing.SpanAttributes(
|
|
traceattrs.String(traceAttrProviderAddr, n.Addr.Provider.String()),
|
|
traceattrs.String(traceAttrProviderConfigAddr, n.Addr.String()),
|
|
traceattrs.String(traceAttrProviderInstanceAddr, traceProviderInstanceAddr(n.Addr, providerKey)),
|
|
),
|
|
)
|
|
defer span.End()
|
|
|
|
if n.Config != nil && n.Config.IsMocked {
|
|
// Mocked for testing
|
|
return nil
|
|
}
|
|
|
|
config := n.ProviderConfig()
|
|
|
|
configBody := buildProviderConfig(ctx, evalCtx, n.Addr, config)
|
|
|
|
resp := provider.GetProviderSchema(ctx)
|
|
diags := resp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey))
|
|
if diags.HasErrors() {
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
configSchema := resp.Provider.Block
|
|
data := EvalDataForNoInstanceKey
|
|
if n.Config != nil && n.Config.Instances != nil {
|
|
data = n.Config.Instances[providerKey]
|
|
}
|
|
|
|
configVal, configBody, evalDiags := evalCtx.EvaluateBlock(ctx, configBody, configSchema, nil, data)
|
|
diags = diags.Append(evalDiags)
|
|
if evalDiags.HasErrors() {
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
if verifyConfigIsKnown && !configVal.IsWhollyKnown() {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid provider configuration",
|
|
Detail: fmt.Sprintf("The configuration for %s depends on values that cannot be determined until apply.", n.Addr),
|
|
Subject: &config.DeclRange,
|
|
})
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
// If our config value contains any marked values, ensure those are
|
|
// stripped out before sending this to the provider
|
|
unmarkedConfigVal, _ := configVal.UnmarkDeep()
|
|
|
|
// Allow the provider to validate and insert any defaults into the full
|
|
// configuration.
|
|
req := providers.ValidateProviderConfigRequest{
|
|
Config: unmarkedConfigVal,
|
|
}
|
|
|
|
// ValidateProviderConfig is only used for validation. We are intentionally
|
|
// ignoring the PreparedConfig field to maintain existing behavior.
|
|
validateResp := provider.ValidateProviderConfig(ctx, req)
|
|
diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
|
|
if diags.HasErrors() && config == nil {
|
|
// If there isn't an explicit "provider" block in the configuration,
|
|
// this error message won't be very clear. Add some detail to the error
|
|
// message in this case.
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Invalid provider configuration",
|
|
fmt.Sprintf(providerConfigErr, n.Addr.Provider),
|
|
))
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
// If the provider returns something different, log a warning to help
|
|
// indicate to provider developers that the value is not used.
|
|
preparedCfg := validateResp.PreparedConfig
|
|
if preparedCfg != cty.NilVal && !preparedCfg.IsNull() && !preparedCfg.RawEquals(unmarkedConfigVal) {
|
|
log.Printf("[WARN] ValidateProviderConfig from %q changed the config value, but that value is unused", n.Addr)
|
|
}
|
|
|
|
configDiags := evalCtx.ConfigureProvider(ctx, n.Addr, providerKey, unmarkedConfigVal)
|
|
diags = diags.Append(configDiags.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
|
|
if diags.HasErrors() && config == nil {
|
|
// If there isn't an explicit "provider" block in the configuration,
|
|
// this error message won't be very clear. Add some detail to the error
|
|
// message in this case.
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Invalid provider configuration",
|
|
fmt.Sprintf(providerConfigErr, n.Addr.Provider),
|
|
))
|
|
}
|
|
tracing.SetSpanError(span, diags)
|
|
return diags
|
|
}
|
|
|
|
const providerConfigErr = `Provider %q requires explicit configuration. Add a provider block to the root module and configure the provider's required arguments as described in the provider documentation.
|
|
`
|
|
|
|
// traceProviderInstanceAddr generates a value to be used for tracing attributes
|
|
// that refer to a specific instance of a provider configuration.
|
|
//
|
|
// This is here to compensate for the fact that we don't currently have an
|
|
// address type for provider instance addresses in package addrs, and instead
|
|
// just pass around loose config address and instance key values as separate
|
|
// arguments. If we do eventually have a suitable address type then this
|
|
// function should be removed and all uses of it replaced by calling the
|
|
// String method on that address type.
|
|
func traceProviderInstanceAddr(configAddr addrs.AbsProviderConfig, instKey addrs.InstanceKey) string {
|
|
if instKey == addrs.NoKey {
|
|
return configAddr.String()
|
|
}
|
|
return configAddr.String() + instKey.String()
|
|
}
|