opentofu/terraform/eval_read_data.go
Martin Atkins ab29eca045 core: don't force data resource id diff to be empty
In an attempt to always show "id" as computed we were producing a
synthetic diff for it, but this causes problems when the id attribute for
a particular data source is actually settable in configuration, since it
masks the setting from the config and forces it to always be empty.

Instead, we'll set it conditionally so that a value provided in the config
can shine through when available.
2016-09-24 12:48:58 -07:00

140 lines
3.3 KiB
Go

package terraform
import (
"fmt"
)
// EvalReadDataDiff is an EvalNode implementation that executes a data
// resource's ReadDataDiff method to discover what attributes it exports.
type EvalReadDataDiff struct {
Provider *ResourceProvider
Output **InstanceDiff
OutputState **InstanceState
Config **ResourceConfig
Info *InstanceInfo
// Set Previous when re-evaluating diff during apply, to ensure that
// the "Destroy" flag is preserved.
Previous **InstanceDiff
}
func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) {
// TODO: test
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreDiff(n.Info, nil)
})
if err != nil {
return nil, err
}
var diff *InstanceDiff
if n.Previous != nil && *n.Previous != nil && (*n.Previous).GetDestroy() {
// If we're re-diffing for a diff that was already planning to
// destroy, then we'll just continue with that plan.
diff = &InstanceDiff{Destroy: true}
} else {
provider := *n.Provider
config := *n.Config
var err error
diff, err = provider.ReadDataDiff(n.Info, config)
if err != nil {
return nil, err
}
if diff == nil {
diff = new(InstanceDiff)
}
// if id isn't explicitly set then it's always computed, because we're
// always "creating a new resource".
diff.init()
if _, ok := diff.Attributes["id"]; !ok {
diff.SetAttribute("id", &ResourceAttrDiff{
Old: "",
NewComputed: true,
RequiresNew: true,
Type: DiffAttrOutput,
})
}
}
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostDiff(n.Info, diff)
})
if err != nil {
return nil, err
}
*n.Output = diff
if n.OutputState != nil {
state := &InstanceState{}
*n.OutputState = state
// Apply the diff to the returned state, so the state includes
// any attribute values that are not computed.
if !diff.Empty() && n.OutputState != nil {
*n.OutputState = state.MergeDiff(diff)
}
}
return nil, nil
}
// EvalReadDataApply is an EvalNode implementation that executes a data
// resource's ReadDataApply method to read data from the data source.
type EvalReadDataApply struct {
Provider *ResourceProvider
Output **InstanceState
Diff **InstanceDiff
Info *InstanceInfo
}
func (n *EvalReadDataApply) Eval(ctx EvalContext) (interface{}, error) {
// TODO: test
provider := *n.Provider
diff := *n.Diff
// If the diff is for *destroying* this resource then we'll
// just drop its state and move on, since data resources don't
// support an actual "destroy" action.
if diff != nil && diff.GetDestroy() {
if n.Output != nil {
*n.Output = nil
}
return nil, nil
}
// For the purpose of external hooks we present a data apply as a
// "Refresh" rather than an "Apply" because creating a data source
// is presented to users/callers as a "read" operation.
err := ctx.Hook(func(h Hook) (HookAction, error) {
// We don't have a state yet, so we'll just give the hook an
// empty one to work with.
return h.PreRefresh(n.Info, &InstanceState{})
})
if err != nil {
return nil, err
}
state, err := provider.ReadDataApply(n.Info, diff)
if err != nil {
return nil, fmt.Errorf("%s: %s", n.Info.Id, err)
}
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostRefresh(n.Info, state)
})
if err != nil {
return nil, err
}
if n.Output != nil {
*n.Output = state
}
return nil, nil
}