terraform: ProviderTransform to map resources to providers by dep

This commit is contained in:
Mitchell Hashimoto 2015-01-27 21:48:46 -08:00
parent e1d3f308a6
commit 01ec680019
7 changed files with 121 additions and 4 deletions

View File

@ -88,7 +88,7 @@ func (g *Graph) String() string {
names := make([]string, 0, len(g.vertices))
mapping := make(map[string]Vertex, len(g.vertices))
for _, v := range g.vertices {
name := vertName(v)
name := VertexName(v)
names = append(names, name)
mapping[name] = v
}
@ -104,7 +104,7 @@ func (g *Graph) String() string {
// Alphabetize dependencies
deps := make([]string, 0, targets.Len())
for _, target := range targets.List() {
deps = append(deps, vertName(target))
deps = append(deps, VertexName(target))
}
sort.Strings(deps)
@ -124,7 +124,8 @@ func (g *Graph) init() {
g.upEdges = make(map[Vertex]*set)
}
func vertName(raw Vertex) string {
// VertexName returns the name of a vertex.
func VertexName(raw Vertex) string {
switch v := raw.(type) {
case NamedVertex:
return v.Name()

View File

@ -62,7 +62,7 @@ func testSCCStr(list [][]Vertex) string {
for _, vs := range list {
result := make([]string, len(vs))
for i, v := range vs {
result[i] = vertName(v)
result[i] = VertexName(v)
}
buf.WriteString(fmt.Sprintf("%s\n", strings.Join(result, ",")))

View File

@ -73,6 +73,11 @@ func (n *GraphNodeConfigProvider) DependentOn() []string {
return result
}
// GraphNodeProvider implementation
func (n *GraphNodeConfigProvider) ProviderName() string {
return n.Provider.Name
}
// GraphNodeConfigResource represents a resource within the config graph.
type GraphNodeConfigResource struct {
Resource *config.Resource
@ -105,3 +110,8 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id()
}
// GraphNodeProviderConsumer
func (n *GraphNodeConfigResource) ProvidedBy() string {
return resourceProvider(n.Resource.Type)
}

View File

@ -0,0 +1,2 @@
provider "aws" {}
resource "aws_instance" "web" {}

View File

@ -0,0 +1,55 @@
package terraform
import (
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/dag"
)
// ProviderTransformer is a GraphTransformer that maps resources to
// providers within the graph. This will error if there are any resources
// that don't map to proper resources.
type ProviderTransformer struct{}
func (t *ProviderTransformer) Transform(g *Graph) error {
// First, build a map of the providers
m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvider); ok {
m[pv.ProviderName()] = v
}
}
// Go through the other nodes and match them to providers they need
var err error
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProviderConsumer); ok {
target := m[pv.ProvidedBy()]
if target == nil {
err = multierror.Append(err, fmt.Errorf(
"%s: provider %s couldn't be found",
dag.VertexName(v), pv.ProvidedBy()))
continue
}
g.Connect(dag.BasicEdge(v, target))
}
}
return err
}
// GraphNodeProvider is an interface that nodes that can be a provider
// must implement. The ProviderName returned is the name of the provider
// they satisfy.
type GraphNodeProvider interface {
ProviderName() string
}
// GraphNodeProviderConsumer is an interface that nodes that require
// a provider must implement. ProvidedBy must return the name of the provider
// to use.
type GraphNodeProviderConsumer interface {
ProvidedBy() string
}

View File

@ -0,0 +1,35 @@
package terraform
import (
"strings"
"testing"
)
func TestProviderTransformer(t *testing.T) {
mod := testModule(t, "transform-provider-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &ProviderTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformProviderBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformProviderBasicStr = `
aws_instance.web
provider.aws
provider.aws
`

View File

@ -1,5 +1,9 @@
package terraform
import (
"strings"
)
// Semaphore is a wrapper around a channel to provide
// utility methods to clarify that we are treating the
// channel as a semaphore
@ -42,6 +46,16 @@ func (s Semaphore) Release() {
}
}
// resourceProvider returns the provider name for the given type.
func resourceProvider(t string) string {
idx := strings.IndexRune(t, '_')
if idx == -1 {
return ""
}
return t[:idx]
}
// strSliceContains checks if a given string is contained in a slice
// When anybody asks why Go needs generics, here you go.
func strSliceContains(haystack []string, needle string) bool {