From 3f090df26ea56c26ac6122a285e51056fcbd2cd1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 13 Sep 2016 10:56:37 -0700 Subject: [PATCH] terraform: start apply-based graph builder, basic diff transform --- terraform/graph_builder_apply.go | 44 +++++++++++++++++++++ terraform/graph_builder_apply_test.go | 57 +++++++++++++++++++++++++++ terraform/node_resource.go | 10 +++++ terraform/transform_diff.go | 53 +++++++++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 terraform/graph_builder_apply.go create mode 100644 terraform/graph_builder_apply_test.go create mode 100644 terraform/node_resource.go create mode 100644 terraform/transform_diff.go diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go new file mode 100644 index 000000000..f5f84d719 --- /dev/null +++ b/terraform/graph_builder_apply.go @@ -0,0 +1,44 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/config/module" +) + +// ApplyGraphBuilder implements GraphBuilder and is responsible for building +// a graph for applying a Terraform diff. +// +// Because the graph is built from the diff (vs. the config or state), +// this helps ensure that the apply-time graph doesn't modify any resources +// that aren't explicitly in the diff. There are other scenarios where the +// diff can be deviated, so this is just one layer of protection. +type ApplyGraphBuilder struct { + // Config is the root module for the graph to build. + Config *module.Tree + + // Diff is the diff to apply. + Diff *Diff + + // Providers is the list of providers supported. + Providers []string + + // Provisioners is the list of provisioners supported. + Provisioners []string +} + +// See GraphBuilder +func (b *ApplyGraphBuilder) Build(path []string) (*Graph, error) { + return (&BasicGraphBuilder{ + Steps: b.Steps(), + Validate: true, + }).Build(path) +} + +// See GraphBuilder +func (b *ApplyGraphBuilder) Steps() []GraphTransformer { + steps := []GraphTransformer{ + // Creates all the nodes represented in the diff. + &DiffTransformer{Diff: b.Diff}, + } + + return steps +} diff --git a/terraform/graph_builder_apply_test.go b/terraform/graph_builder_apply_test.go new file mode 100644 index 000000000..45d4c95bc --- /dev/null +++ b/terraform/graph_builder_apply_test.go @@ -0,0 +1,57 @@ +package terraform + +import ( + "reflect" + "strings" + "testing" +) + +func TestApplyGraphBuilder_impl(t *testing.T) { + var _ GraphBuilder = new(ApplyGraphBuilder) +} + +func TestApplyGraphBuilder(t *testing.T) { + diff := &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{ + Path: []string{"root"}, + Resources: map[string]*InstanceDiff{ + // Verify noop doesn't show up in graph + "aws_instance.noop": &InstanceDiff{}, + + "aws_instance.create": &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "name": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + }, + }, + }, + }, + }, + } + + b := &ApplyGraphBuilder{ + Diff: diff, + } + + g, err := b.Build(RootModulePath) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(g.Path, RootModulePath) { + t.Fatalf("bad: %#v", g.Path) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testApplyGraphBuilderStr) + if actual != expected { + t.Fatalf("bad: %s", actual) + } +} + +const testApplyGraphBuilderStr = ` +aws_instance.create +` diff --git a/terraform/node_resource.go b/terraform/node_resource.go new file mode 100644 index 000000000..86dd245f2 --- /dev/null +++ b/terraform/node_resource.go @@ -0,0 +1,10 @@ +package terraform + +// NodeResource is a graph node for referencing a resource. +type NodeResource struct { + Addr *ResourceAddress // Addr is the address for this resource +} + +func (n *NodeResource) Name() string { + return n.Addr.String() +} diff --git a/terraform/transform_diff.go b/terraform/transform_diff.go new file mode 100644 index 000000000..e51834629 --- /dev/null +++ b/terraform/transform_diff.go @@ -0,0 +1,53 @@ +package terraform + +import ( + "fmt" +) + +// DiffTransformer is a GraphTransformer that adds the elements of +// the diff to the graph. +// +// This transform is used for example by the ApplyGraphBuilder to ensure +// that only resources that are being modified are represented in the graph. +type DiffTransformer struct { + Diff *Diff +} + +func (t *DiffTransformer) Transform(g *Graph) error { + // If the diff is nil or empty (nil is empty) then do nothing + if t.Diff.Empty() { + return nil + } + + // Go through all the modules in the diff. + for _, m := range t.Diff.Modules { + // TODO: If this is a destroy diff then add a module destroy node + + // Go through all the resources in this module. + for name, inst := range m.Resources { + // TODO: Destroy diff + + // If this diff has no attribute changes, then we have + // nothing to do and therefore won't add it to the graph. + if len(inst.Attributes) == 0 { + continue + } + + // We have changes! This is a create or update operation. + // First grab the address so we have a unique way to + // reference this resource. + addr, err := parseResourceAddressInternal(name) + if err != nil { + return fmt.Errorf( + "Error parsing internal name, this is a bug: %q", name) + } + + // Add the resource to the graph + g.Add(&NodeResource{ + Addr: addr, + }) + } + } + + return nil +}