plans: New package for in-memory plan models

The types in this package are intended to replace plan- and diff-related
types from the "terraform" package, although those older types must remain
for now so that they can be used to implement shims for older codepaths.

type "Changes" is approximately equivalent to terraform.Diff, but renamed
since it now describes whole objects before and after rather than an
attribute-level diff as before. The term "diff" is now reserved for the
visual rendition of the changes we'll display to the user, although
rendering of this new Changes model is not yet implemented.
This commit is contained in:
Martin Atkins 2018-06-19 16:03:59 -07:00
parent eb7aaf2414
commit 7357e7f734
8 changed files with 247 additions and 0 deletions

14
plans/action.go Normal file
View File

@ -0,0 +1,14 @@
package plans
type Action rune
const (
NoOp Action = 0
Create Action = '+'
Read Action = '←'
Update Action = '~'
Replace Action = '±'
Delete Action = '-'
)
//go:generate stringer -type Action

33
plans/action_string.go Normal file
View File

@ -0,0 +1,33 @@
// Code generated by "stringer -type Action"; DO NOT EDIT.
package plans
import "strconv"
const (
_Action_name_0 = "NoOp"
_Action_name_1 = "Create"
_Action_name_2 = "Delete"
_Action_name_3 = "Update"
_Action_name_4 = "Replace"
_Action_name_5 = "Read"
)
func (i Action) String() string {
switch {
case i == 0:
return _Action_name_0
case i == 43:
return _Action_name_1
case i == 45:
return _Action_name_2
case i == 126:
return _Action_name_3
case i == 177:
return _Action_name_4
case i == 8592:
return _Action_name_5
default:
return "Action(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

78
plans/changes.go Normal file
View File

@ -0,0 +1,78 @@
package plans
import (
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
)
// Changes describes various actions that Terraform will attempt to take if
// the corresponding plan is applied.
//
// A Changes object can be rendered into a visual diff (by the caller, using
// code in another package) for display to the user.
type Changes struct {
Resources []*ResourceInstanceChange
RootOutputs map[string]*OutputChange
}
// ResourceInstanceChange describes a change to a particular resource instance
// object.
type ResourceInstanceChange struct {
// Addr is the absolute address of the resource instance that the change
// will apply to.
Addr addrs.AbsResourceInstance
// DeposedKey is the identifier for a deposed object associated with the
// given instance, or states.NotDeposed if this change applies to the
// current object.
//
// A Replace change for a resource with create_before_destroy set will
// create a new DeposedKey temporarily during replacement. In that case,
// DeposedKey in the plan is always states.NotDeposed, representing that
// the current object is being replaced with the deposed.
DeposedKey states.DeposedKey
// Change is an embedded description of the change.
Change
}
// OutputChange describes a change to an output value.
type OutputChange struct {
// Change is an embedded description of the change.
//
// For output value changes, the type constraint for the DynamicValue
// instances is always cty.DynamicPseudoType.
Change
// Sensitive, if true, indicates that either the old or new value in the
// change is sensitive and so a rendered version of the plan in the UI
// should elide the actual values while still indicating the action of the
// change.
Sensitive bool
}
// Change describes a single change with a given action.
type Change struct {
// Action defines what kind of change is being made.
Action Action
// Interpretation of Before and After depend on Action:
//
// NoOp Before and After are the same, unchanged value
// Create Before is nil, and After is the expected value after create.
// Read Before is any prior value (nil if no prior), and After is the
// value that was or will be read.
// Update Before is the value prior to update, and After is the expected
// value after update.
// Replace As with Update.
// Delete Before is the value prior to delete, and After is always nil.
//
// Unknown values may appear anywhere within the Before and After values,
// either as the values themselves or as nested elements within known
// collections/structures.
//
// A plan contains only raw (not yet decoded) values. The caller must use
// schema information obtained out-of-band to decode dynamic values before
// they can be used.
Before, After DynamicValue
}

15
plans/changes_state.go Normal file
View File

@ -0,0 +1,15 @@
package plans
import (
"github.com/hashicorp/terraform/states"
)
// PlannedState merges the set of changes described by the receiver into the
// given prior state to produce the planned result state.
//
// The result is an approximation of the state as it would exist after
// applying these changes, omitting any values that cannot be determined until
// the changes are actually applied.
func (c *Changes) PlannedState(prior *states.State) (*states.State, error) {
panic("Changes.PlannedState not yet implemented")
}

5
plans/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Package plans contains the types that are used to represent Terraform plans.
//
// A plan describes a set of changes that Terraform will make to update remote
// objects to match with changes to the configuration.
package plans

71
plans/dynamic_value.go Normal file
View File

@ -0,0 +1,71 @@
package plans
import (
"github.com/zclconf/go-cty/cty"
ctymsgpack "github.com/zclconf/go-cty/cty/msgpack"
)
// DynamicValue is the representation in the plan of a value whose type cannot
// be determined at compile time, such as because it comes from a schema
// defined in a plugin.
//
// This type is used as an indirection so that the overall plan structure can
// be decoded without schema available, and then the dynamic values accessed
// at a later time once the appropriate schema has been determined.
//
// Internally, DynamicValue is a serialized version of a cty.Value created
// against a particular type constraint. Callers should not access directly
// the serialized form, whose format may change in future. Values of this
// type must always be created by calling NewDynamicValue.
//
// The zero value of DynamicValue is nil, and represents the absense of a
// value within the Go type system. This is distinct from a cty.NullVal
// result, which represents the absense of a value within the cty type system.
type DynamicValue []byte
// NewDynamicValue creates a DynamicValue by serializing the given value
// against the given type constraint. The value must conform to the type
// constraint, or the result is undefined.
//
// If the value to be encoded has no predefined schema (for example, for
// module output values and input variables), set the type constraint to
// cty.DynamicPseudoType in order to save type information as part of the
// value, and then also pass cty.DynamicPseudoType to method Decode to recover
// the original value.
//
// cty.NilVal can be used to represent the absense of a value, but callers
// must be careful to distinguish values that are absent at the Go layer
// (cty.NilVal) vs. values that are absent at the cty layer (cty.NullVal
// results).
func NewDynamicValue(val cty.Value, ty cty.Type) (DynamicValue, error) {
// If we're given cty.NilVal (the zero value of cty.Value, which is
// distinct from a typed null value created by cty.NullVal) then we'll
// assume the caller is trying to represent the _absense_ of a value,
// and so we'll return a nil DynamicValue.
if val == cty.NilVal {
return DynamicValue(nil), nil
}
// Currently our internal encoding is msgpack, via ctymsgpack.
buf, err := ctymsgpack.Marshal(val, ty)
if err != nil {
return nil, err
}
return DynamicValue(buf), nil
}
// Decode retrieves the effective value from the receiever by interpreting the
// serialized form against the given type constraint. For correct results,
// the type constraint must match (or be consistent with) the one that was
// used to create the receiver.
//
// A nil DynamicValue decodes to cty.NilVal, which is not a valid value and
// instead represents the absense of a value.
func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) {
if v == nil {
return cty.NilVal, nil
}
return ctymsgpack.Unmarshal([]byte(v), ty)
}

View File

@ -0,0 +1,13 @@
package plans
import (
"github.com/zclconf/go-cty/cty"
)
func mustNewDynamicValue(val cty.Value, ty cty.Type) DynamicValue {
ret, err := NewDynamicValue(val, ty)
if err != nil {
panic(err)
}
return ret
}

18
plans/plan.go Normal file
View File

@ -0,0 +1,18 @@
package plans
// Plan is the top-level type representing a planned set of changes.
//
// A plan is a summary of the set of changes required to move from a current
// state to a goal state derived from configuration. The described changes
// are not applied directly, but contain an approximation of the final
// result that will be completed during apply by resolving any values that
// cannot be predicted.
//
// A plan must always be accompanied by the state and configuration it was
// built from, since the plan does not itself include all of the information
// required to make the changes indicated.
type Plan struct {
VariableValues map[string]DynamicValue
Changes *Changes
ProviderSHA256s map[string][]byte
}