terraform: connect providers in the apply graph

This commit is contained in:
Mitchell Hashimoto 2016-09-13 17:11:34 -07:00
parent 77b9177bd5
commit dc9b9eee44
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
6 changed files with 182 additions and 9 deletions

View File

@ -28,6 +28,11 @@ type Graph struct {
// RootModuleName // RootModuleName
Path []string Path []string
// annotations are the annotations that are added to vertices. Annotations
// are arbitrary metadata taht is used for various logic. Annotations
// should have unique keys that are referenced via constants.
annotations map[dag.Vertex]map[string]interface{}
// dependableMap is a lookaside table for fast lookups for connecting // dependableMap is a lookaside table for fast lookups for connecting
// dependencies by their GraphNodeDependable value to avoid O(n^3)-like // dependencies by their GraphNodeDependable value to avoid O(n^3)-like
// situations and turn them into O(1) with respect to the number of new // situations and turn them into O(1) with respect to the number of new
@ -37,6 +42,29 @@ type Graph struct {
once sync.Once once sync.Once
} }
// Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty.
//
// The returned map may be modified to modify the annotations of the
// vertex.
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
g.once.Do(g.init)
// If this vertex isn't in the graph, then just return an empty map
if !g.HasVertex(v) {
return map[string]interface{}{}
}
// Get the map, if it doesn't exist yet then initialize it
m, ok := g.annotations[v]
if !ok {
m = make(map[string]interface{})
g.annotations[v] = m
}
return m
}
// Add is the same as dag.Graph.Add. // Add is the same as dag.Graph.Add.
func (g *Graph) Add(v dag.Vertex) dag.Vertex { func (g *Graph) Add(v dag.Vertex) dag.Vertex {
g.once.Do(g.init) g.once.Do(g.init)
@ -51,6 +79,14 @@ func (g *Graph) Add(v dag.Vertex) dag.Vertex {
} }
} }
// If this initializes annotations, then do that
if av, ok := v.(GraphNodeAnnotationInit); ok {
as := g.Annotations(v)
for k, v := range av.AnnotationInit() {
as[k] = v
}
}
return v return v
} }
@ -65,12 +101,17 @@ func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
} }
} }
// Remove the annotations
delete(g.annotations, v)
// Call upwards to remove it from the actual graph // Call upwards to remove it from the actual graph
return g.Graph.Remove(v) return g.Graph.Remove(v)
} }
// Replace is the same as dag.Graph.Replace // Replace is the same as dag.Graph.Replace
func (g *Graph) Replace(o, n dag.Vertex) bool { func (g *Graph) Replace(o, n dag.Vertex) bool {
g.once.Do(g.init)
// Go through and update our lookaside to point to the new vertex // Go through and update our lookaside to point to the new vertex
for k, v := range g.dependableMap { for k, v := range g.dependableMap {
if v == o { if v == o {
@ -82,6 +123,12 @@ func (g *Graph) Replace(o, n dag.Vertex) bool {
} }
} }
// Move the annotation if it exists
if m, ok := g.annotations[o]; ok {
g.annotations[n] = m
delete(g.annotations, o)
}
return g.Graph.Replace(o, n) return g.Graph.Replace(o, n)
} }
@ -153,6 +200,10 @@ func (g *Graph) Walk(walker GraphWalker) error {
} }
func (g *Graph) init() { func (g *Graph) init() {
if g.annotations == nil {
g.annotations = make(map[dag.Vertex]map[string]interface{})
}
if g.dependableMap == nil { if g.dependableMap == nil {
g.dependableMap = make(map[string]dag.Vertex) g.dependableMap = make(map[string]dag.Vertex)
} }
@ -237,6 +288,16 @@ func (g *Graph) walk(walker GraphWalker) error {
return g.AcyclicGraph.Walk(walkFn) return g.AcyclicGraph.Walk(walkFn)
} }
// GraphNodeAnnotationInit is an interface that allows a node to
// initialize it's annotations.
//
// AnnotationInit will be called _once_ when the node is added to a
// graph for the first time and is expected to return it's initial
// annotations.
type GraphNodeAnnotationInit interface {
AnnotationInit() map[string]interface{}
}
// GraphNodeDependable is an interface which says that a node can be // GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according // depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName. // to the well-known name returned by DependableName.

View File

@ -39,6 +39,10 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
// Creates all the nodes represented in the diff. // Creates all the nodes represented in the diff.
&DiffTransformer{Diff: b.Diff}, &DiffTransformer{Diff: b.Diff},
// Create all the providers
&MissingProviderTransformer{Providers: b.Providers},
&ProviderTransformer{},
// Single root // Single root
&RootTransformer{}, &RootTransformer{},
} }

View File

@ -34,6 +34,7 @@ func TestApplyGraphBuilder(t *testing.T) {
b := &ApplyGraphBuilder{ b := &ApplyGraphBuilder{
Diff: diff, Diff: diff,
Providers: []string{"aws"},
} }
g, err := b.Build(RootModulePath) g, err := b.Build(RootModulePath)
@ -54,4 +55,6 @@ func TestApplyGraphBuilder(t *testing.T) {
const testApplyGraphBuilderStr = ` const testApplyGraphBuilderStr = `
aws_instance.create aws_instance.create
provider.aws
provider.aws
` `

View File

@ -1,10 +1,33 @@
package terraform package terraform
// NodeResource is a graph node for referencing a resource. import (
type NodeResource struct { "github.com/hashicorp/terraform/config"
)
// NodeApplyableResource represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
type NodeApplyableResource struct {
Addr *ResourceAddress // Addr is the address for this resource Addr *ResourceAddress // Addr is the address for this resource
Config *config.Resource // Config is the resource in the config
ResourceState *ResourceState // ResourceState is the ResourceState for this
} }
func (n *NodeResource) Name() string { func (n *NodeApplyableResource) Name() string {
return n.Addr.String() return n.Addr.String()
} }
// GraphNodeProviderConsumer
func (n *NodeApplyableResource) ProvidedBy() []string {
// If we have a config we prefer that above all else
if n.Config != nil {
return []string{resourceProvider(n.Config.Type, n.Config.Provider)}
}
// If we have state, then we will use the provider from there
if n.ResourceState != nil {
return []string{n.ResourceState.Provider}
}
// Use our type
return []string{resourceProvider(n.Addr.Type, "")}
}

View File

@ -85,6 +85,17 @@ func (r *ResourceAddress) String() string {
return strings.Join(result, ".") return strings.Join(result, ".")
} }
// parseResourceAddressConfig creates a resource address from a config.Resource
func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
return &ResourceAddress{
Type: r.Type,
Name: r.Name,
Index: -1,
InstanceType: TypePrimary,
Mode: r.Mode,
}, nil
}
// parseResourceAddressInternal parses the somewhat bespoke resource // parseResourceAddressInternal parses the somewhat bespoke resource
// identifier used in states and diffs, such as "instance.name.0". // identifier used in states and diffs, such as "instance.name.0".
func parseResourceAddressInternal(s string) (*ResourceAddress, error) { func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
@ -101,6 +112,7 @@ func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
Name: parts[1], Name: parts[1],
Index: -1, Index: -1,
InstanceType: TypePrimary, InstanceType: TypePrimary,
Mode: config.ManagedResourceMode,
} }
// If we have more parts, then we have an index. Parse that. // If we have more parts, then we have an index. Parse that.

View File

@ -2,6 +2,8 @@ package terraform
import ( import (
"fmt" "fmt"
"github.com/hashicorp/terraform/config/module"
) )
// DiffTransformer is a GraphTransformer that adds the elements of // DiffTransformer is a GraphTransformer that adds the elements of
@ -9,8 +11,16 @@ import (
// //
// This transform is used for example by the ApplyGraphBuilder to ensure // This transform is used for example by the ApplyGraphBuilder to ensure
// that only resources that are being modified are represented in the graph. // that only resources that are being modified are represented in the graph.
//
// Config and State is still required for the DiffTransformer for annotations
// since the Diff doesn't contain all the information required to build the
// complete graph (such as create-before-destroy information). The graph
// is built based on the diff first, though, ensuring that only resources
// that are being modified are present in the graph.
type DiffTransformer struct { type DiffTransformer struct {
Diff *Diff Diff *Diff
Config *module.Tree
State *State
} }
func (t *DiffTransformer) Transform(g *Graph) error { func (t *DiffTransformer) Transform(g *Graph) error {
@ -20,6 +30,7 @@ func (t *DiffTransformer) Transform(g *Graph) error {
} }
// Go through all the modules in the diff. // Go through all the modules in the diff.
var nodes []*NodeApplyableResource
for _, m := range t.Diff.Modules { for _, m := range t.Diff.Modules {
// TODO: If this is a destroy diff then add a module destroy node // TODO: If this is a destroy diff then add a module destroy node
@ -38,16 +49,75 @@ func (t *DiffTransformer) Transform(g *Graph) error {
// reference this resource. // reference this resource.
addr, err := parseResourceAddressInternal(name) addr, err := parseResourceAddressInternal(name)
if err != nil { if err != nil {
return fmt.Errorf( panic(fmt.Sprintf(
"Error parsing internal name, this is a bug: %q", name) "Error parsing internal name, this is a bug: %q", name))
} }
// Very important: add the module path for this resource to
// the address. Remove "root" from it.
addr.Path = m.Path[1:]
// Add the resource to the graph // Add the resource to the graph
g.Add(&NodeResource{ nodes = append(nodes, &NodeApplyableResource{
Addr: addr, Addr: addr,
}) })
} }
} }
// NOTE: Lots of room for performance optimizations below. For
// resource-heavy diffs this part alone is probably pretty slow.
// Annotate all nodes with their config and state
for _, n := range nodes {
// Grab the configuration at this path.
if t := t.Config.Child(n.Addr.Path); t != nil {
for _, r := range t.Config().Resources {
// Get a resource address so we can compare
addr, err := parseResourceAddressConfig(r)
if err != nil {
panic(fmt.Sprintf(
"Error parsing config address, this is a bug: %#v", r))
}
addr.Path = n.Addr.Path
// If this is not the same resource, then continue
if !addr.Equals(n.Addr) {
continue
}
// Same resource! Mark it and exit
n.Config = r
break
}
}
// Grab the state at this path
if ms := t.State.ModuleByPath(normalizeModulePath(n.Addr.Path)); ms != nil {
for name, rs := range ms.Resources {
// Parse the name for comparison
addr, err := parseResourceAddressInternal(name)
if err != nil {
panic(fmt.Sprintf(
"Error parsing internal name, this is a bug: %q", name))
}
addr.Path = n.Addr.Path
// If this is not the same resource, then continue
if !addr.Equals(n.Addr) {
continue
}
// Same resource!
n.ResourceState = rs
break
}
}
}
// Add all the nodes to the graph
for _, n := range nodes {
g.Add(n)
}
return nil return nil
} }