terraform: diff is split down into modules

This commit is contained in:
Mitchell Hashimoto 2014-09-23 11:43:21 -07:00
parent 29603f36d2
commit bc67e7c443
8 changed files with 176 additions and 87 deletions

View File

@ -831,7 +831,7 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc {
l.Lock()
if !diff.Empty() {
result.Diff.Resources[r.Id] = diff
result.Diff.RootModule().Resources[r.Id] = diff
}
l.Unlock()
@ -872,7 +872,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc {
l.Lock()
defer l.Unlock()
result.Diff.Resources[r.Id] = &InstanceDiff{Destroy: true}
result.Diff.RootModule().Resources[r.Id] = &InstanceDiff{Destroy: true}
} else {
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id)
}

View File

@ -1394,8 +1394,8 @@ func TestContextPlan(t *testing.T) {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources)
if len(plan.Diff.RootModule().Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
actual := strings.TrimSpace(plan.String())
@ -1458,8 +1458,8 @@ func TestContextPlan_nil(t *testing.T) {
if err != nil {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.Resources) != 0 {
t.Fatalf("bad: %#v", plan.Diff.Resources)
if len(plan.Diff.RootModule().Resources) != 0 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
}
@ -1525,8 +1525,8 @@ func TestContextPlan_count(t *testing.T) {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.Resources) < 6 {
t.Fatalf("bad: %#v", plan.Diff.Resources)
if len(plan.Diff.RootModule().Resources) < 6 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
actual := strings.TrimSpace(plan.String())
@ -1715,8 +1715,8 @@ func TestContextPlan_destroy(t *testing.T) {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.Resources) != 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources)
if len(plan.Diff.RootModule().Resources) != 2 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
actual := strings.TrimSpace(plan.String())
@ -1880,8 +1880,8 @@ func TestContextPlan_state(t *testing.T) {
t.Fatalf("err: %s", err)
}
if len(plan.Diff.Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources)
if len(plan.Diff.RootModule().Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
}
actual := strings.TrimSpace(plan.String())

View File

@ -1,29 +1,98 @@
package terraform
import (
"bufio"
"bytes"
"fmt"
"reflect"
"sort"
"strings"
"sync"
)
// Diff tracks the differences between resources to apply.
// Diff trackes the changes that are necessary to apply a configuration
// to an existing infrastructure.
type Diff struct {
Resources map[string]*InstanceDiff
once sync.Once
// Modules contains all the modules that have a diff
Modules []*ModuleDiff
}
// ModuleByPath is used to lookup the module diff for the given path.
// This should be the prefered lookup mechanism as it allows for future
// lookup optimizations.
func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
if d == nil {
return nil
}
for _, mod := range d.Modules {
if mod.Path == nil {
panic("missing module path")
}
if reflect.DeepEqual(mod.Path, path) {
return mod
}
}
return nil
}
// RootModule returns the ModuleState for the root module
func (d *Diff) RootModule() *ModuleDiff {
root := d.ModuleByPath(rootModulePath)
if root == nil {
panic("missing root module")
}
return root
}
func (d *Diff) String() string {
var buf bytes.Buffer
for _, m := range d.Modules {
mStr := m.String()
// If we're the root module, we just write the output directly.
if reflect.DeepEqual(m.Path, rootModulePath) {
buf.WriteString(mStr + "\n")
continue
}
buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], ".")))
s := bufio.NewScanner(strings.NewReader(mStr))
for s.Scan() {
buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
}
}
return strings.TrimSpace(buf.String())
}
func (d *Diff) init() {
d.once.Do(func() {
if d.Modules == nil {
rootDiff := &ModuleDiff{Path: rootModulePath}
d.Modules = []*ModuleDiff{rootDiff}
}
for _, m := range d.Modules {
m.init()
}
}
// ModuleDiff tracks the differences between resources to apply within
// a single module.
type ModuleDiff struct {
Path []string
Resources map[string]*InstanceDiff
}
func (d *ModuleDiff) init() {
if d.Resources == nil {
d.Resources = make(map[string]*InstanceDiff)
}
})
for _, r := range d.Resources {
r.init()
}
}
// Empty returns true if the diff has no changes.
func (d *Diff) Empty() bool {
// Empty returns true if the diff has no changes within this module.
func (d *ModuleDiff) Empty() bool {
if len(d.Resources) == 0 {
return true
}
@ -39,7 +108,7 @@ func (d *Diff) Empty() bool {
// String outputs the diff in a long but command-line friendly output
// format that users can read to quickly inspect a diff.
func (d *Diff) String() string {
func (d *ModuleDiff) String() string {
var buf bytes.Buffer
names := make([]string, 0, len(d.Resources))
@ -110,8 +179,6 @@ type InstanceDiff struct {
Attributes map[string]*ResourceAttrDiff
Destroy bool
DestroyTainted bool
once sync.Once
}
// ResourceAttrDiff is the diff of a single attribute of a resource.
@ -143,11 +210,9 @@ const (
)
func (d *InstanceDiff) init() {
d.once.Do(func() {
if d.Attributes == nil {
d.Attributes = make(map[string]*ResourceAttrDiff)
}
})
}
// Empty returns true if this diff encapsulates no changes.

View File

@ -5,8 +5,8 @@ import (
"testing"
)
func TestDiff_Empty(t *testing.T) {
diff := new(Diff)
func TestModuleDiff_Empty(t *testing.T) {
diff := new(ModuleDiff)
if !diff.Empty() {
t.Fatal("should be empty")
}
@ -38,8 +38,8 @@ func TestDiff_Empty(t *testing.T) {
}
}
func TestDiff_String(t *testing.T) {
diff := &Diff{
func TestModuleDiff_String(t *testing.T) {
diff := &ModuleDiff{
Resources: map[string]*InstanceDiff{
"nodeA": &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
@ -62,7 +62,7 @@ func TestDiff_String(t *testing.T) {
}
actual := strings.TrimSpace(diff.String())
expected := strings.TrimSpace(diffStrBasic)
expected := strings.TrimSpace(moduleDiffStrBasic)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
@ -206,7 +206,7 @@ func TestResourceDiffSame(t *testing.T) {
}
}
const diffStrBasic = `
const moduleDiffStrBasic = `
CREATE: nodeA
bar: "foo" => "<computed>"
foo: "foo" => "bar"

View File

@ -132,10 +132,14 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
config := currentModule.Config()
// Get the state of the module that we're working with.
var mod *ModuleState
// Get the state and diff of the module that we're working with.
var modDiff *ModuleDiff
var modState *ModuleState
if opts.Diff != nil {
modDiff = opts.Diff.ModuleByPath(opts.ModulePath)
}
if opts.State != nil {
mod = opts.State.ModuleByPath(opts.ModulePath)
modState = opts.State.ModuleByPath(opts.ModulePath)
}
log.Printf("[DEBUG] Creating graph...")
@ -145,7 +149,7 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
// First, build the initial resource graph. This only has the resources
// and no dependencies. This only adds resources that are in the config
// and not "orphans" (that are in the state, but not in the config).
graphAddConfigResources(g, config, mod)
graphAddConfigResources(g, config, modState)
// Add the modules that are in the configuration.
if err := graphAddConfigModules(g, config, opts); err != nil {
@ -155,12 +159,12 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
// Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g)
if mod != nil {
if modState != nil {
// Next, add the state orphans if we have any
graphAddOrphans(g, config, mod)
graphAddOrphans(g, config, modState)
// Add tainted resources if we have any.
graphAddTainted(g, mod)
graphAddTainted(g, modState)
}
// Map the provider configurations to all of the resources
@ -198,8 +202,8 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
}
// If we have a diff, then make sure to add that in
if opts.Diff != nil {
if err := graphAddDiff(g, opts.Diff); err != nil {
if modDiff != nil {
if err := graphAddDiff(g, modDiff); err != nil {
return nil, err
}
}
@ -423,7 +427,7 @@ func graphAddConfigResources(
// destroying the VPC's subnets first, whereas creating a VPC requires
// doing it before the subnets are created. This function handles inserting
// these nodes for you.
func graphAddDiff(g *depgraph.Graph, d *Diff) error {
func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
var nlist []*depgraph.Noun
for _, n := range g.Nouns {
rn, ok := n.Meta.(*GraphNodeResource)

View File

@ -357,6 +357,9 @@ func TestGraphProvisioners(t *testing.T) {
func TestGraphAddDiff(t *testing.T) {
m := testModule(t, "graph-diff")
diff := &Diff{
Modules: []*ModuleDiff{
&ModuleDiff{
Path: rootModulePath,
Resources: map[string]*InstanceDiff{
"aws_instance.foo": &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
@ -366,6 +369,8 @@ func TestGraphAddDiff(t *testing.T) {
},
},
},
},
},
}
g, err := Graph(&GraphOpts{Module: m, Diff: diff})
@ -383,7 +388,7 @@ func TestGraphAddDiff(t *testing.T) {
n := g.Noun("aws_instance.foo")
rn := n.Meta.(*GraphNodeResource)
expected2 := diff.Resources["aws_instance.foo"]
expected2 := diff.RootModule().Resources["aws_instance.foo"]
actual2 := rn.Resource.Diff
if !reflect.DeepEqual(actual2, expected2) {
t.Fatalf("bad: %#v", actual2)
@ -393,6 +398,9 @@ func TestGraphAddDiff(t *testing.T) {
func TestGraphAddDiff_destroy(t *testing.T) {
m := testModule(t, "graph-diff-destroy")
diff := &Diff{
Modules: []*ModuleDiff{
&ModuleDiff{
Path: rootModulePath,
Resources: map[string]*InstanceDiff{
"aws_instance.foo": &InstanceDiff{
Destroy: true,
@ -401,6 +409,8 @@ func TestGraphAddDiff_destroy(t *testing.T) {
Destroy: true,
},
},
},
},
}
state := &State{
Modules: []*ModuleState{
@ -463,6 +473,9 @@ func TestGraphAddDiff_destroy(t *testing.T) {
func TestGraphAddDiff_destroy_counts(t *testing.T) {
m := testModule(t, "graph-count")
diff := &Diff{
Modules: []*ModuleDiff{
&ModuleDiff{
Path: rootModulePath,
Resources: map[string]*InstanceDiff{
"aws_instance.web.0": &InstanceDiff{
Destroy: true,
@ -477,6 +490,8 @@ func TestGraphAddDiff_destroy_counts(t *testing.T) {
Destroy: true,
},
},
},
},
}
state := &State{
Modules: []*ModuleState{

View File

@ -55,7 +55,7 @@ func (p *Plan) String() string {
buf := new(bytes.Buffer)
buf.WriteString("DIFF:\n\n")
buf.WriteString(p.Diff.String())
buf.WriteString("\nSTATE:\n\n")
buf.WriteString("\n\nSTATE:\n\n")
buf.WriteString(p.State.String())
return buf.String()
}

View File

@ -11,6 +11,9 @@ func TestReadWritePlan(t *testing.T) {
plan := &Plan{
Config: testConfig(t, "new-good"),
Diff: &Diff{
Modules: []*ModuleDiff{
&ModuleDiff{
Path: rootModulePath,
Resources: map[string]*InstanceDiff{
"nodeA": &InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
@ -31,6 +34,8 @@ func TestReadWritePlan(t *testing.T) {
},
},
},
},
},
State: &State{
Modules: []*ModuleState{
&ModuleState{