Merge pull request #26329 from hashicorp/jbardin/remove-refresh
Remove refresh entirely
This commit is contained in:
commit
ce2e59f835
|
@ -311,16 +311,6 @@ func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, tfdiags.
|
||||||
Validate: opts.Validate,
|
Validate: opts.Validate,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
|
|
||||||
case GraphTypeRefresh:
|
|
||||||
return (&RefreshGraphBuilder{
|
|
||||||
Config: c.config,
|
|
||||||
State: c.state,
|
|
||||||
Components: c.components,
|
|
||||||
Schemas: c.schemas,
|
|
||||||
Targets: c.targets,
|
|
||||||
Validate: opts.Validate,
|
|
||||||
}).Build(addrs.RootModuleInstance)
|
|
||||||
|
|
||||||
case GraphTypeEval:
|
case GraphTypeEval:
|
||||||
return (&EvalGraphBuilder{
|
return (&EvalGraphBuilder{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
|
|
|
@ -8,9 +8,7 @@ package terraform
|
||||||
type GraphType byte
|
type GraphType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GraphTypeInvalid GraphType = 0
|
GraphTypeInvalid GraphType = iota
|
||||||
GraphTypeLegacy GraphType = iota
|
|
||||||
GraphTypeRefresh
|
|
||||||
GraphTypePlan
|
GraphTypePlan
|
||||||
GraphTypePlanDestroy
|
GraphTypePlanDestroy
|
||||||
GraphTypeApply
|
GraphTypeApply
|
||||||
|
@ -25,8 +23,6 @@ var GraphTypeMap = map[string]GraphType{
|
||||||
"apply": GraphTypeApply,
|
"apply": GraphTypeApply,
|
||||||
"plan": GraphTypePlan,
|
"plan": GraphTypePlan,
|
||||||
"plan-destroy": GraphTypePlanDestroy,
|
"plan-destroy": GraphTypePlanDestroy,
|
||||||
"refresh": GraphTypeRefresh,
|
|
||||||
"legacy": GraphTypeLegacy,
|
|
||||||
"validate": GraphTypeValidate,
|
"validate": GraphTypeValidate,
|
||||||
"eval": GraphTypeEval,
|
"eval": GraphTypeEval,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,190 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/states"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RefreshGraphBuilder implements GraphBuilder and is responsible for building
|
|
||||||
// a graph for refreshing (updating the Terraform state).
|
|
||||||
//
|
|
||||||
// The primary difference between this graph and others:
|
|
||||||
//
|
|
||||||
// * Based on the state since it represents the only resources that
|
|
||||||
// need to be refreshed.
|
|
||||||
//
|
|
||||||
// * Ignores lifecycle options since no lifecycle events occur here. This
|
|
||||||
// simplifies the graph significantly since complex transforms such as
|
|
||||||
// create-before-destroy can be completely ignored.
|
|
||||||
//
|
|
||||||
type RefreshGraphBuilder struct {
|
|
||||||
// Config is the configuration tree.
|
|
||||||
Config *configs.Config
|
|
||||||
|
|
||||||
// State is the prior state
|
|
||||||
State *states.State
|
|
||||||
|
|
||||||
// Components is a factory for the plug-in components (providers and
|
|
||||||
// provisioners) available for use.
|
|
||||||
Components contextComponentFactory
|
|
||||||
|
|
||||||
// Schemas is the repository of schemas we will draw from to analyse
|
|
||||||
// the configuration.
|
|
||||||
Schemas *Schemas
|
|
||||||
|
|
||||||
// Targets are resources to target
|
|
||||||
Targets []addrs.Targetable
|
|
||||||
|
|
||||||
// Validate will do structural validation of the graph.
|
|
||||||
Validate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// See GraphBuilder
|
|
||||||
func (b *RefreshGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
|
||||||
return (&BasicGraphBuilder{
|
|
||||||
Steps: b.Steps(),
|
|
||||||
Validate: b.Validate,
|
|
||||||
Name: "RefreshGraphBuilder",
|
|
||||||
}).Build(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See GraphBuilder
|
|
||||||
func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
|
||||||
// Custom factory for creating providers.
|
|
||||||
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
|
|
||||||
return &NodeApplyableProvider{
|
|
||||||
NodeAbstractProvider: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteManagedResource := func(a *NodeAbstractResource) dag.Vertex {
|
|
||||||
return &nodeExpandRefreshableManagedResource{
|
|
||||||
NodeAbstractResource: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteManagedResourceInstance := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
return &NodeRefreshableManagedResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteResourceInstanceDeposed := func(a *NodeAbstractResourceInstance, key states.DeposedKey) dag.Vertex {
|
|
||||||
// The "Plan" node type also handles refreshing behavior.
|
|
||||||
return &NodePlanDeposedResourceInstanceObject{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
DeposedKey: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
concreteDataResource := func(a *NodeAbstractResource) dag.Vertex {
|
|
||||||
return &nodeExpandRefreshableDataResource{
|
|
||||||
NodeAbstractResource: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []GraphTransformer{
|
|
||||||
// Creates all the managed resources that aren't in the state, but only if
|
|
||||||
// we have a state already. No resources in state means there's not
|
|
||||||
// anything to refresh.
|
|
||||||
func() GraphTransformer {
|
|
||||||
if b.State.HasResources() {
|
|
||||||
return &ConfigTransformer{
|
|
||||||
Concrete: concreteManagedResource,
|
|
||||||
Config: b.Config,
|
|
||||||
Unique: true,
|
|
||||||
ModeFilter: true,
|
|
||||||
Mode: addrs.ManagedResourceMode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Println("[TRACE] No managed resources in state during refresh; skipping managed resource transformer")
|
|
||||||
return nil
|
|
||||||
}(),
|
|
||||||
|
|
||||||
// Creates all the data resources that aren't in the state. This will also
|
|
||||||
// add any orphans from scaling in as destroy nodes.
|
|
||||||
&ConfigTransformer{
|
|
||||||
Concrete: concreteDataResource,
|
|
||||||
Config: b.Config,
|
|
||||||
Unique: true,
|
|
||||||
ModeFilter: true,
|
|
||||||
Mode: addrs.DataResourceMode,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add any fully-orphaned resources from config (ones that have been
|
|
||||||
// removed completely, not ones that are just orphaned due to a scaled-in
|
|
||||||
// count.
|
|
||||||
&OrphanResourceInstanceTransformer{
|
|
||||||
Concrete: concreteManagedResourceInstance,
|
|
||||||
State: b.State,
|
|
||||||
Config: b.Config,
|
|
||||||
},
|
|
||||||
|
|
||||||
// We also need nodes for any deposed instance objects present in the
|
|
||||||
// state, so we can check if they still exist. (This intentionally
|
|
||||||
// skips creating nodes for _current_ objects, since ConfigTransformer
|
|
||||||
// created nodes that will do that during DynamicExpand.)
|
|
||||||
&StateTransformer{
|
|
||||||
ConcreteDeposed: concreteResourceInstanceDeposed,
|
|
||||||
State: b.State,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Attach the state
|
|
||||||
&AttachStateTransformer{State: b.State},
|
|
||||||
|
|
||||||
// Attach the configuration to any resources
|
|
||||||
&AttachResourceConfigTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Add root variables
|
|
||||||
&RootVariableTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Add the local values
|
|
||||||
&LocalTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Add the outputs
|
|
||||||
&OutputTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Add module variables
|
|
||||||
&ModuleVariableTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
TransformProviders(b.Components.ResourceProviders(), concreteProvider, b.Config),
|
|
||||||
|
|
||||||
// Must attach schemas before ReferenceTransformer so that we can
|
|
||||||
// analyze the configuration to find references.
|
|
||||||
&AttachSchemaTransformer{Schemas: b.Schemas, Config: b.Config},
|
|
||||||
|
|
||||||
// Create expansion nodes for all of the module calls. This must
|
|
||||||
// come after all other transformers that create nodes representing
|
|
||||||
// objects that can belong to modules.
|
|
||||||
&ModuleExpansionTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Connect so that the references are ready for targeting. We'll
|
|
||||||
// have to connect again later for providers and so on.
|
|
||||||
&ReferenceTransformer{},
|
|
||||||
&AttachDependenciesTransformer{},
|
|
||||||
&attachDataResourceDependenciesTransformer{},
|
|
||||||
|
|
||||||
// Target
|
|
||||||
&TargetsTransformer{
|
|
||||||
Targets: b.Targets,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Close opened plugin connections
|
|
||||||
&CloseProviderTransformer{},
|
|
||||||
|
|
||||||
// Close root module
|
|
||||||
&CloseRootModuleTransformer{},
|
|
||||||
|
|
||||||
// Perform the transitive reduction to make our graph a bit
|
|
||||||
// more sane if possible (it usually is possible).
|
|
||||||
&TransitiveReductionTransformer{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRefreshGraphBuilder_configOrphans(t *testing.T) {
|
|
||||||
|
|
||||||
m := testModule(t, "refresh-config-orphan")
|
|
||||||
|
|
||||||
state := states.NewState()
|
|
||||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
||||||
deposedKey := states.DeposedKey("00000001")
|
|
||||||
testSetResourceInstanceDeposed(root, "test_object.foo[0]", `{"id":"foo"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
testSetResourceInstanceDeposed(root, "test_object.foo[1]", `{"id":"bar"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
testSetResourceInstanceDeposed(root, "test_object.foo[2]", `{"id":"baz"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
|
|
||||||
// NOTE: Real-world data resources don't get deposed
|
|
||||||
testSetResourceInstanceDeposed(root, "data.test_object.foo[0]", `{"id":"foo"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
testSetResourceInstanceDeposed(root, "data.test_object.foo[1]", `{"id":"bar"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
testSetResourceInstanceDeposed(root, "data.test_object.foo[2]", `{"id":"baz"}`, `provider["registry.terraform.io/hashicorp/test"]`, deposedKey)
|
|
||||||
|
|
||||||
b := &RefreshGraphBuilder{
|
|
||||||
Config: m,
|
|
||||||
State: state,
|
|
||||||
Components: simpleMockComponentFactory(),
|
|
||||||
Schemas: simpleTestSchemas(),
|
|
||||||
}
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error building graph: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.StringWithNodeTypes())
|
|
||||||
expected := strings.TrimSpace(`
|
|
||||||
data.test_object.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
data.test_object.foo[0] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
data.test_object.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
data.test_object.foo[1] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
data.test_object.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
data.test_object.foo[2] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] (close) - *terraform.graphNodeCloseProvider
|
|
||||||
data.test_object.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
data.test_object.foo[0] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
data.test_object.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
data.test_object.foo[1] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
data.test_object.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
data.test_object.foo[2] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
test_object.foo (expand) - *terraform.nodeExpandRefreshableManagedResource
|
|
||||||
test_object.foo[0] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
test_object.foo[1] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
test_object.foo[2] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
root - *terraform.nodeCloseModule
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] (close) - *terraform.graphNodeCloseProvider
|
|
||||||
test_object.foo (expand) - *terraform.nodeExpandRefreshableManagedResource
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
test_object.foo[0] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
test_object.foo[1] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
test_object.foo[2] (deposed 00000001) - *terraform.NodePlanDeposedResourceInstanceObject
|
|
||||||
provider["registry.terraform.io/hashicorp/test"] - *terraform.NodeApplyableProvider
|
|
||||||
`)
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s\ndiff:\n%s", actual, expected, cmp.Diff(expected, actual))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,6 @@ const (
|
||||||
walkApply
|
walkApply
|
||||||
walkPlan
|
walkPlan
|
||||||
walkPlanDestroy
|
walkPlanDestroy
|
||||||
walkRefresh
|
|
||||||
walkValidate
|
walkValidate
|
||||||
walkDestroy
|
walkDestroy
|
||||||
walkImport
|
walkImport
|
||||||
|
|
|
@ -9,18 +9,16 @@ func _() {
|
||||||
// Re-run the stringer command to generate them again.
|
// Re-run the stringer command to generate them again.
|
||||||
var x [1]struct{}
|
var x [1]struct{}
|
||||||
_ = x[GraphTypeInvalid-0]
|
_ = x[GraphTypeInvalid-0]
|
||||||
_ = x[GraphTypeLegacy-1]
|
_ = x[GraphTypePlan-1]
|
||||||
_ = x[GraphTypeRefresh-2]
|
_ = x[GraphTypePlanDestroy-2]
|
||||||
_ = x[GraphTypePlan-3]
|
_ = x[GraphTypeApply-3]
|
||||||
_ = x[GraphTypePlanDestroy-4]
|
_ = x[GraphTypeValidate-4]
|
||||||
_ = x[GraphTypeApply-5]
|
_ = x[GraphTypeEval-5]
|
||||||
_ = x[GraphTypeValidate-6]
|
|
||||||
_ = x[GraphTypeEval-7]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypeRefreshGraphTypePlanGraphTypePlanDestroyGraphTypeApplyGraphTypeValidateGraphTypeEval"
|
const _GraphType_name = "GraphTypeInvalidGraphTypePlanGraphTypePlanDestroyGraphTypeApplyGraphTypeValidateGraphTypeEval"
|
||||||
|
|
||||||
var _GraphType_index = [...]uint8{0, 16, 31, 47, 60, 80, 94, 111, 124}
|
var _GraphType_index = [...]uint8{0, 16, 29, 49, 63, 80, 93}
|
||||||
|
|
||||||
func (i GraphType) String() string {
|
func (i GraphType) String() string {
|
||||||
if i >= GraphType(len(_GraphType_index)-1) {
|
if i >= GraphType(len(_GraphType_index)-1) {
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nodeExpandRefreshableDataResource struct {
|
|
||||||
*NodeAbstractResource
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeDynamicExpandable = (*nodeExpandRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeReferenceable = (*nodeExpandRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeReferencer = (*nodeExpandRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeConfigResource = (*nodeExpandRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeAttachResourceConfig = (*nodeExpandRefreshableDataResource)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableDataResource) Name() string {
|
|
||||||
return n.NodeAbstractResource.Name() + " (expand)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableDataResource) References() []*addrs.Reference {
|
|
||||||
return (&NodeRefreshableManagedResource{NodeAbstractResource: n.NodeAbstractResource}).References()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
||||||
var g Graph
|
|
||||||
|
|
||||||
expander := ctx.InstanceExpander()
|
|
||||||
for _, module := range expander.ExpandModule(n.Addr.Module) {
|
|
||||||
g.Add(&NodeRefreshableDataResource{
|
|
||||||
NodeAbstractResource: n.NodeAbstractResource,
|
|
||||||
Addr: n.Addr.Resource.Absolute(module),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeRefreshableDataResource represents a resource that is "refreshable".
|
|
||||||
type NodeRefreshableDataResource struct {
|
|
||||||
*NodeAbstractResource
|
|
||||||
|
|
||||||
Addr addrs.AbsResource
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeModuleInstance = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeDynamicExpandable = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeReferenceable = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeReferencer = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeConfigResource = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil)
|
|
||||||
_ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResource)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NodeRefreshableDataResource) Path() addrs.ModuleInstance {
|
|
||||||
return n.Addr.Module
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable
|
|
||||||
func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
|
|
||||||
expander := ctx.InstanceExpander()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case n.Config.Count != nil:
|
|
||||||
count, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx)
|
|
||||||
diags = diags.Append(countDiags)
|
|
||||||
if countDiags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
if !count.IsKnown() {
|
|
||||||
// If the count isn't known yet, we'll skip refreshing and try expansion
|
|
||||||
// again during the plan walk.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c, _ := count.AsBigFloat().Int64()
|
|
||||||
expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, int(c))
|
|
||||||
|
|
||||||
case n.Config.ForEach != nil:
|
|
||||||
forEachVal, forEachDiags := evaluateForEachExpressionValue(n.Config.ForEach, ctx)
|
|
||||||
diags = diags.Append(forEachDiags)
|
|
||||||
if forEachDiags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
if !forEachVal.IsKnown() {
|
|
||||||
// If the for_each isn't known yet, we'll skip refreshing and try expansion
|
|
||||||
// again during the plan walk.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachVal.AsValueMap())
|
|
||||||
|
|
||||||
default:
|
|
||||||
expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next we need to potentially rename an instance address in the state
|
|
||||||
// if we're transitioning whether "count" is set at all.
|
|
||||||
fixResourceCountSetTransition(ctx, n.ResourceAddr(), n.Config.Count != nil)
|
|
||||||
|
|
||||||
instanceAddrs := expander.ExpandResource(n.Addr)
|
|
||||||
|
|
||||||
// Our graph transformers require access to the full state, so we'll
|
|
||||||
// temporarily lock it while we work on this.
|
|
||||||
state := ctx.State().Lock()
|
|
||||||
defer ctx.State().Unlock()
|
|
||||||
|
|
||||||
// The concrete resource factory we'll use
|
|
||||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
// Add the config and state since we don't do that via transforms
|
|
||||||
a.Config = n.Config
|
|
||||||
a.ResolvedProvider = n.ResolvedProvider
|
|
||||||
a.ProviderMetas = n.ProviderMetas
|
|
||||||
a.dependsOn = n.dependsOn
|
|
||||||
a.forceDependsOn = n.forceDependsOn
|
|
||||||
a.Targets = n.Targets
|
|
||||||
|
|
||||||
return &NodeRefreshableDataResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We also need a destroyable resource for orphans that are a result of a
|
|
||||||
// scaled-in count.
|
|
||||||
concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
// Add the config and provider since we don't do that via transforms
|
|
||||||
a.Config = n.Config
|
|
||||||
a.ResolvedProvider = n.ResolvedProvider
|
|
||||||
|
|
||||||
return &NodeDestroyableDataResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start creating the steps
|
|
||||||
steps := []GraphTransformer{
|
|
||||||
// Expand the count.
|
|
||||||
&ResourceCountTransformer{
|
|
||||||
Concrete: concreteResource,
|
|
||||||
Schema: n.Schema,
|
|
||||||
Addr: n.ResourceAddr(),
|
|
||||||
InstanceAddrs: instanceAddrs,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add the count orphans. As these are orphaned refresh nodes, we add them
|
|
||||||
// directly as NodeDestroyableDataResource.
|
|
||||||
&OrphanResourceInstanceCountTransformer{
|
|
||||||
Concrete: concreteResourceDestroyable,
|
|
||||||
Addr: n.Addr,
|
|
||||||
InstanceAddrs: instanceAddrs,
|
|
||||||
State: state,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Attach the state
|
|
||||||
&AttachStateTransformer{State: state},
|
|
||||||
|
|
||||||
// Targeting
|
|
||||||
&TargetsTransformer{Targets: n.Targets},
|
|
||||||
|
|
||||||
// Connect references so ordering is correct
|
|
||||||
&ReferenceTransformer{},
|
|
||||||
|
|
||||||
// Make sure there is a single root
|
|
||||||
&RootTransformer{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the graph
|
|
||||||
b := &BasicGraphBuilder{
|
|
||||||
Steps: steps,
|
|
||||||
Validate: true,
|
|
||||||
Name: "NodeRefreshableDataResource",
|
|
||||||
}
|
|
||||||
|
|
||||||
graph, diags := b.Build(nil)
|
|
||||||
return graph, diags.ErrWithWarnings()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeRefreshableDataResourceInstance represents a single resource instance
|
|
||||||
// that is refreshable.
|
|
||||||
type NodeRefreshableDataResourceInstance struct {
|
|
||||||
*NodeAbstractResourceInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeExecutable
|
|
||||||
func (n *NodeRefreshableDataResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
|
|
||||||
addr := n.ResourceInstanceAddr()
|
|
||||||
|
|
||||||
// These variables are the state for the eval sequence below, and are
|
|
||||||
// updated through pointers.
|
|
||||||
var change *plans.ResourceInstanceChange
|
|
||||||
var state *states.ResourceInstanceObject
|
|
||||||
|
|
||||||
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalReadState
|
|
||||||
readStateReq := &EvalReadState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
Output: &state,
|
|
||||||
}
|
|
||||||
_, err = readStateReq.Eval(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalReadDataRefresh will _attempt_ to read the data source, but
|
|
||||||
// may generate an incomplete planned object if the configuration
|
|
||||||
// includes values that won't be known until apply.
|
|
||||||
readDataRefreshReq := &evalReadDataRefresh{
|
|
||||||
evalReadData{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Config: n.Config,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
ProviderMetas: n.ProviderMetas,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
OutputChange: &change,
|
|
||||||
State: &state,
|
|
||||||
dependsOn: n.dependsOn,
|
|
||||||
forceDependsOn: n.forceDependsOn,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err = readDataRefreshReq.Eval(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if change == nil {
|
|
||||||
// EvalWriteState
|
|
||||||
writeStateRequest := EvalWriteState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
State: &state,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
}
|
|
||||||
_, err := writeStateRequest.Eval(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = UpdateStateHook(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// EvalWriteDiff
|
|
||||||
writeDiffReq := &EvalWriteDiff{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Change: &change,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
}
|
|
||||||
_, err = writeDiffReq.Eval(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// EvalWriteState
|
|
||||||
writeStateRequest := EvalWriteState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
State: &state,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
}
|
|
||||||
_, err := writeStateRequest.Eval(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/instances"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNodeRefreshableDataResourceDynamicExpand_scaleOut(t *testing.T) {
|
|
||||||
m := testModule(t, "refresh-data-scale-inout")
|
|
||||||
|
|
||||||
state := MustShimLegacyState(&State{
|
|
||||||
Modules: []*ModuleState{
|
|
||||||
&ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
Resources: map[string]*ResourceState{
|
|
||||||
"data.aws_instance.foo.0": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"data.aws_instance.foo.1": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
addr := addrs.RootModule.Resource(addrs.DataResourceMode, "aws_instance", "foo")
|
|
||||||
n := &NodeRefreshableDataResource{
|
|
||||||
NodeAbstractResource: &NodeAbstractResource{
|
|
||||||
Addr: addr,
|
|
||||||
Config: m.Module.DataResources["data.aws_instance.foo"],
|
|
||||||
},
|
|
||||||
Addr: addr.Absolute(addrs.RootModuleInstance),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := n.DynamicExpand(&MockEvalContext{
|
|
||||||
PathPath: addrs.RootModuleInstance,
|
|
||||||
StateState: state.SyncWrapper(),
|
|
||||||
InstanceExpanderExpander: instances.NewExpander(),
|
|
||||||
|
|
||||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
|
||||||
// expression, which is just a literal number 3 in the fixture config
|
|
||||||
// and so we'll just hard-code this here too.
|
|
||||||
EvaluateExprResult: cty.NumberIntVal(3),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error on DynamicExpand: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := g.StringWithNodeTypes()
|
|
||||||
expected := `data.aws_instance.foo[0] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[1] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[2] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
root - terraform.graphNodeRoot
|
|
||||||
data.aws_instance.foo[0] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[1] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[2] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
`
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeRefreshableDataResourceDynamicExpand_scaleIn(t *testing.T) {
|
|
||||||
m := testModule(t, "refresh-data-scale-inout")
|
|
||||||
|
|
||||||
state := MustShimLegacyState(&State{
|
|
||||||
Modules: []*ModuleState{
|
|
||||||
&ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
Resources: map[string]*ResourceState{
|
|
||||||
"data.aws_instance.foo.0": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"data.aws_instance.foo.1": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"data.aws_instance.foo.2": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"data.aws_instance.foo.3": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "qux",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
addr := addrs.RootModule.Resource(addrs.DataResourceMode, "aws_instance", "foo")
|
|
||||||
n := &NodeRefreshableDataResource{
|
|
||||||
NodeAbstractResource: &NodeAbstractResource{
|
|
||||||
Addr: addr,
|
|
||||||
Config: m.Module.DataResources["data.aws_instance.foo"],
|
|
||||||
ResolvedProvider: addrs.AbsProviderConfig{
|
|
||||||
Provider: addrs.NewDefaultProvider("aws"),
|
|
||||||
Module: addrs.RootModule,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Addr: addr.Absolute(addrs.RootModuleInstance),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := n.DynamicExpand(&MockEvalContext{
|
|
||||||
PathPath: addrs.RootModuleInstance,
|
|
||||||
StateState: state.SyncWrapper(),
|
|
||||||
InstanceExpanderExpander: instances.NewExpander(),
|
|
||||||
|
|
||||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
|
||||||
// expression, which is just a literal number 3 in the fixture config
|
|
||||||
// and so we'll just hard-code this here too.
|
|
||||||
EvaluateExprResult: cty.NumberIntVal(3),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error on DynamicExpand: %s", err)
|
|
||||||
}
|
|
||||||
actual := g.StringWithNodeTypes()
|
|
||||||
expected := `data.aws_instance.foo[0] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[1] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[2] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[3] - *terraform.NodeDestroyableDataResourceInstance
|
|
||||||
root - terraform.graphNodeRoot
|
|
||||||
data.aws_instance.foo[0] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[1] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[2] - *terraform.NodeRefreshableDataResourceInstance
|
|
||||||
data.aws_instance.foo[3] - *terraform.NodeDestroyableDataResourceInstance
|
|
||||||
`
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
var destroyableDataResource *NodeDestroyableDataResourceInstance
|
|
||||||
for _, v := range g.Vertices() {
|
|
||||||
if r, ok := v.(*NodeDestroyableDataResourceInstance); ok {
|
|
||||||
destroyableDataResource = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if destroyableDataResource == nil {
|
|
||||||
t.Fatal("failed to find a destroyableDataResource")
|
|
||||||
}
|
|
||||||
|
|
||||||
if destroyableDataResource.ResolvedProvider.Provider.Type == "" {
|
|
||||||
t.Fatal("NodeDestroyableDataResourceInstance missing provider config")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -153,7 +153,7 @@ func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case walkRefresh, walkPlan, walkApply, walkDestroy, walkImport:
|
case walkPlan, walkApply, walkDestroy, walkImport:
|
||||||
vals, err = n.EvalModuleCallArgument(ctx, false)
|
vals, err = n.EvalModuleCallArgument(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -200,7 +200,7 @@ func (n *NodeApplyableOutput) References() []*addrs.Reference {
|
||||||
func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) error {
|
func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
switch op {
|
switch op {
|
||||||
// Everything except walkImport
|
// Everything except walkImport
|
||||||
case walkEval, walkRefresh, walkPlan, walkApply, walkValidate, walkDestroy, walkPlanDestroy:
|
case walkEval, walkPlan, walkApply, walkValidate, walkDestroy, walkPlanDestroy:
|
||||||
// This has to run before we have a state lock, since evaluation also
|
// This has to run before we have a state lock, since evaluation also
|
||||||
// reads the state
|
// reads the state
|
||||||
val, diags := ctx.EvaluateExpr(n.Config.Expr, cty.DynamicPseudoType, nil)
|
val, diags := ctx.EvaluateExpr(n.Config.Expr, cty.DynamicPseudoType, nil)
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (n *NodeApplyableProvider) Execute(ctx EvalContext, op walkOperation) error
|
||||||
switch op {
|
switch op {
|
||||||
case walkValidate:
|
case walkValidate:
|
||||||
return n.ValidateProvider(ctx, provider)
|
return n.ValidateProvider(ctx, provider)
|
||||||
case walkRefresh, walkPlan, walkApply, walkDestroy:
|
case walkPlan, walkApply, walkDestroy:
|
||||||
return n.ConfigureProvider(ctx, provider, false)
|
return n.ConfigureProvider(ctx, provider, false)
|
||||||
case walkImport:
|
case walkImport:
|
||||||
return n.ConfigureProvider(ctx, provider, true)
|
return n.ConfigureProvider(ctx, provider, true)
|
||||||
|
|
|
@ -73,46 +73,6 @@ func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode {
|
||||||
|
|
||||||
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
||||||
|
|
||||||
// During the refresh walk we will ensure that our record of the deposed
|
|
||||||
// object is up-to-date. If it was already deleted outside of Terraform
|
|
||||||
// then this will remove it from state and thus avoid us planning a
|
|
||||||
// destroy for it during the subsequent plan walk.
|
|
||||||
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkRefresh},
|
|
||||||
Node: &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalGetProvider{
|
|
||||||
Addr: n.ResolvedProvider,
|
|
||||||
Output: &provider,
|
|
||||||
Schema: &providerSchema,
|
|
||||||
},
|
|
||||||
&EvalReadStateDeposed{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
Key: n.DeposedKey,
|
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
&EvalRefresh{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderMetas: n.ProviderMetas,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
&EvalWriteStateDeposed{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Key: n.DeposedKey,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// During the plan walk we always produce a planned destroy change, because
|
// During the plan walk we always produce a planned destroy change, because
|
||||||
// destroying is the only supported action for deposed objects.
|
// destroying is the only supported action for deposed objects.
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
|
|
|
@ -1,381 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/states"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nodeExpandRefreshableResource handles the first layer of resource
|
|
||||||
// expansion durin refresh. We need this extra layer so DynamicExpand is called
|
|
||||||
// twice for the resource, the first to expand the Resource for each module
|
|
||||||
// instance, and the second to expand each ResourceInstance for the expanded
|
|
||||||
// Resources.
|
|
||||||
type nodeExpandRefreshableManagedResource struct {
|
|
||||||
*NodeAbstractResource
|
|
||||||
|
|
||||||
// We attach dependencies to the Resource during refresh, since the
|
|
||||||
// instances are instantiated during DynamicExpand.
|
|
||||||
Dependencies []addrs.ConfigResource
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeDynamicExpandable = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeReferenceable = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeReferencer = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeConfigResource = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeAttachResourceConfig = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeAttachDependencies = (*nodeExpandRefreshableManagedResource)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableManagedResource) Name() string {
|
|
||||||
return n.NodeAbstractResource.Name() + " (expand)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeAttachDependencies
|
|
||||||
func (n *nodeExpandRefreshableManagedResource) AttachDependencies(deps []addrs.ConfigResource) {
|
|
||||||
n.Dependencies = deps
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableManagedResource) References() []*addrs.Reference {
|
|
||||||
return (&NodeRefreshableManagedResource{NodeAbstractResource: n.NodeAbstractResource}).References()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *nodeExpandRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
||||||
var g Graph
|
|
||||||
|
|
||||||
expander := ctx.InstanceExpander()
|
|
||||||
for _, module := range expander.ExpandModule(n.Addr.Module) {
|
|
||||||
g.Add(&NodeRefreshableManagedResource{
|
|
||||||
NodeAbstractResource: n.NodeAbstractResource,
|
|
||||||
Addr: n.Addr.Resource.Absolute(module),
|
|
||||||
Dependencies: n.Dependencies,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeRefreshableManagedResource represents a resource that is expandable into
|
|
||||||
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
|
|
||||||
type NodeRefreshableManagedResource struct {
|
|
||||||
*NodeAbstractResource
|
|
||||||
|
|
||||||
Addr addrs.AbsResource
|
|
||||||
|
|
||||||
// We attach dependencies to the Resource during refresh, since the
|
|
||||||
// instances are instantiated during DynamicExpand.
|
|
||||||
Dependencies []addrs.ConfigResource
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeModuleInstance = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeConfigResource = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NodeRefreshableManagedResource) Path() addrs.ModuleInstance {
|
|
||||||
return n.Addr.Module
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable
|
|
||||||
func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
|
|
||||||
expander := ctx.InstanceExpander()
|
|
||||||
// Inform our instance expander about our expansion results, and then use
|
|
||||||
// it to calculate the instance addresses we'll expand for.
|
|
||||||
switch {
|
|
||||||
case n.Config.Count != nil:
|
|
||||||
count, countDiags := evaluateCountExpression(n.Config.Count, ctx)
|
|
||||||
diags = diags.Append(countDiags)
|
|
||||||
if countDiags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count)
|
|
||||||
|
|
||||||
case n.Config.ForEach != nil:
|
|
||||||
forEachMap, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx)
|
|
||||||
if forEachDiags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachMap)
|
|
||||||
|
|
||||||
default:
|
|
||||||
expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next we need to potentially rename an instance address in the state
|
|
||||||
// if we're transitioning whether "count" is set at all.
|
|
||||||
fixResourceCountSetTransition(ctx, n.Addr.Config(), n.Config.Count != nil)
|
|
||||||
instanceAddrs := expander.ExpandResource(n.Addr)
|
|
||||||
|
|
||||||
// Our graph transformers require access to the full state, so we'll
|
|
||||||
// temporarily lock it while we work on this.
|
|
||||||
state := ctx.State().Lock()
|
|
||||||
defer ctx.State().Unlock()
|
|
||||||
|
|
||||||
// The concrete resource factory we'll use
|
|
||||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
// Add the config and state since we don't do that via transforms
|
|
||||||
a.Config = n.Config
|
|
||||||
a.ResolvedProvider = n.ResolvedProvider
|
|
||||||
a.Dependencies = n.Dependencies
|
|
||||||
a.ProviderMetas = n.ProviderMetas
|
|
||||||
|
|
||||||
return &NodeRefreshableManagedResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start creating the steps
|
|
||||||
steps := []GraphTransformer{
|
|
||||||
// Expand the count.
|
|
||||||
&ResourceCountTransformer{
|
|
||||||
Concrete: concreteResource,
|
|
||||||
Schema: n.Schema,
|
|
||||||
Addr: n.Addr.Config(),
|
|
||||||
InstanceAddrs: instanceAddrs,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add the count orphans to make sure these resources are accounted for
|
|
||||||
// during a scale in.
|
|
||||||
&OrphanResourceInstanceCountTransformer{
|
|
||||||
Concrete: concreteResource,
|
|
||||||
Addr: n.Addr,
|
|
||||||
InstanceAddrs: instanceAddrs,
|
|
||||||
State: state,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Attach the state
|
|
||||||
&AttachStateTransformer{State: state},
|
|
||||||
|
|
||||||
// Targeting
|
|
||||||
&TargetsTransformer{Targets: n.Targets},
|
|
||||||
|
|
||||||
// Connect references so ordering is correct
|
|
||||||
&ReferenceTransformer{},
|
|
||||||
|
|
||||||
// Make sure there is a single root
|
|
||||||
&RootTransformer{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the graph
|
|
||||||
b := &BasicGraphBuilder{
|
|
||||||
Steps: steps,
|
|
||||||
Validate: true,
|
|
||||||
Name: "NodeRefreshableManagedResource",
|
|
||||||
}
|
|
||||||
|
|
||||||
graph, diags := b.Build(nil)
|
|
||||||
return graph, diags.ErrWithWarnings()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
|
|
||||||
// it is ready to be applied and is represented by a diff.
|
|
||||||
type NodeRefreshableManagedResourceInstance struct {
|
|
||||||
*NodeAbstractResourceInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ GraphNodeModuleInstance = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeConfigResource = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
_ GraphNodeExecutable = (*NodeRefreshableManagedResourceInstance)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// GraphNodeDestroyer
|
|
||||||
func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
|
|
||||||
addr := n.ResourceInstanceAddr()
|
|
||||||
return &addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeEvalable
|
|
||||||
func (n *NodeRefreshableManagedResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
|
|
||||||
addr := n.ResourceInstanceAddr()
|
|
||||||
|
|
||||||
// Eval info is different depending on what kind of resource this is
|
|
||||||
switch addr.Resource.Resource.Mode {
|
|
||||||
case addrs.ManagedResourceMode:
|
|
||||||
if n.instanceState == nil {
|
|
||||||
log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr)
|
|
||||||
_, err := n.evalTreeManagedResourceNoState().Eval(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr)
|
|
||||||
_, err := n.evalTreeManagedResource().Eval(ctx)
|
|
||||||
return err
|
|
||||||
|
|
||||||
case addrs.DataResourceMode:
|
|
||||||
// Get the data source node. If we don't have a configuration
|
|
||||||
// then it is an orphan so we destroy it (remove it from the state).
|
|
||||||
var dn GraphNodeExecutable
|
|
||||||
if n.Config != nil {
|
|
||||||
dn = &NodeRefreshableDataResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dn = &NodeDestroyableDataResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dn.Execute(ctx, op)
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
|
|
||||||
addr := n.ResourceInstanceAddr()
|
|
||||||
|
|
||||||
// Declare a bunch of variables that are used for state during
|
|
||||||
// evaluation. Most of this are written to by-address below.
|
|
||||||
var provider providers.Interface
|
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var state *states.ResourceInstanceObject
|
|
||||||
|
|
||||||
// This happened during initial development. All known cases were
|
|
||||||
// fixed and tested but as a sanity check let's assert here.
|
|
||||||
if n.instanceState == nil {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"No resource state attached for addr: %s\n\n"+
|
|
||||||
"This is a bug. Please report this to Terraform with your configuration\n"+
|
|
||||||
"and state attached. Please be careful to scrub any sensitive information.",
|
|
||||||
addr)
|
|
||||||
return &EvalReturnError{Error: &err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalGetProvider{
|
|
||||||
Addr: n.ResolvedProvider,
|
|
||||||
Output: &provider,
|
|
||||||
Schema: &providerSchema,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalReadState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
|
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalRefreshDependencies{
|
|
||||||
State: &state,
|
|
||||||
Dependencies: &n.Dependencies,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalRefresh{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderMetas: n.ProviderMetas,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalWriteState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
Dependencies: &n.Dependencies,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
|
|
||||||
// nodes that don't have state attached. An example of where this functionality
|
|
||||||
// is useful is when a resource that already exists in state is being scaled
|
|
||||||
// out, ie: has its resource count increased. In this case, the scaled out node
|
|
||||||
// needs to be available to other nodes (namely data sources) that may depend
|
|
||||||
// on it for proper interpolation, or confusing "index out of range" errors can
|
|
||||||
// occur.
|
|
||||||
//
|
|
||||||
// The steps in this sequence are very similar to the steps carried out in
|
|
||||||
// plan, but nothing is done with the diff after it is created - it is dropped,
|
|
||||||
// and its changes are not counted in the UI.
|
|
||||||
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
|
|
||||||
addr := n.ResourceInstanceAddr()
|
|
||||||
|
|
||||||
// Declare a bunch of variables that are used for state during
|
|
||||||
// evaluation. Most of this are written to by-address below.
|
|
||||||
var provider providers.Interface
|
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var change *plans.ResourceInstanceChange
|
|
||||||
var state *states.ResourceInstanceObject
|
|
||||||
|
|
||||||
return &EvalSequence{
|
|
||||||
Nodes: []EvalNode{
|
|
||||||
&EvalGetProvider{
|
|
||||||
Addr: n.ResolvedProvider,
|
|
||||||
Output: &provider,
|
|
||||||
Schema: &providerSchema,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalReadState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
|
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalDiff{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Config: n.Config,
|
|
||||||
Provider: &provider,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
OutputChange: &change,
|
|
||||||
OutputState: &state,
|
|
||||||
Stub: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalWriteState{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderAddr: n.ResolvedProvider,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
State: &state,
|
|
||||||
Dependencies: &n.Dependencies,
|
|
||||||
},
|
|
||||||
|
|
||||||
// We must also save the planned change, so that expressions in
|
|
||||||
// other nodes, such as provider configurations and data resources,
|
|
||||||
// can work with the planned new value.
|
|
||||||
//
|
|
||||||
// This depends on the fact that Context.Refresh creates a
|
|
||||||
// temporary new empty changeset for the duration of its graph
|
|
||||||
// walk, and so this recorded change will be discarded immediately
|
|
||||||
// after the refresh walk completes.
|
|
||||||
&EvalWriteDiff{
|
|
||||||
Addr: addr.Resource,
|
|
||||||
Change: &change,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/instances"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNodeRefreshableManagedResourceDynamicExpand_scaleOut(t *testing.T) {
|
|
||||||
m := testModule(t, "refresh-resource-scale-inout")
|
|
||||||
|
|
||||||
state := MustShimLegacyState(&State{
|
|
||||||
Modules: []*ModuleState{
|
|
||||||
&ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
Resources: map[string]*ResourceState{
|
|
||||||
"aws_instance.foo.0": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aws_instance.foo.1": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).SyncWrapper()
|
|
||||||
|
|
||||||
cfgAddr := addrs.RootModule.Resource(addrs.ManagedResourceMode, "aws_instance", "foo")
|
|
||||||
n := &NodeRefreshableManagedResource{
|
|
||||||
NodeAbstractResource: &NodeAbstractResource{
|
|
||||||
Addr: cfgAddr,
|
|
||||||
Config: m.Module.ManagedResources["aws_instance.foo"],
|
|
||||||
},
|
|
||||||
Addr: cfgAddr.Absolute(addrs.RootModuleInstance),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := n.DynamicExpand(&MockEvalContext{
|
|
||||||
PathPath: addrs.RootModuleInstance,
|
|
||||||
StateState: state,
|
|
||||||
InstanceExpanderExpander: instances.NewExpander(),
|
|
||||||
|
|
||||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
|
||||||
// expression, which is just a literal number 3 in the fixture config
|
|
||||||
// and so we'll just hard-code this here too.
|
|
||||||
EvaluateExprResult: cty.NumberIntVal(3),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error attempting DynamicExpand: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := g.StringWithNodeTypes()
|
|
||||||
expected := `aws_instance.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
root - terraform.graphNodeRoot
|
|
||||||
aws_instance.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
`
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeRefreshableManagedResourceDynamicExpand_scaleIn(t *testing.T) {
|
|
||||||
m := testModule(t, "refresh-resource-scale-inout")
|
|
||||||
|
|
||||||
state := MustShimLegacyState(&State{
|
|
||||||
Modules: []*ModuleState{
|
|
||||||
&ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
Resources: map[string]*ResourceState{
|
|
||||||
"aws_instance.foo.0": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aws_instance.foo.1": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aws_instance.foo.2": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aws_instance.foo.3": &ResourceState{
|
|
||||||
Type: "aws_instance",
|
|
||||||
Deposed: []*InstanceState{
|
|
||||||
&InstanceState{
|
|
||||||
ID: "qux",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).SyncWrapper()
|
|
||||||
|
|
||||||
cfgAddr := addrs.RootModule.Resource(addrs.ManagedResourceMode, "aws_instance", "foo")
|
|
||||||
n := &NodeRefreshableManagedResource{
|
|
||||||
NodeAbstractResource: &NodeAbstractResource{
|
|
||||||
Addr: cfgAddr,
|
|
||||||
Config: m.Module.ManagedResources["aws_instance.foo"],
|
|
||||||
},
|
|
||||||
Addr: cfgAddr.Absolute(addrs.RootModuleInstance),
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := n.DynamicExpand(&MockEvalContext{
|
|
||||||
PathPath: addrs.RootModuleInstance,
|
|
||||||
StateState: state,
|
|
||||||
InstanceExpanderExpander: instances.NewExpander(),
|
|
||||||
|
|
||||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
|
||||||
// expression, which is just a literal number 3 in the fixture config
|
|
||||||
// and so we'll just hard-code this here too.
|
|
||||||
EvaluateExprResult: cty.NumberIntVal(3),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error attempting DynamicExpand: %s", err)
|
|
||||||
}
|
|
||||||
actual := g.StringWithNodeTypes()
|
|
||||||
expected := `aws_instance.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[3] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
root - terraform.graphNodeRoot
|
|
||||||
aws_instance.foo[0] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[1] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[2] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
aws_instance.foo[3] - *terraform.NodeRefreshableManagedResourceInstance
|
|
||||||
`
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("Expected:\n%s\nGot:\n%s", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,16 +12,15 @@ func _() {
|
||||||
_ = x[walkApply-1]
|
_ = x[walkApply-1]
|
||||||
_ = x[walkPlan-2]
|
_ = x[walkPlan-2]
|
||||||
_ = x[walkPlanDestroy-3]
|
_ = x[walkPlanDestroy-3]
|
||||||
_ = x[walkRefresh-4]
|
_ = x[walkValidate-4]
|
||||||
_ = x[walkValidate-5]
|
_ = x[walkDestroy-5]
|
||||||
_ = x[walkDestroy-6]
|
_ = x[walkImport-6]
|
||||||
_ = x[walkImport-7]
|
_ = x[walkEval-7]
|
||||||
_ = x[walkEval-8]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _walkOperation_name = "walkInvalidwalkApplywalkPlanwalkPlanDestroywalkRefreshwalkValidatewalkDestroywalkImportwalkEval"
|
const _walkOperation_name = "walkInvalidwalkApplywalkPlanwalkPlanDestroywalkValidatewalkDestroywalkImportwalkEval"
|
||||||
|
|
||||||
var _walkOperation_index = [...]uint8{0, 11, 20, 28, 43, 54, 66, 77, 87, 95}
|
var _walkOperation_index = [...]uint8{0, 11, 20, 28, 43, 55, 66, 76, 84}
|
||||||
|
|
||||||
func (i walkOperation) String() string {
|
func (i walkOperation) String() string {
|
||||||
if i >= walkOperation(len(_walkOperation_index)-1) {
|
if i >= walkOperation(len(_walkOperation_index)-1) {
|
||||||
|
|
Loading…
Reference in New Issue