157 lines
4.6 KiB
Go
157 lines
4.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
"github.com/zclconf/go-cty/cty/gocty"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/hcl2shim"
|
|
)
|
|
|
|
// 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, lock := ctx.State()
|
|
if state == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Get a write lock so we can access this instance
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
// Look for the module state. If we don't have one, create it.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
delete(mod.Outputs, n.Addr.Name)
|
|
|
|
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) {
|
|
// 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, lock := ctx.State()
|
|
if state == nil {
|
|
return nil, fmt.Errorf("cannot write state to nil state")
|
|
}
|
|
|
|
// Get a write lock so we can access this instance
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
// Look for the module state. If we don't have one, create it.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
mod = state.AddModule(ctx.Path())
|
|
}
|
|
|
|
// 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
|
|
mod.Outputs[n.Addr.Name] = &OutputState{
|
|
Type: "string",
|
|
Value: config.UnknownVariableValue,
|
|
}
|
|
return nil, EvalEarlyExitError{}
|
|
}
|
|
return nil, diags.Err()
|
|
}
|
|
|
|
ty := val.Type()
|
|
switch {
|
|
case ty.IsPrimitiveType():
|
|
// For now we record all primitive types as strings, for compatibility
|
|
// with our existing state formats.
|
|
// FIXME: Revise the state format to support any type.
|
|
var valueTyped string
|
|
switch {
|
|
case !val.IsKnown():
|
|
// Legacy handling of unknown values as a special string.
|
|
valueTyped = config.UnknownVariableValue
|
|
case val.IsNull():
|
|
// State doesn't currently support null, so we'll save as empty string.
|
|
valueTyped = ""
|
|
default:
|
|
strVal, err := convert.Convert(val, cty.String)
|
|
if err != nil {
|
|
// Should never happen, because all primitives can convert to string.
|
|
return nil, fmt.Errorf("cannot marshal %#v for storage in state: %s", val, err)
|
|
}
|
|
err = gocty.FromCtyValue(strVal, &valueTyped)
|
|
if err != nil {
|
|
// Should never happen, because we already converted to string.
|
|
return nil, fmt.Errorf("cannot marshal %#v for storage in state: %s", val, err)
|
|
}
|
|
}
|
|
mod.Outputs[n.Addr.Name] = &OutputState{
|
|
Type: "string",
|
|
Sensitive: n.Sensitive,
|
|
Value: valueTyped,
|
|
}
|
|
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
|
|
// For now we'll use our legacy storage forms for list-like types.
|
|
// This produces a []interface{}.
|
|
valueTyped := hcl2shim.ConfigValueFromHCL2(val)
|
|
mod.Outputs[n.Addr.Name] = &OutputState{
|
|
Type: "list",
|
|
Sensitive: n.Sensitive,
|
|
Value: valueTyped,
|
|
}
|
|
case ty.IsMapType() || ty.IsObjectType():
|
|
// For now we'll use our legacy storage forms for map-like types.
|
|
// This produces a map[string]interface{}.
|
|
valueTyped := hcl2shim.ConfigValueFromHCL2(val)
|
|
mod.Outputs[n.Addr.Name] = &OutputState{
|
|
Type: "map",
|
|
Sensitive: n.Sensitive,
|
|
Value: valueTyped,
|
|
}
|
|
case ty == cty.DynamicPseudoType || !val.IsWhollyKnown():
|
|
// While we're still using our existing state format, we can't represent
|
|
// partially-unknown values properly, so we'll just stub the whole
|
|
// thing out.
|
|
// FIXME: After the state format is revised, remove this special case
|
|
// and just store the unknown value directly.
|
|
mod.Outputs[n.Addr.Name] = &OutputState{
|
|
Type: "unknown",
|
|
Sensitive: n.Sensitive,
|
|
Value: hcl2shim.UnknownVariableValue,
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("output %s is not a valid type (%s)", n.Addr.Name, ty.FriendlyName())
|
|
}
|
|
|
|
return nil, nil
|
|
}
|