terraform: initial Plan structure
This is REALLY heavy and would be really hard to maintain any sort of compatibility with, but it is what we're going to do during dev initially (if we don't ship with it) in order to just get stuff working.
This commit is contained in:
parent
dc193f5f33
commit
d2001275dc
|
@ -1,6 +1,9 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
@ -31,16 +34,12 @@ type RawConfig struct {
|
||||||
// NewRawConfig creates a new RawConfig structure and populates the
|
// NewRawConfig creates a new RawConfig structure and populates the
|
||||||
// publicly readable struct fields.
|
// publicly readable struct fields.
|
||||||
func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
|
func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
|
||||||
walker := new(variableDetectWalker)
|
result := &RawConfig{Raw: raw}
|
||||||
if err := reflectwalk.Walk(raw, walker); err != nil {
|
if err := result.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RawConfig{
|
return result, nil
|
||||||
Raw: raw,
|
|
||||||
Variables: walker.Variables,
|
|
||||||
config: raw,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config returns the entire configuration with the variables
|
// Config returns the entire configuration with the variables
|
||||||
|
@ -82,8 +81,42 @@ func (r *RawConfig) Interpolate(vs map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RawConfig) init() error {
|
||||||
|
walker := new(variableDetectWalker)
|
||||||
|
if err := reflectwalk.Walk(r.Raw, walker); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Variables = walker.Variables
|
||||||
|
r.config = r.Raw
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnknownKeys returns the keys of the configuration that are unknown
|
// UnknownKeys returns the keys of the configuration that are unknown
|
||||||
// because they had interpolated variables that must be computed.
|
// because they had interpolated variables that must be computed.
|
||||||
func (r *RawConfig) UnknownKeys() []string {
|
func (r *RawConfig) UnknownKeys() []string {
|
||||||
return r.unknownKeys
|
return r.unknownKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See GobEncode
|
||||||
|
func (r *RawConfig) GobDecode(b []byte) error {
|
||||||
|
err := gob.NewDecoder(bytes.NewReader(b)).Decode(&r.Raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode is a custom Gob encoder to use so that we only include the
|
||||||
|
// raw configuration. Interpolated variables and such are lost and the
|
||||||
|
// tree of interpolated variables is recomputed on decode, since it is
|
||||||
|
// referentially transparent.
|
||||||
|
func (r *RawConfig) GobEncode() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := gob.NewEncoder(&buf).Encode(r.Raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -119,3 +120,8 @@ func TestRawConfig_unknown(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
t.Fatalf("bad: %#v", rc.UnknownKeys())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRawConfig_implGob(t *testing.T) {
|
||||||
|
var _ gob.GobDecoder = new(RawConfig)
|
||||||
|
var _ gob.GobEncoder = new(RawConfig)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plan represents a single Terraform execution plan, which contains
|
||||||
|
// all the information necessary to make an infrastructure change.
|
||||||
|
type Plan struct {
|
||||||
|
Config *config.Config
|
||||||
|
Diff *Diff
|
||||||
|
State *State
|
||||||
|
Vars map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// The format byte is prefixed into the plan file format so that we have
|
||||||
|
// the ability in the future to change the file format if we want for any
|
||||||
|
// reason.
|
||||||
|
const planFormatByte byte = 1
|
||||||
|
|
||||||
|
// ReadPlan reads a plan structure out of a reader in the format that
|
||||||
|
// was written by WritePlan.
|
||||||
|
func ReadPlan(src io.Reader) (*Plan, error) {
|
||||||
|
var result *Plan
|
||||||
|
|
||||||
|
var formatByte [1]byte
|
||||||
|
n, err := src.Read(formatByte[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != len(formatByte) {
|
||||||
|
return nil, errors.New("failed to read plan version byte")
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatByte[0] != planFormatByte {
|
||||||
|
return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := gob.NewDecoder(src)
|
||||||
|
if err := dec.Decode(&result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePlan writes a plan somewhere in a binary format.
|
||||||
|
func WritePlan(d *Plan, dst io.Writer) error {
|
||||||
|
n, err := dst.Write([]byte{planFormatByte})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
return errors.New("failed to write plan version byte")
|
||||||
|
}
|
||||||
|
|
||||||
|
return gob.NewEncoder(dst).Encode(d)
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadWritePlan(t *testing.T) {
|
||||||
|
tf := testTerraform(t, "new-good")
|
||||||
|
plan := &Plan{
|
||||||
|
Config: tf.config,
|
||||||
|
Diff: &Diff{
|
||||||
|
Resources: map[string]*ResourceDiff{
|
||||||
|
"nodeA": &ResourceDiff{
|
||||||
|
Attributes: map[string]*ResourceAttrDiff{
|
||||||
|
"foo": &ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
"bar": &ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
NewComputed: true,
|
||||||
|
},
|
||||||
|
"longfoo": &ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
RequiresNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
State: &State{
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"foo": &ResourceState{
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Vars: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := WritePlan(plan, buf); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := ReadPlan(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, plan) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue