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:
Mitchell Hashimoto 2014-06-20 10:33:26 -07:00
parent dc193f5f33
commit d2001275dc
4 changed files with 169 additions and 7 deletions

View File

@ -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
}

View File

@ -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)
}

63
terraform/plan.go Normal file
View File

@ -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)
}

60
terraform/plan_test.go Normal file
View File

@ -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)
}
}