terraform: connect providers in the apply graph
This commit is contained in:
parent
77b9177bd5
commit
dc9b9eee44
|
@ -28,6 +28,11 @@ type Graph struct {
|
|||
// RootModuleName
|
||||
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
|
||||
// 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
|
||||
|
@ -37,6 +42,29 @@ type Graph struct {
|
|||
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.
|
||||
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
return g.Graph.Remove(v)
|
||||
}
|
||||
|
||||
// Replace is the same as dag.Graph.Replace
|
||||
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
|
||||
for k, v := range g.dependableMap {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -153,6 +200,10 @@ func (g *Graph) Walk(walker GraphWalker) error {
|
|||
}
|
||||
|
||||
func (g *Graph) init() {
|
||||
if g.annotations == nil {
|
||||
g.annotations = make(map[dag.Vertex]map[string]interface{})
|
||||
}
|
||||
|
||||
if g.dependableMap == nil {
|
||||
g.dependableMap = make(map[string]dag.Vertex)
|
||||
}
|
||||
|
@ -237,6 +288,16 @@ func (g *Graph) walk(walker GraphWalker) error {
|
|||
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
|
||||
// depended on (an edge can be placed between this node and another) according
|
||||
// to the well-known name returned by DependableName.
|
||||
|
|
|
@ -39,6 +39,10 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||
// Creates all the nodes represented in the diff.
|
||||
&DiffTransformer{Diff: b.Diff},
|
||||
|
||||
// Create all the providers
|
||||
&MissingProviderTransformer{Providers: b.Providers},
|
||||
&ProviderTransformer{},
|
||||
|
||||
// Single root
|
||||
&RootTransformer{},
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ func TestApplyGraphBuilder(t *testing.T) {
|
|||
|
||||
b := &ApplyGraphBuilder{
|
||||
Diff: diff,
|
||||
Providers: []string{"aws"},
|
||||
}
|
||||
|
||||
g, err := b.Build(RootModulePath)
|
||||
|
@ -54,4 +55,6 @@ func TestApplyGraphBuilder(t *testing.T) {
|
|||
|
||||
const testApplyGraphBuilderStr = `
|
||||
aws_instance.create
|
||||
provider.aws
|
||||
provider.aws
|
||||
`
|
||||
|
|
|
@ -1,10 +1,33 @@
|
|||
package terraform
|
||||
|
||||
// NodeResource is a graph node for referencing a resource.
|
||||
type NodeResource struct {
|
||||
import (
|
||||
"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
|
||||
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()
|
||||
}
|
||||
|
||||
// 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, "")}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,17 @@ func (r *ResourceAddress) String() string {
|
|||
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
|
||||
// identifier used in states and diffs, such as "instance.name.0".
|
||||
func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
|
||||
|
@ -101,6 +112,7 @@ func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
|
|||
Name: parts[1],
|
||||
Index: -1,
|
||||
InstanceType: TypePrimary,
|
||||
Mode: config.ManagedResourceMode,
|
||||
}
|
||||
|
||||
// If we have more parts, then we have an index. Parse that.
|
||||
|
|
|
@ -2,6 +2,8 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
Diff *Diff
|
||||
Config *module.Tree
|
||||
State *State
|
||||
}
|
||||
|
||||
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.
|
||||
var nodes []*NodeApplyableResource
|
||||
for _, m := range t.Diff.Modules {
|
||||
// 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.
|
||||
addr, err := parseResourceAddressInternal(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error parsing internal name, this is a bug: %q", name)
|
||||
panic(fmt.Sprintf(
|
||||
"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
|
||||
g.Add(&NodeResource{
|
||||
nodes = append(nodes, &NodeApplyableResource{
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue