terraform: prune down the resource node struct quite a bit
This commit is contained in:
parent
a6bab455cc
commit
6c266d6ce3
|
@ -1,19 +1,9 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphNodeCountDependent is implemented by resources for giving only
|
|
||||||
// the dependencies they have from the "count" field.
|
|
||||||
type GraphNodeCountDependent interface {
|
|
||||||
CountDependentOn() []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeConfigResource represents a resource within the config graph.
|
// GraphNodeConfigResource represents a resource within the config graph.
|
||||||
type GraphNodeConfigResource struct {
|
type GraphNodeConfigResource struct {
|
||||||
Resource *config.Resource
|
Resource *config.Resource
|
||||||
|
@ -28,38 +18,10 @@ type GraphNodeConfigResource struct {
|
||||||
Path []string
|
Path []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) Copy() *GraphNodeConfigResource {
|
|
||||||
ncr := &GraphNodeConfigResource{
|
|
||||||
Resource: n.Resource.Copy(),
|
|
||||||
Destroy: n.Destroy,
|
|
||||||
Targets: make([]ResourceAddress, 0, len(n.Targets)),
|
|
||||||
Path: make([]string, 0, len(n.Path)),
|
|
||||||
}
|
|
||||||
for _, t := range n.Targets {
|
|
||||||
ncr.Targets = append(ncr.Targets, *t.Copy())
|
|
||||||
}
|
|
||||||
for _, p := range n.Path {
|
|
||||||
ncr.Path = append(ncr.Path, p)
|
|
||||||
}
|
|
||||||
return ncr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) DependableName() []string {
|
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||||
return []string{n.Resource.Id()}
|
return []string{n.Resource.Id()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeCountDependent impl.
|
|
||||||
func (n *GraphNodeConfigResource) CountDependentOn() []string {
|
|
||||||
result := make([]string, 0, len(n.Resource.RawCount.Variables))
|
|
||||||
for _, v := range n.Resource.RawCount.Variables {
|
|
||||||
if vn := varNameForVar(v); vn != "" {
|
|
||||||
result = append(result, vn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDependent impl.
|
// GraphNodeDependent impl.
|
||||||
func (n *GraphNodeConfigResource) DependentOn() []string {
|
func (n *GraphNodeConfigResource) DependentOn() []string {
|
||||||
result := make([]string, len(n.Resource.DependsOn),
|
result := make([]string, len(n.Resource.DependsOn),
|
||||||
|
@ -120,313 +82,3 @@ func (n *GraphNodeConfigResource) Name() string {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeDotter impl.
|
|
||||||
func (n *GraphNodeConfigResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
|
|
||||||
if n.Destroy && !opts.Verbose {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &dag.DotNode{
|
|
||||||
Name: name,
|
|
||||||
Attrs: map[string]string{
|
|
||||||
"label": n.Name(),
|
|
||||||
"shape": "box",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeFlattenable impl.
|
|
||||||
func (n *GraphNodeConfigResource) Flatten(p []string) (dag.Vertex, error) {
|
|
||||||
return &GraphNodeConfigResourceFlat{
|
|
||||||
GraphNodeConfigResource: n,
|
|
||||||
PathValue: p,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable impl.
|
|
||||||
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
||||||
state, lock := ctx.State()
|
|
||||||
lock.RLock()
|
|
||||||
defer lock.RUnlock()
|
|
||||||
|
|
||||||
// Start creating the steps
|
|
||||||
steps := make([]GraphTransformer, 0, 5)
|
|
||||||
|
|
||||||
// Additional destroy modifications.
|
|
||||||
if n.Destroy {
|
|
||||||
steps = append(steps, &DeposedTransformer{
|
|
||||||
State: state,
|
|
||||||
View: n.Resource.Id(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always want to apply targeting
|
|
||||||
steps = append(steps, &TargetsTransformer{
|
|
||||||
ParsedTargets: n.Targets,
|
|
||||||
Destroy: n.Destroy,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Always end with the root being added
|
|
||||||
steps = append(steps, &RootTransformer{})
|
|
||||||
|
|
||||||
// Build the graph
|
|
||||||
b := &BasicGraphBuilder{
|
|
||||||
Steps: steps,
|
|
||||||
Validate: true,
|
|
||||||
Name: "GraphNodeConfigResource",
|
|
||||||
}
|
|
||||||
return b.Build(ctx.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeAddressable impl.
|
|
||||||
func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
|
|
||||||
return &ResourceAddress{
|
|
||||||
Path: n.Path[1:],
|
|
||||||
Index: -1,
|
|
||||||
InstanceType: TypePrimary,
|
|
||||||
Name: n.Resource.Name,
|
|
||||||
Type: n.Resource.Type,
|
|
||||||
Mode: n.Resource.Mode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeTargetable impl.
|
|
||||||
func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) {
|
|
||||||
n.Targets = targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeEvalable impl.
|
|
||||||
func (n *GraphNodeConfigResource) EvalTree() EvalNode {
|
|
||||||
return &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalInterpolate{Config: n.Resource.RawCount},
|
|
||||||
&EvalCountCheckComputed{Resource: n.Resource},
|
|
||||||
&EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkValidate},
|
|
||||||
Node: &EvalValidateCount{Resource: n.Resource},
|
|
||||||
},
|
|
||||||
&EvalCountFixZeroOneBoundary{Resource: n.Resource},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeProviderConsumer
|
|
||||||
func (n *GraphNodeConfigResource) ProvidedBy() []string {
|
|
||||||
return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as GraphNodeConfigResource, but for flattening
|
|
||||||
type GraphNodeConfigResourceFlat struct {
|
|
||||||
*GraphNodeConfigResource
|
|
||||||
|
|
||||||
PathValue []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) Name() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigResource.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) Path() []string {
|
|
||||||
return n.PathValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) DependableName() []string {
|
|
||||||
return modulePrefixList(
|
|
||||||
n.GraphNodeConfigResource.DependableName(),
|
|
||||||
modulePrefixStr(n.PathValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) DependentOn() []string {
|
|
||||||
prefix := modulePrefixStr(n.PathValue)
|
|
||||||
return modulePrefixList(
|
|
||||||
n.GraphNodeConfigResource.DependentOn(),
|
|
||||||
prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) ProvidedBy() []string {
|
|
||||||
prefix := modulePrefixStr(n.PathValue)
|
|
||||||
return modulePrefixList(
|
|
||||||
n.GraphNodeConfigResource.ProvidedBy(),
|
|
||||||
prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceFlat) ProvisionedBy() []string {
|
|
||||||
prefix := modulePrefixStr(n.PathValue)
|
|
||||||
return modulePrefixList(
|
|
||||||
n.GraphNodeConfigResource.ProvisionedBy(),
|
|
||||||
prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphNodeResourceDestroyFlat struct {
|
|
||||||
*graphNodeResourceDestroy
|
|
||||||
|
|
||||||
PathValue []string
|
|
||||||
|
|
||||||
// Needs to be able to properly yield back a flattened create node to prevent
|
|
||||||
FlatCreateNode *GraphNodeConfigResourceFlat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroyFlat) Name() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeResourceDestroy.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroyFlat) Path() []string {
|
|
||||||
return n.PathValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroyFlat) CreateNode() dag.Vertex {
|
|
||||||
return n.FlatCreateNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroyFlat) ProvidedBy() []string {
|
|
||||||
prefix := modulePrefixStr(n.PathValue)
|
|
||||||
return modulePrefixList(
|
|
||||||
n.GraphNodeConfigResource.ProvidedBy(),
|
|
||||||
prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// graphNodeResourceDestroy represents the logical destruction of a
|
|
||||||
// resource. This node doesn't mean it will be destroyed for sure, but
|
|
||||||
// instead that if a destroy were to happen, it must happen at this point.
|
|
||||||
type graphNodeResourceDestroy struct {
|
|
||||||
GraphNodeConfigResource
|
|
||||||
Original *GraphNodeConfigResource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
|
|
||||||
// CBD is enabled if the resource enables it
|
|
||||||
return n.Original.Resource.Lifecycle.CreateBeforeDestroy && n.Destroy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
|
|
||||||
return n.Original
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool {
|
|
||||||
if n.Destroy {
|
|
||||||
return n.destroyInclude(d, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *graphNodeResourceDestroy) destroyInclude(
|
|
||||||
d *ModuleDiff, s *ModuleState) bool {
|
|
||||||
// Get the count, and specifically the raw value of the count
|
|
||||||
// (with interpolations and all). If the count is NOT a static "1",
|
|
||||||
// then we keep the destroy node no matter what.
|
|
||||||
//
|
|
||||||
// The reasoning for this is complicated and not intuitively obvious,
|
|
||||||
// but I attempt to explain it below.
|
|
||||||
//
|
|
||||||
// The destroy transform works by generating the worst case graph,
|
|
||||||
// with worst case being the case that every resource already exists
|
|
||||||
// and needs to be destroy/created (force-new). There is a single important
|
|
||||||
// edge case where this actually results in a real-life cycle: if a
|
|
||||||
// create-before-destroy (CBD) resource depends on a non-CBD resource.
|
|
||||||
// Imagine a EC2 instance "foo" with CBD depending on a security
|
|
||||||
// group "bar" without CBD, and conceptualize the worst case destroy
|
|
||||||
// order:
|
|
||||||
//
|
|
||||||
// 1.) SG must be destroyed (non-CBD)
|
|
||||||
// 2.) SG must be created/updated
|
|
||||||
// 3.) EC2 instance must be created (CBD, requires the SG be made)
|
|
||||||
// 4.) EC2 instance must be destroyed (requires SG be destroyed)
|
|
||||||
//
|
|
||||||
// Except, #1 depends on #4, since the SG can't be destroyed while
|
|
||||||
// an EC2 instance is using it (AWS API requirements). As you can see,
|
|
||||||
// this is a real life cycle that can't be automatically reconciled
|
|
||||||
// except under two conditions:
|
|
||||||
//
|
|
||||||
// 1.) SG is also CBD. This doesn't work 100% of the time though
|
|
||||||
// since the non-CBD resource might not support CBD. To make matters
|
|
||||||
// worse, the entire transitive closure of dependencies must be
|
|
||||||
// CBD (if the SG depends on a VPC, you have the same problem).
|
|
||||||
// 2.) EC2 must not CBD. This can't happen automatically because CBD
|
|
||||||
// is used as a way to ensure zero (or minimal) downtime Terraform
|
|
||||||
// applies, and it isn't acceptable for TF to ignore this request,
|
|
||||||
// since it can result in unexpected downtime.
|
|
||||||
//
|
|
||||||
// Therefore, we compromise with this edge case here: if there is
|
|
||||||
// a static count of "1", we prune the diff to remove cycles during a
|
|
||||||
// graph optimization path if we don't see the resource in the diff.
|
|
||||||
// If the count is set to ANYTHING other than a static "1" (variable,
|
|
||||||
// computed attribute, static number greater than 1), then we keep the
|
|
||||||
// destroy, since it is required for dynamic graph expansion to find
|
|
||||||
// orphan count objects.
|
|
||||||
//
|
|
||||||
// This isn't ideal logic, but its strictly better without introducing
|
|
||||||
// new impossibilities. It breaks the cycle in practical cases, and the
|
|
||||||
// cycle comes back in no cases we've found to be practical, but just
|
|
||||||
// as the cycle would already exist without this anyways.
|
|
||||||
count := n.Original.Resource.RawCount
|
|
||||||
if raw := count.Raw[count.Key]; raw != "1" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okay, we're dealing with a static count. There are a few ways
|
|
||||||
// to include this resource.
|
|
||||||
prefix := n.Original.Resource.Id()
|
|
||||||
|
|
||||||
// If we're present in the diff proper, then keep it. We're looking
|
|
||||||
// only for resources in the diff that match our resource or a count-index
|
|
||||||
// of our resource that are marked for destroy.
|
|
||||||
if d != nil {
|
|
||||||
for k, v := range d.Resources {
|
|
||||||
match := k == prefix || strings.HasPrefix(k, prefix+".")
|
|
||||||
if match && v.GetDestroy() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're in the state as a primary in any form, then keep it.
|
|
||||||
// This does a prefix check so it will also catch orphans on count
|
|
||||||
// decreases to "1".
|
|
||||||
if s != nil {
|
|
||||||
for k, v := range s.Resources {
|
|
||||||
// Ignore exact matches
|
|
||||||
if k == prefix {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore anything that doesn't have a "." afterwards so that
|
|
||||||
// we only get our own resource and any counts on it.
|
|
||||||
if !strings.HasPrefix(k, prefix+".") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore exact matches and the 0'th index. We only care
|
|
||||||
// about if there is a decrease in count.
|
|
||||||
if k == prefix+".0" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Primary != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're in the state as _both_ "foo" and "foo.0", then
|
|
||||||
// keep it, since we treat the latter as an orphan.
|
|
||||||
_, okOne := s.Resources[prefix]
|
|
||||||
_, okTwo := s.Resources[prefix+".0"]
|
|
||||||
if okOne && okTwo {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGraphNodeConfigResource_impl(t *testing.T) {
|
|
||||||
var _ dag.Vertex = new(GraphNodeConfigResource)
|
|
||||||
var _ dag.NamedVertex = new(GraphNodeConfigResource)
|
|
||||||
var _ graphNodeConfig = new(GraphNodeConfigResource)
|
|
||||||
var _ GraphNodeProviderConsumer = new(GraphNodeConfigResource)
|
|
||||||
var _ GraphNodeProvisionerConsumer = new(GraphNodeConfigResource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGraphNodeConfigResource_ProvidedBy(t *testing.T) {
|
|
||||||
n := &GraphNodeConfigResource{
|
|
||||||
Resource: &config.Resource{Type: "aws_instance"},
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := n.ProvidedBy(); v[0] != "aws" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGraphNodeConfigResource_ProvidedBy_alias(t *testing.T) {
|
|
||||||
n := &GraphNodeConfigResource{
|
|
||||||
Resource: &config.Resource{Type: "aws_instance", Provider: "aws.bar"},
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := n.ProvidedBy(); v[0] != "aws.bar" {
|
|
||||||
t.Fatalf("bad: %#v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGraphNodeConfigResource_ProvisionedBy(t *testing.T) {
|
|
||||||
n := &GraphNodeConfigResource{
|
|
||||||
Resource: &config.Resource{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Provisioners: []*config.Provisioner{
|
|
||||||
&config.Provisioner{Type: "foo"},
|
|
||||||
&config.Provisioner{Type: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"foo", "bar"}
|
|
||||||
actual := n.ProvisionedBy()
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
|
||||||
t.Fatalf("bad: %#v", actual)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue