mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 17:01:04 -06:00
39e609d5fd
Previously we were using the experimental HCL 2 repository, but now we'll shift over to the v2 import path within the main HCL repository as part of actually releasing HCL 2.0 as stable. This is a mechanical search/replace to the new import paths. It also switches to the v2.0.0 release of HCL, which includes some new code that Terraform didn't previously have but should not change any behavior that matters for Terraform's purposes. For the moment the experimental HCL2 repository is still an indirect dependency via terraform-config-inspect, so it remains in our go.sum and vendor directories for the moment. Because terraform-config-inspect uses a much smaller subset of the HCL2 functionality, this does still manage to prune the vendor directory a little. A subsequent release of terraform-config-inspect should allow us to completely remove that old repository in a future commit.
136 lines
4.3 KiB
Go
136 lines
4.3 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/states"
|
|
)
|
|
|
|
// EvalDeleteOutput is an EvalNode implementation that deletes an output
|
|
// from the state.
|
|
type EvalDeleteOutput struct {
|
|
Addr addrs.OutputValue
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|
state := ctx.State()
|
|
if state == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
state.RemoveOutputValue(n.Addr.Absolute(ctx.Path()))
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalWriteOutput is an EvalNode implementation that writes the output
|
|
// for the given name to the current state.
|
|
type EvalWriteOutput struct {
|
|
Addr addrs.OutputValue
|
|
Sensitive bool
|
|
Expr hcl.Expression
|
|
// ContinueOnErr allows interpolation to fail during Input
|
|
ContinueOnErr bool
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|
addr := n.Addr.Absolute(ctx.Path())
|
|
|
|
// This has to run before we have a state lock, since evaluation also
|
|
// reads the state
|
|
val, diags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil)
|
|
// We'll handle errors below, after we have loaded the module.
|
|
|
|
state := ctx.State()
|
|
if state == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
changes := ctx.Changes() // may be nil, if we're not working on a changeset
|
|
|
|
// handling the interpolation error
|
|
if diags.HasErrors() {
|
|
if n.ContinueOnErr || flagWarnOutputErrors {
|
|
log.Printf("[ERROR] Output interpolation %q failed: %s", n.Addr.Name, diags.Err())
|
|
// if we're continuing, make sure the output is included, and
|
|
// marked as unknown. If the evaluator was able to find a type
|
|
// for the value in spite of the error then we'll use it.
|
|
n.setValue(addr, state, changes, cty.UnknownVal(val.Type()))
|
|
return nil, EvalEarlyExitError{}
|
|
}
|
|
return nil, diags.Err()
|
|
}
|
|
|
|
n.setValue(addr, state, changes, val)
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *EvalWriteOutput) setValue(addr addrs.AbsOutputValue, state *states.SyncState, changes *plans.ChangesSync, val cty.Value) {
|
|
if val.IsKnown() && !val.IsNull() {
|
|
// The state itself doesn't represent unknown values, so we null them
|
|
// out here and then we'll save the real unknown value in the planned
|
|
// changeset below, if we have one on this graph walk.
|
|
log.Printf("[TRACE] EvalWriteOutput: Saving value for %s in state", addr)
|
|
stateVal := cty.UnknownAsNull(val)
|
|
state.SetOutputValue(addr, stateVal, n.Sensitive)
|
|
} else {
|
|
log.Printf("[TRACE] EvalWriteOutput: Removing %s from state (it is now null)", addr)
|
|
state.RemoveOutputValue(addr)
|
|
}
|
|
|
|
// If we also have an active changeset then we'll replicate the value in
|
|
// there. This is used in preference to the state where present, since it
|
|
// *is* able to represent unknowns, while the state cannot.
|
|
if changes != nil {
|
|
// For the moment we are not properly tracking changes to output
|
|
// values, and just marking them always as "Create" or "Destroy"
|
|
// actions. A future release will rework the output lifecycle so we
|
|
// can track their changes properly, in a similar way to how we work
|
|
// with resource instances.
|
|
|
|
var change *plans.OutputChange
|
|
if !val.IsNull() {
|
|
change = &plans.OutputChange{
|
|
Addr: addr,
|
|
Sensitive: n.Sensitive,
|
|
Change: plans.Change{
|
|
Action: plans.Create,
|
|
Before: cty.NullVal(cty.DynamicPseudoType),
|
|
After: val,
|
|
},
|
|
}
|
|
} else {
|
|
change = &plans.OutputChange{
|
|
Addr: addr,
|
|
Sensitive: n.Sensitive,
|
|
Change: plans.Change{
|
|
// This is just a weird placeholder delete action since
|
|
// we don't have an actual prior value to indicate.
|
|
// FIXME: Generate real planned changes for output values
|
|
// that include the old values.
|
|
Action: plans.Delete,
|
|
Before: cty.NullVal(cty.DynamicPseudoType),
|
|
After: cty.NullVal(cty.DynamicPseudoType),
|
|
},
|
|
}
|
|
}
|
|
|
|
cs, err := change.Encode()
|
|
if err != nil {
|
|
// Should never happen, since we just constructed this right above
|
|
panic(fmt.Sprintf("planned change for %s could not be encoded: %s", addr, err))
|
|
}
|
|
log.Printf("[TRACE] EvalWriteOutput: Saving %s change for %s in changeset", change.Action, addr)
|
|
changes.RemoveOutputChange(addr) // remove any existing planned change, if present
|
|
changes.AppendOutputChange(cs) // add the new planned change
|
|
}
|
|
}
|