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() l.Lock()
if !diff.Empty() { if !diff.Empty() {
result.Diff.Resources[r.Id] = diff result.Diff.RootModule().Resources[r.Id] = diff
} }
l.Unlock() l.Unlock()
@ -872,7 +872,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
result.Diff.Resources[r.Id] = &InstanceDiff{Destroy: true} result.Diff.RootModule().Resources[r.Id] = &InstanceDiff{Destroy: true}
} else { } else {
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id) 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) t.Fatalf("err: %s", err)
} }
if len(plan.Diff.Resources) < 2 { if len(plan.Diff.RootModule().Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources) t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
} }
actual := strings.TrimSpace(plan.String()) actual := strings.TrimSpace(plan.String())
@ -1458,8 +1458,8 @@ func TestContextPlan_nil(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if len(plan.Diff.Resources) != 0 { if len(plan.Diff.RootModule().Resources) != 0 {
t.Fatalf("bad: %#v", plan.Diff.Resources) t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
} }
} }
@ -1525,8 +1525,8 @@ func TestContextPlan_count(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if len(plan.Diff.Resources) < 6 { if len(plan.Diff.RootModule().Resources) < 6 {
t.Fatalf("bad: %#v", plan.Diff.Resources) t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
} }
actual := strings.TrimSpace(plan.String()) actual := strings.TrimSpace(plan.String())
@ -1715,8 +1715,8 @@ func TestContextPlan_destroy(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if len(plan.Diff.Resources) != 2 { if len(plan.Diff.RootModule().Resources) != 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources) t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
} }
actual := strings.TrimSpace(plan.String()) actual := strings.TrimSpace(plan.String())
@ -1880,8 +1880,8 @@ func TestContextPlan_state(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if len(plan.Diff.Resources) < 2 { if len(plan.Diff.RootModule().Resources) < 2 {
t.Fatalf("bad: %#v", plan.Diff.Resources) t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources)
} }
actual := strings.TrimSpace(plan.String()) actual := strings.TrimSpace(plan.String())

View File

@ -1,29 +1,98 @@
package terraform package terraform
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"reflect"
"sort" "sort"
"strings" "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 { type Diff struct {
Resources map[string]*InstanceDiff // Modules contains all the modules that have a diff
once sync.Once 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() { 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 { if d.Resources == nil {
d.Resources = make(map[string]*InstanceDiff) d.Resources = make(map[string]*InstanceDiff)
} }
}) for _, r := range d.Resources {
r.init()
}
} }
// Empty returns true if the diff has no changes. // Empty returns true if the diff has no changes within this module.
func (d *Diff) Empty() bool { func (d *ModuleDiff) Empty() bool {
if len(d.Resources) == 0 { if len(d.Resources) == 0 {
return true return true
} }
@ -39,7 +108,7 @@ func (d *Diff) Empty() bool {
// String outputs the diff in a long but command-line friendly output // String outputs the diff in a long but command-line friendly output
// format that users can read to quickly inspect a diff. // format that users can read to quickly inspect a diff.
func (d *Diff) String() string { func (d *ModuleDiff) String() string {
var buf bytes.Buffer var buf bytes.Buffer
names := make([]string, 0, len(d.Resources)) names := make([]string, 0, len(d.Resources))
@ -110,8 +179,6 @@ type InstanceDiff struct {
Attributes map[string]*ResourceAttrDiff Attributes map[string]*ResourceAttrDiff
Destroy bool Destroy bool
DestroyTainted bool DestroyTainted bool
once sync.Once
} }
// ResourceAttrDiff is the diff of a single attribute of a resource. // ResourceAttrDiff is the diff of a single attribute of a resource.
@ -143,11 +210,9 @@ const (
) )
func (d *InstanceDiff) init() { func (d *InstanceDiff) init() {
d.once.Do(func() {
if d.Attributes == nil { if d.Attributes == nil {
d.Attributes = make(map[string]*ResourceAttrDiff) d.Attributes = make(map[string]*ResourceAttrDiff)
} }
})
} }
// Empty returns true if this diff encapsulates no changes. // Empty returns true if this diff encapsulates no changes.

View File

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

View File

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

View File

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

View File

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

View File

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