terraform: ResourceTransformer to ResourceTransformerOld

This commit is contained in:
Mitchell Hashimoto 2016-11-06 10:07:17 -08:00
parent d7aa59be3c
commit 4cdaf6f687
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
5 changed files with 241 additions and 204 deletions

View File

@ -156,7 +156,7 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error)
steps := make([]GraphTransformer, 0, 5) steps := make([]GraphTransformer, 0, 5)
// Expand counts. // Expand counts.
steps = append(steps, &ResourceCountTransformer{ steps = append(steps, &ResourceCountTransformerOld{
Resource: n.Resource, Resource: n.Resource,
Destroy: n.Destroy, Destroy: n.Destroy,
Targets: n.Targets, Targets: n.Targets,

View File

@ -1,11 +1,5 @@
package terraform package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
// NodePlannableResource represents a resource that is "plannable": // NodePlannableResource represents a resource that is "plannable":
// it is ready to be planned in order to create a diff. // it is ready to be planned in order to create a diff.
type NodePlannableResource struct { type NodePlannableResource struct {
@ -14,194 +8,41 @@ type NodePlannableResource struct {
// GraphNodeEvalable // GraphNodeEvalable
func (n *NodePlannableResource) EvalTree() EvalNode { func (n *NodePlannableResource) EvalTree() EvalNode {
addr := n.NodeAbstractResource.Addr
// stateId is the ID to put into the state
stateId := addr.stateId()
if addr.Index > -1 {
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
}
// Build the instance info. More of this will be populated during eval
info := &InstanceInfo{
Id: stateId,
Type: addr.Type,
}
// Build the resource for eval
resource := &Resource{
Name: addr.Name,
Type: addr.Type,
CountIndex: addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// Determine the dependencies for the state. We use some older
// code for this that we've used for a long time.
var stateDeps []string
{
oldN := &graphNodeExpandedResource{Resource: n.Config}
stateDeps = oldN.StateDependencies()
}
// Eval info is different depending on what kind of resource this is
switch n.Config.Mode {
case config.ManagedResourceMode:
return n.evalTreeManagedResource(
stateId, info, resource, stateDeps,
)
case config.DataResourceMode:
return n.evalTreeDataResource(
stateId, info, resource, stateDeps)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
}
func (n *NodePlannableResource) evalTreeDataResource(
stateId string, info *InstanceInfo,
resource *Resource, stateDeps []string) EvalNode {
var provider ResourceProvider
var config *ResourceConfig
var diff *InstanceDiff
var state *InstanceState
return &EvalSequence{ return &EvalSequence{
Nodes: []EvalNode{ Nodes: []EvalNode{
// Get the saved diff for apply // The EvalTree for a plannable resource primarily involves
&EvalReadDiff{ // interpolating the count since it can contain variables
Name: stateId, // we only just received access to.
Diff: &diff, //
}, // With the interpolated count, we can then DynamicExpand
// into the proper number of instances.
// Stop here if we don't actually have a diff &EvalInterpolate{Config: n.Config.RawCount},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
if diff == nil {
return true, EvalEarlyExitError{}
}
if diff.GetAttributesLen() == 0 {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
// We need to re-interpolate the config here, rather than
// just using the diff's values directly, because we've
// potentially learned more variable values during the
// apply pass that weren't known when the diff was produced.
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
// Make a new diff with our newly-interpolated config.
&EvalReadDataDiff{
Info: info,
Config: &config,
Previous: &diff,
Provider: &provider,
Output: &diff,
},
&EvalReadDataApply{
Info: info,
Diff: &diff,
Provider: &provider,
Output: &state,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
// Clear the diff now that we've applied it, so
// later nodes won't see a diff that's now a no-op.
&EvalWriteDiff{
Name: stateId,
Diff: nil,
},
&EvalUpdateStateHook{},
}, },
} }
} }
func (n *NodePlannableResource) evalTreeManagedResource( /*
stateId string, info *InstanceInfo, // GraphNodeDynamicExpandable
resource *Resource, stateDeps []string) EvalNode { func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// Declare a bunch of variables that are used for state during state, lock := ctx.State()
// evaluation. Most of this are written to by-address below. lock.RLock()
var provider ResourceProvider defer lock.RUnlock()
var diff *InstanceDiff
var state *InstanceState
var resourceConfig *ResourceConfig
return &EvalSequence{ // Start creating the steps
Nodes: []EvalNode{ steps := make([]GraphTransformer, 0, 5)
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(), // Expand counts.
Resource: resource, steps = append(steps, &ResourceCountTransformer{
Output: &resourceConfig, Resource: n.Resource,
}, Destroy: n.Destroy,
&EvalGetProvider{ Targets: n.Targets,
Name: n.ProvidedBy()[0], })
Output: &provider,
}, // Always end with the root being added
// Re-run validation to catch any errors we missed, e.g. type steps = append(steps, &RootTransformer{})
// mismatches on computed values.
&EvalValidateResource{ // Build the graph
Provider: &provider, b := &BasicGraphBuilder{Steps: steps, Validate: true}
Config: &resourceConfig, return b.Build(ctx.Path())
ResourceName: n.Config.Name,
ResourceType: n.Config.Type,
ResourceMode: n.Config.Mode,
IgnoreWarnings: true,
},
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalDiff{
Info: info,
Config: &resourceConfig,
Resource: n.Config,
Provider: &provider,
State: &state,
OutputDiff: &diff,
OutputState: &state,
},
&EvalCheckPreventDestroy{
Resource: n.Config,
Diff: &diff,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
&EvalWriteDiff{
Name: stateId,
Diff: &diff,
},
},
}
} }
*/

View File

@ -0,0 +1,196 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
)
// NodePlannableResourceInstance represents a _single_ resource
// instance that is plannable. This means this represents a single
// count index, for example.
type NodePlannableResourceInstance struct {
*NodeAbstractResource
}
// GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
addr := n.NodeAbstractResource.Addr
// stateId is the ID to put into the state
stateId := addr.stateId()
if addr.Index > -1 {
stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
}
// Build the instance info. More of this will be populated during eval
info := &InstanceInfo{
Id: stateId,
Type: addr.Type,
}
// Build the resource for eval
resource := &Resource{
Name: addr.Name,
Type: addr.Type,
CountIndex: addr.Index,
}
if resource.CountIndex < 0 {
resource.CountIndex = 0
}
// Determine the dependencies for the state. We use some older
// code for this that we've used for a long time.
var stateDeps []string
{
oldN := &graphNodeExpandedResource{Resource: n.Config}
stateDeps = oldN.StateDependencies()
}
// Eval info is different depending on what kind of resource this is
switch n.Config.Mode {
case config.ManagedResourceMode:
return n.evalTreeManagedResource(
stateId, info, resource, stateDeps,
)
case config.DataResourceMode:
return n.evalTreeDataResource(
stateId, info, resource, stateDeps)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
}
func (n *NodePlannableResourceInstance) evalTreeDataResource(
stateId string, info *InstanceInfo,
resource *Resource, stateDeps []string) EvalNode {
var provider ResourceProvider
var config *ResourceConfig
var diff *InstanceDiff
var state *InstanceState
return &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: stateId,
Output: &state,
},
// We need to re-interpolate the config here because some
// of the attributes may have become computed during
// earlier planning, due to other resources having
// "requires new resource" diffs.
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &config,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
computed := config.ComputedKeys != nil && len(config.ComputedKeys) > 0
// If the configuration is complete and we
// already have a state then we don't need to
// do any further work during apply, because we
// already populated the state during refresh.
if !computed && state != nil {
return true, EvalEarlyExitError{}
}
return true, nil
},
Then: EvalNoop{},
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadDataDiff{
Info: info,
Config: &config,
Provider: &provider,
Output: &diff,
OutputState: &state,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
&EvalWriteDiff{
Name: stateId,
Diff: &diff,
},
},
}
}
func (n *NodePlannableResourceInstance) evalTreeManagedResource(
stateId string, info *InstanceInfo,
resource *Resource, stateDeps []string) EvalNode {
// Declare a bunch of variables that are used for state during
// evaluation. Most of this are written to by-address below.
var provider ResourceProvider
var diff *InstanceDiff
var state *InstanceState
var resourceConfig *ResourceConfig
return &EvalSequence{
Nodes: []EvalNode{
&EvalInterpolate{
Config: n.Config.RawConfig.Copy(),
Resource: resource,
Output: &resourceConfig,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
// Re-run validation to catch any errors we missed, e.g. type
// mismatches on computed values.
&EvalValidateResource{
Provider: &provider,
Config: &resourceConfig,
ResourceName: n.Config.Name,
ResourceType: n.Config.Type,
ResourceMode: n.Config.Mode,
IgnoreWarnings: true,
},
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalDiff{
Info: info,
Config: &resourceConfig,
Resource: n.Config,
Provider: &provider,
State: &state,
OutputDiff: &diff,
OutputState: &state,
},
&EvalCheckPreventDestroy{
Resource: n.Config,
Diff: &diff,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.Config.Type,
Provider: n.Config.Provider,
Dependencies: stateDeps,
State: &state,
},
&EvalWriteDiff{
Name: stateId,
Diff: &diff,
},
},
}
}

View File

@ -8,15 +8,15 @@ import (
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
) )
// ResourceCountTransformer is a GraphTransformer that expands the count // ResourceCountTransformerOld is a GraphTransformer that expands the count
// out for a specific resource. // out for a specific resource.
type ResourceCountTransformer struct { type ResourceCountTransformerOld struct {
Resource *config.Resource Resource *config.Resource
Destroy bool Destroy bool
Targets []ResourceAddress Targets []ResourceAddress
} }
func (t *ResourceCountTransformer) Transform(g *Graph) error { func (t *ResourceCountTransformerOld) Transform(g *Graph) error {
// Expand the resource count // Expand the resource count
count, err := t.Resource.Count() count, err := t.Resource.Count()
if err != nil { if err != nil {
@ -72,7 +72,7 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
return nil return nil
} }
func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { func (t *ResourceCountTransformerOld) nodeIsTargeted(node dag.Vertex) bool {
// no targets specified, everything stays in the graph // no targets specified, everything stays in the graph
if len(t.Targets) == 0 { if len(t.Targets) == 0 {
return true return true

View File

@ -5,64 +5,64 @@ import (
"testing" "testing"
) )
func TestResourceCountTransformer(t *testing.T) { func TestResourceCountTransformerOld(t *testing.T) {
cfg := testModule(t, "transform-resource-count-basic").Config() cfg := testModule(t, "transform-resource-count-basic").Config()
resource := cfg.Resources[0] resource := cfg.Resources[0]
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ResourceCountTransformer{Resource: resource} tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
actual := strings.TrimSpace(g.String()) actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testResourceCountTransformStr) expected := strings.TrimSpace(testResourceCountTransformOldStr)
if actual != expected { if actual != expected {
t.Fatalf("bad:\n\n%s", actual) t.Fatalf("bad:\n\n%s", actual)
} }
} }
func TestResourceCountTransformer_countNegative(t *testing.T) { func TestResourceCountTransformerOld_countNegative(t *testing.T) {
cfg := testModule(t, "transform-resource-count-negative").Config() cfg := testModule(t, "transform-resource-count-negative").Config()
resource := cfg.Resources[0] resource := cfg.Resources[0]
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ResourceCountTransformer{Resource: resource} tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err == nil { if err := tf.Transform(&g); err == nil {
t.Fatal("should error") t.Fatal("should error")
} }
} }
} }
func TestResourceCountTransformer_deps(t *testing.T) { func TestResourceCountTransformerOld_deps(t *testing.T) {
cfg := testModule(t, "transform-resource-count-deps").Config() cfg := testModule(t, "transform-resource-count-deps").Config()
resource := cfg.Resources[0] resource := cfg.Resources[0]
g := Graph{Path: RootModulePath} g := Graph{Path: RootModulePath}
{ {
tf := &ResourceCountTransformer{Resource: resource} tf := &ResourceCountTransformerOld{Resource: resource}
if err := tf.Transform(&g); err != nil { if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
actual := strings.TrimSpace(g.String()) actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testResourceCountTransformDepsStr) expected := strings.TrimSpace(testResourceCountTransformOldDepsStr)
if actual != expected { if actual != expected {
t.Fatalf("bad:\n\n%s", actual) t.Fatalf("bad:\n\n%s", actual)
} }
} }
const testResourceCountTransformStr = ` const testResourceCountTransformOldStr = `
aws_instance.foo #0 aws_instance.foo #0
aws_instance.foo #1 aws_instance.foo #1
aws_instance.foo #2 aws_instance.foo #2
` `
const testResourceCountTransformDepsStr = ` const testResourceCountTransformOldDepsStr = `
aws_instance.foo #0 aws_instance.foo #0
aws_instance.foo #1 aws_instance.foo #1
aws_instance.foo #0 aws_instance.foo #0