terraform: provisioner transforms

This commit is contained in:
Mitchell Hashimoto 2015-02-09 10:14:09 -08:00
parent 31f6b7474d
commit ea42deb66c
6 changed files with 245 additions and 0 deletions

View File

@ -67,19 +67,29 @@ func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) {
// to build a complete graph. // to build a complete graph.
func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
return []GraphTransformer{ return []GraphTransformer{
// Create all our resources from the configuration and state
&ConfigTransformer{Module: b.Root}, &ConfigTransformer{Module: b.Root},
&OrphanTransformer{State: b.State, Module: b.Root}, &OrphanTransformer{State: b.State, Module: b.Root},
&TaintedTransformer{State: b.State}, &TaintedTransformer{State: b.State},
// Provider-related transformations
&MissingProviderTransformer{Providers: b.Providers}, &MissingProviderTransformer{Providers: b.Providers},
&ProviderTransformer{}, &ProviderTransformer{},
&PruneProviderTransformer{}, &PruneProviderTransformer{},
// Provisioner-related transformations
// Run our vertex-level transforms
&VertexTransformer{ &VertexTransformer{
Transforms: []GraphVertexTransformer{ Transforms: []GraphVertexTransformer{
// Expand any statically expanded nodes, such as module graphs
&ExpandTransform{ &ExpandTransform{
Builder: b, Builder: b,
}, },
}, },
}, },
// Make sure we create one root
&RootTransformer{}, &RootTransformer{},
} }
} }

View File

@ -147,3 +147,13 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode {
func (n *GraphNodeConfigResource) ProvidedBy() string { func (n *GraphNodeConfigResource) ProvidedBy() string {
return resourceProvider(n.Resource.Type) return resourceProvider(n.Resource.Type)
} }
// GraphNodeProvisionerConsumer
func (n *GraphNodeConfigResource) ProvisionedBy() []string {
result := make([]string, len(n.Resource.Provisioners))
for i, p := range n.Resource.Provisioners {
result[i] = p.Type
}
return result
}

View File

@ -0,0 +1,3 @@
resource "aws_instance" "web" {
provisioner "shell" {}
}

View File

@ -0,0 +1,3 @@
resource "aws_instance" "web" {
provisioner "foo" {}
}

View File

@ -0,0 +1,124 @@
package terraform
import (
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeProvisioner is an interface that nodes that can be a provisioner
// must implement. The ProvisionerName returned is the name of the provisioner
// they satisfy.
type GraphNodeProvisioner interface {
ProvisionerName() string
}
// GraphNodeProvisionerConsumer is an interface that nodes that require
// a provisioner must implement. ProvisionedBy must return the name of the
// provisioner to use.
type GraphNodeProvisionerConsumer interface {
ProvisionedBy() []string
}
// ProvisionerTransformer is a GraphTransformer that maps resources to
// provisioners within the graph. This will error if there are any resources
// that don't map to proper resources.
type ProvisionerTransformer struct{}
func (t *ProvisionerTransformer) Transform(g *Graph) error {
// Go through the other nodes and match them to provisioners they need
var err error
m := provisionerVertexMap(g)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvisionerConsumer); ok {
for _, provisionerName := range pv.ProvisionedBy() {
target := m[provisionerName]
if target == nil {
err = multierror.Append(err, fmt.Errorf(
"%s: provisioner %s couldn't be found",
dag.VertexName(v), provisionerName))
continue
}
g.Connect(dag.BasicEdge(v, target))
}
}
}
return err
}
// MissingProvisionerTransformer is a GraphTransformer that adds nodes
// for missing provisioners into the graph. Specifically, it creates provisioner
// configuration nodes for all the provisioners that we support. These are
// pruned later during an optimization pass.
type MissingProvisionerTransformer struct {
// Provisioners is the list of provisioners we support.
Provisioners []string
}
func (t *MissingProvisionerTransformer) Transform(g *Graph) error {
m := provisionerVertexMap(g)
for _, p := range t.Provisioners {
if _, ok := m[p]; ok {
// This provisioner already exists as a configured node
continue
}
// Add our own missing provisioner node to the graph
g.Add(&graphNodeMissingProvisioner{ProvisionerNameValue: p})
}
return nil
}
// PruneProvisionerTransformer is a GraphTransformer that prunes all the
// provisioners that aren't needed from the graph. A provisioner is unneeded if
// no resource or module is using that provisioner.
type PruneProvisionerTransformer struct{}
func (t *PruneProvisionerTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// We only care about the provisioners
if _, ok := v.(GraphNodeProvisioner); !ok {
continue
}
// Does anything depend on this? If not, then prune it.
if s := g.UpEdges(v); s.Len() == 0 {
g.Remove(v)
}
}
return nil
}
type graphNodeMissingProvisioner struct {
ProvisionerNameValue string
}
func (n *graphNodeMissingProvisioner) Name() string {
return fmt.Sprintf("provisioner.%s", n.ProvisionerNameValue)
}
// GraphNodeEvalable impl.
func (n *graphNodeMissingProvisioner) EvalTree() EvalNode {
return nil
//return ProvisionerEvalTree(n.ProvisionerNameValue, nil)
}
func (n *graphNodeMissingProvisioner) ProvisionerName() string {
return n.ProvisionerNameValue
}
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvisioner); ok {
m[pv.ProvisionerName()] = v
}
}
return m
}

View File

@ -0,0 +1,95 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestMissingProvisionerTransformer(t *testing.T) {
mod := testModule(t, "transform-provisioner-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
transform := &MissingProvisionerTransformer{Provisioners: []string{"foo"}}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformMissingProvisionerBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestPruneProvisionerTransformer(t *testing.T) {
mod := testModule(t, "transform-provisioner-prune")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &MissingProvisionerTransformer{
Provisioners: []string{"foo", "bar"}}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &ProvisionerTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
transform := &PruneProvisionerTransformer{}
if err := transform.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformPruneProvisionerBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestGraphNodeMissingProvisioner_impl(t *testing.T) {
var _ dag.Vertex = new(graphNodeMissingProvisioner)
var _ dag.NamedVertex = new(graphNodeMissingProvisioner)
var _ GraphNodeProvisioner = new(graphNodeMissingProvisioner)
}
func TestGraphNodeMissingProvisioner_ProvisionerName(t *testing.T) {
n := &graphNodeMissingProvisioner{ProvisionerNameValue: "foo"}
if v := n.ProvisionerName(); v != "foo" {
t.Fatalf("bad: %#v", v)
}
}
const testTransformMissingProvisionerBasicStr = `
aws_instance.web
provisioner.foo
`
const testTransformPruneProvisionerBasicStr = `
aws_instance.web
provisioner.foo
provisioner.foo
`