mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
171 lines
5.7 KiB
Go
171 lines
5.7 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/plans/objchange"
|
|
"github.com/hashicorp/terraform/states"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
)
|
|
|
|
// evalReadDataPlan is an EvalNode implementation that deals with the main part
|
|
// of the data resource lifecycle: either actually reading from the data source
|
|
// or generating a plan to do so.
|
|
type evalReadDataPlan struct {
|
|
evalReadData
|
|
}
|
|
|
|
func (n *evalReadDataPlan) Eval(ctx EvalContext) tfdiags.Diagnostics {
|
|
absAddr := n.Addr.Absolute(ctx.Path())
|
|
|
|
var diags tfdiags.Diagnostics
|
|
var configVal cty.Value
|
|
|
|
if n.ProviderSchema == nil || *n.ProviderSchema == nil {
|
|
diags = diags.Append(fmt.Errorf("provider schema not available for %s", n.Addr))
|
|
return diags
|
|
}
|
|
|
|
config := *n.Config
|
|
providerSchema := *n.ProviderSchema
|
|
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
|
|
if schema == nil {
|
|
// Should be caught during validation, so we don't bother with a pretty error here
|
|
diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ProviderAddr.Provider.String(), n.Addr.Resource.Type))
|
|
return diags
|
|
}
|
|
|
|
objTy := schema.ImpliedType()
|
|
priorVal := cty.NullVal(objTy)
|
|
if n.State != nil && *n.State != nil {
|
|
priorVal = (*n.State).Value
|
|
}
|
|
|
|
forEach, _ := evaluateForEachExpression(config.ForEach, ctx)
|
|
keyData := EvalDataForInstanceKey(n.Addr.Key, forEach)
|
|
|
|
var configDiags tfdiags.Diagnostics
|
|
configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
|
|
diags = diags.Append(configDiags)
|
|
if configDiags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
configKnown := configVal.IsWhollyKnown()
|
|
// If our configuration contains any unknown values, or we depend on any
|
|
// unknown values then we must defer the read to the apply phase by
|
|
// producing a "Read" change for this resource, and a placeholder value for
|
|
// it in the state.
|
|
if n.forcePlanRead(ctx) || !configKnown {
|
|
if configKnown {
|
|
log.Printf("[TRACE] evalReadDataPlan: %s configuration is fully known, but we're forcing a read plan to be created", absAddr)
|
|
} else {
|
|
log.Printf("[TRACE] evalReadDataPlan: %s configuration not fully known yet, so deferring to apply phase", absAddr)
|
|
}
|
|
|
|
proposedNewVal := objchange.PlannedDataResourceObject(schema, configVal)
|
|
|
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
|
|
}))
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// Apply detects that the data source will need to be read by the After
|
|
// value containing unknowns from PlanDataResourceObject.
|
|
*n.OutputChange = &plans.ResourceInstanceChange{
|
|
Addr: absAddr,
|
|
ProviderAddr: n.ProviderAddr,
|
|
Change: plans.Change{
|
|
Action: plans.Read,
|
|
Before: priorVal,
|
|
After: proposedNewVal,
|
|
},
|
|
}
|
|
|
|
*n.State = &states.ResourceInstanceObject{
|
|
Value: proposedNewVal,
|
|
Status: states.ObjectPlanned,
|
|
}
|
|
|
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostDiff(absAddr, states.CurrentGen, plans.Read, priorVal, proposedNewVal)
|
|
}))
|
|
|
|
return diags
|
|
}
|
|
|
|
// We have a complete configuration with no dependencies to wait on, so we
|
|
// can read the data source into the state.
|
|
newVal, readDiags := n.readDataSource(ctx, configVal)
|
|
diags = diags.Append(readDiags)
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
// if we have a prior value, we can check for any irregularities in the response
|
|
if !priorVal.IsNull() {
|
|
// While we don't propose planned changes for data sources, we can
|
|
// generate a proposed value for comparison to ensure the data source
|
|
// is returning a result following the rules of the provider contract.
|
|
proposedVal := objchange.ProposedNewObject(schema, priorVal, configVal)
|
|
if errs := objchange.AssertObjectCompatible(schema, proposedVal, newVal); len(errs) > 0 {
|
|
// Resources have the LegacyTypeSystem field to signal when they are
|
|
// using an SDK which may not produce precise values. While data
|
|
// sources are read-only, they can still return a value which is not
|
|
// compatible with the config+schema. Since we can't detect the legacy
|
|
// type system, we can only warn about this for now.
|
|
var buf strings.Builder
|
|
fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s.",
|
|
n.ProviderAddr.Provider.String(), absAddr)
|
|
for _, err := range errs {
|
|
fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err))
|
|
}
|
|
log.Print(buf.String())
|
|
}
|
|
}
|
|
|
|
*n.State = &states.ResourceInstanceObject{
|
|
Value: newVal,
|
|
Status: states.ObjectReady,
|
|
}
|
|
|
|
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostDiff(absAddr, states.CurrentGen, plans.Update, priorVal, newVal)
|
|
}))
|
|
return diags
|
|
}
|
|
|
|
// forcePlanRead determines if we need to override the usual behavior of
|
|
// immediately reading from the data source where possible, instead forcing us
|
|
// to generate a plan.
|
|
func (n *evalReadDataPlan) forcePlanRead(ctx EvalContext) bool {
|
|
// Check and see if any depends_on dependencies have
|
|
// changes, since they won't show up as changes in the
|
|
// configuration.
|
|
changes := ctx.Changes()
|
|
for _, d := range n.dependsOn {
|
|
if d.Resource.Mode == addrs.DataResourceMode {
|
|
// Data sources have no external side effects, so they pose a need
|
|
// to delay this read. If they do have a change planned, it must be
|
|
// because of a dependency on a managed resource, in which case
|
|
// we'll also encounter it in this list of dependencies.
|
|
continue
|
|
}
|
|
|
|
for _, change := range changes.GetChangesForConfigResource(d) {
|
|
if change != nil && change.Action != plans.NoOp {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|