terraform: module inputs are passed through to subgraphs
This commit is contained in:
parent
5595229430
commit
23d097ee53
|
@ -118,6 +118,9 @@ func (c *Context2) Plan(opts *PlanOpts) (*Plan, error) {
|
|||
|
||||
// Do the walk
|
||||
walker, err := c.walk(operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Diff = walker.Diff
|
||||
|
||||
// Update the diff so that our context is up-to-date
|
||||
|
|
|
@ -109,12 +109,11 @@ func TestContext2Plan_modules(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestContextPlan_moduleInput(t *testing.T) {
|
||||
func TestContext2Plan_moduleInput(t *testing.T) {
|
||||
m := testModule(t, "plan-module-input")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
|
@ -133,6 +132,7 @@ func TestContextPlan_moduleInput(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestContextPlan_moduleInputComputed(t *testing.T) {
|
||||
m := testModule(t, "plan-module-input-computed")
|
||||
p := testProvider("aws")
|
||||
|
|
|
@ -49,6 +49,11 @@ type EvalContext interface {
|
|||
// that is currently being acted upon.
|
||||
Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error)
|
||||
|
||||
// SetVariables sets the variables for interpolation. These variables
|
||||
// should not have a "var." prefix. For example: "var.foo" should be
|
||||
// "foo" as the key.
|
||||
SetVariables(map[string]string)
|
||||
|
||||
// Diff returns the global diff as well as the lock that should
|
||||
// be used to modify that diff.
|
||||
Diff() (*Diff, *sync.RWMutex)
|
||||
|
@ -100,6 +105,9 @@ type MockEvalContext struct {
|
|||
PathCalled bool
|
||||
PathPath []string
|
||||
|
||||
SetVariablesCalled bool
|
||||
SetVariablesVariables map[string]string
|
||||
|
||||
DiffCalled bool
|
||||
DiffDiff *Diff
|
||||
DiffLock *sync.RWMutex
|
||||
|
@ -164,6 +172,11 @@ func (c *MockEvalContext) Path() []string {
|
|||
return c.PathPath
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) SetVariables(vs map[string]string) {
|
||||
c.SetVariablesCalled = true
|
||||
c.SetVariablesVariables = vs
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
|
||||
c.DiffCalled = true
|
||||
return c.DiffDiff, c.DiffLock
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
|
@ -72,7 +70,7 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx.ProviderCache[ctx.pathCacheKey(ctx.Path())] = p
|
||||
ctx.ProviderCache[PathCacheKey(ctx.Path())] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
|
@ -82,7 +80,7 @@ func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
|
|||
ctx.ProviderLock.Lock()
|
||||
defer ctx.ProviderLock.Unlock()
|
||||
|
||||
return ctx.ProviderCache[ctx.pathCacheKey(ctx.Path())]
|
||||
return ctx.ProviderCache[PathCacheKey(ctx.Path())]
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) ConfigureProvider(
|
||||
|
@ -94,7 +92,7 @@ func (ctx *BuiltinEvalContext) ConfigureProvider(
|
|||
|
||||
// Save the configuration
|
||||
ctx.ProviderLock.Lock()
|
||||
ctx.ProviderConfigCache[ctx.pathCacheKey(ctx.Path())] = cfg
|
||||
ctx.ProviderConfigCache[PathCacheKey(ctx.Path())] = cfg
|
||||
ctx.ProviderLock.Unlock()
|
||||
|
||||
return p.Configure(cfg)
|
||||
|
@ -106,7 +104,7 @@ func (ctx *BuiltinEvalContext) ParentProviderConfig(n string) *ResourceConfig {
|
|||
|
||||
path := ctx.Path()
|
||||
for i := len(path) - 1; i >= 1; i-- {
|
||||
k := ctx.pathCacheKey(path[:i])
|
||||
k := PathCacheKey(path[:i])
|
||||
if v, ok := ctx.ProviderConfigCache[k]; ok {
|
||||
return v
|
||||
}
|
||||
|
@ -139,7 +137,7 @@ func (ctx *BuiltinEvalContext) InitProvisioner(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx.ProvisionerCache[ctx.pathCacheKey(ctx.Path())] = p
|
||||
ctx.ProvisionerCache[PathCacheKey(ctx.Path())] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
|
@ -149,7 +147,7 @@ func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
|
|||
ctx.ProvisionerLock.Lock()
|
||||
defer ctx.ProvisionerLock.Unlock()
|
||||
|
||||
return ctx.ProvisionerCache[ctx.pathCacheKey(ctx.Path())]
|
||||
return ctx.ProvisionerCache[PathCacheKey(ctx.Path())]
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) Interpolate(
|
||||
|
@ -179,6 +177,12 @@ func (ctx *BuiltinEvalContext) Path() []string {
|
|||
return ctx.PathValue
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) SetVariables(vs map[string]string) {
|
||||
for k, v := range vs {
|
||||
ctx.Interpolater.Variables[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) {
|
||||
return ctx.DiffValue, ctx.DiffLock
|
||||
}
|
||||
|
@ -194,23 +198,3 @@ func (ctx *BuiltinEvalContext) init() {
|
|||
ctx.Providers = make(map[string]ResourceProviderFactory)
|
||||
}
|
||||
}
|
||||
|
||||
// pathCacheKey returns a cache key for the current module path, unique to
|
||||
// the module path.
|
||||
//
|
||||
// This is used because there is a variety of information that needs to be
|
||||
// cached per-path, rather than per-context.
|
||||
func (ctx *BuiltinEvalContext) pathCacheKey(path []string) string {
|
||||
// There is probably a better way to do this, but this is working for now.
|
||||
// We just create an MD5 hash of all the MD5 hashes of all the path
|
||||
// elements. This gets us the property that it is unique per ordering.
|
||||
hash := md5.New()
|
||||
for _, p := range path {
|
||||
single := md5.Sum([]byte(p))
|
||||
if _, err := hash.Write(single[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
// EvalSetVariables is an EvalNode implementation that sets the variables
|
||||
// explicitly for interpolation later.
|
||||
type EvalSetVariables struct {
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
func (n *EvalSetVariables) Args() ([]EvalNode, []EvalType) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalSetVariables) Eval(
|
||||
ctx EvalContext, args []interface{}) (interface{}, error) {
|
||||
ctx.SetVariables(n.Variables)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *EvalSetVariables) Type() EvalType {
|
||||
return EvalTypeNull
|
||||
}
|
||||
|
||||
// EvalVariableBlock is an EvalNode implementation that evaluates the
|
||||
// given configuration, and uses the final values as a way to set the
|
||||
// mapping.
|
||||
type EvalVariableBlock struct {
|
||||
Config EvalNode
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
func (n *EvalVariableBlock) Args() ([]EvalNode, []EvalType) {
|
||||
return []EvalNode{n.Config}, []EvalType{EvalTypeConfig}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalVariableBlock) Eval(
|
||||
ctx EvalContext, args []interface{}) (interface{}, error) {
|
||||
// Clear out the existing mapping
|
||||
for k, _ := range n.Variables {
|
||||
delete(n.Variables, k)
|
||||
}
|
||||
|
||||
// Get our configuration
|
||||
rc := args[0].(*ResourceConfig)
|
||||
for k, v := range rc.Config {
|
||||
n.Variables[k] = v.(string)
|
||||
}
|
||||
for k, _ := range rc.Raw {
|
||||
if _, ok := n.Variables[k]; !ok {
|
||||
n.Variables[k] = config.UnknownVariableValue
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *EvalVariableBlock) Type() EvalType {
|
||||
return EvalTypeNull
|
||||
}
|
|
@ -48,8 +48,26 @@ func (n *GraphNodeConfigModule) Name() string {
|
|||
}
|
||||
|
||||
// GraphNodeExpandable
|
||||
func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (*Graph, error) {
|
||||
return b.Build(n.Path)
|
||||
func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error) {
|
||||
// Build the graph first
|
||||
graph, err := b.Build(n.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add the parameters node to the module
|
||||
t := &ModuleInputTransformer{Variables: make(map[string]string)}
|
||||
if err := t.Transform(graph); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the actual subgraph node
|
||||
return &graphNodeModuleExpanded{
|
||||
Original: n,
|
||||
Graph: graph,
|
||||
InputConfig: n.Module.RawConfig,
|
||||
Variables: t.Variables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GraphNodeExpandable
|
||||
|
@ -181,3 +199,34 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// graphNodeModuleExpanded represents a module where the graph has
|
||||
// been expanded. It stores the graph of the module as well as a reference
|
||||
// to the map of variables.
|
||||
type graphNodeModuleExpanded struct {
|
||||
Original dag.Vertex
|
||||
Graph *Graph
|
||||
InputConfig *config.RawConfig
|
||||
|
||||
// Variables is a map of the input variables. This reference should
|
||||
// be shared with ModuleInputTransformer in order to create a connection
|
||||
// where the variables are set properly.
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleExpanded) Name() string {
|
||||
return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original))
|
||||
}
|
||||
|
||||
// GraphNodeEvalable impl.
|
||||
func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
|
||||
return &EvalVariableBlock{
|
||||
Config: &EvalInterpolate{Config: n.InputConfig},
|
||||
Variables: n.Variables,
|
||||
}
|
||||
}
|
||||
|
||||
// GraphNodeSubgraph impl.
|
||||
func (n *graphNodeModuleExpanded) Subgraph() *Graph {
|
||||
return n.Graph
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
actual := strings.TrimSpace(g.Subgraph().String())
|
||||
expected := strings.TrimSpace(testGraphNodeModuleExpandStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
|
|
|
@ -26,6 +26,8 @@ type ContextGraphWalker struct {
|
|||
errorLock sync.Mutex
|
||||
once sync.Once
|
||||
diffLock sync.RWMutex
|
||||
contexts map[string]*BuiltinEvalContext
|
||||
contextLock sync.Mutex
|
||||
providerCache map[string]ResourceProvider
|
||||
providerConfigCache map[string]*ResourceConfig
|
||||
providerLock sync.Mutex
|
||||
|
@ -36,7 +38,25 @@ type ContextGraphWalker struct {
|
|||
func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
|
||||
w.once.Do(w.init)
|
||||
|
||||
return &BuiltinEvalContext{
|
||||
w.contextLock.Lock()
|
||||
defer w.contextLock.Unlock()
|
||||
|
||||
// If we already have a context for this path cached, use that
|
||||
key := PathCacheKey(g.Path)
|
||||
if ctx, ok := w.contexts[key]; ok {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Variables should be our context variables, but these only apply
|
||||
// to the root module. As we enter subgraphs, we don't want to set
|
||||
// variables, which is set by the SetVariables EvalContext function.
|
||||
variables := w.Context.variables
|
||||
if len(g.Path) > 1 {
|
||||
// We're in a submodule, the variables should be empty
|
||||
variables = make(map[string]string)
|
||||
}
|
||||
|
||||
ctx := &BuiltinEvalContext{
|
||||
PathValue: g.Path,
|
||||
Hooks: w.Context.hooks,
|
||||
Providers: w.Context.providers,
|
||||
|
@ -55,9 +75,12 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
|
|||
Module: w.Context.module,
|
||||
State: w.Context.state,
|
||||
StateLock: &w.Context.stateLock,
|
||||
Variables: w.Context.variables,
|
||||
Variables: variables,
|
||||
},
|
||||
}
|
||||
|
||||
w.contexts[key] = ctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode {
|
||||
|
@ -94,6 +117,7 @@ func (w *ContextGraphWalker) init() {
|
|||
w.Diff = new(Diff)
|
||||
w.Diff.init()
|
||||
|
||||
w.contexts = make(map[string]*BuiltinEvalContext, 5)
|
||||
w.providerCache = make(map[string]ResourceProvider, 5)
|
||||
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
||||
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// PathCacheKey returns a cache key for a module path.
|
||||
//
|
||||
// TODO: test
|
||||
func PathCacheKey(path []string) string {
|
||||
// There is probably a better way to do this, but this is working for now.
|
||||
// We just create an MD5 hash of all the MD5 hashes of all the path
|
||||
// elements. This gets us the property that it is unique per ordering.
|
||||
hash := md5.New()
|
||||
for _, p := range path {
|
||||
single := md5.Sum([]byte(p))
|
||||
if _, err := hash.Write(single[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
|
@ -10,7 +8,7 @@ import (
|
|||
// signal that they can be expanded. Expanded nodes turn into
|
||||
// GraphNodeSubgraph nodes within the graph.
|
||||
type GraphNodeExpandable interface {
|
||||
Expand(GraphBuilder) (*Graph, error)
|
||||
Expand(GraphBuilder) (GraphNodeSubgraph, error)
|
||||
}
|
||||
|
||||
// GraphNodeDynamicExpandable is an interface that nodes can implement
|
||||
|
@ -43,27 +41,5 @@ func (t *ExpandTransform) Transform(v dag.Vertex) (dag.Vertex, error) {
|
|||
}
|
||||
|
||||
// Expand the subgraph!
|
||||
g, err := ev.Expand(t.Builder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Replace with our special node
|
||||
return &graphNodeExpanded{
|
||||
Graph: g,
|
||||
OriginalName: dag.VertexName(v),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type graphNodeExpanded struct {
|
||||
Graph *Graph
|
||||
OriginalName string
|
||||
}
|
||||
|
||||
func (n *graphNodeExpanded) Name() string {
|
||||
return fmt.Sprintf("%s (expanded)", n.OriginalName)
|
||||
}
|
||||
|
||||
func (n *graphNodeExpanded) Subgraph() *Graph {
|
||||
return n.Graph
|
||||
return ev.Expand(t.Builder)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// ModuleInputTransformer is a GraphTransformer that adds a node to the
|
||||
// graph for setting the module input variables for the remainder of the
|
||||
// graph.
|
||||
type ModuleInputTransformer struct {
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
func (t *ModuleInputTransformer) Transform(g *Graph) error {
|
||||
// Create the node
|
||||
n := &graphNodeModuleInput{Variables: t.Variables}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(n)
|
||||
|
||||
// Connect the inputs to the bottom of the graph so that it happens
|
||||
// first.
|
||||
for _, v := range g.Vertices() {
|
||||
if v == n {
|
||||
continue
|
||||
}
|
||||
|
||||
if g.DownEdges(v).Len() == 0 {
|
||||
g.Connect(dag.BasicEdge(v, n))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type graphNodeModuleInput struct {
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleInput) Name() string {
|
||||
return "module inputs"
|
||||
}
|
||||
|
||||
// GraphNodeEvalable impl.
|
||||
func (n *graphNodeModuleInput) EvalTree() EvalNode {
|
||||
return &EvalSetVariables{Variables: n.Variables}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
func TestModuleInputTransformer(t *testing.T) {
|
||||
var g Graph
|
||||
g.Add(1)
|
||||
g.Add(2)
|
||||
g.Add(3)
|
||||
g.Connect(dag.BasicEdge(1, 2))
|
||||
g.Connect(dag.BasicEdge(1, 3))
|
||||
|
||||
{
|
||||
tf := &ModuleInputTransformer{}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testModuleInputTransformStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testModuleInputTransformStr = `
|
||||
1
|
||||
2
|
||||
3
|
||||
2
|
||||
module inputs
|
||||
3
|
||||
module inputs
|
||||
module inputs
|
||||
`
|
Loading…
Reference in New Issue