mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
terraform: decreasing counts works
This commit is contained in:
parent
d7dc0291f5
commit
4ccb12508a
@ -685,7 +685,6 @@ func TestContext2Plan_countOneIndex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
||||||
m := testModule(t, "plan-count-dec")
|
m := testModule(t, "plan-count-dec")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
@ -741,6 +740,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func TestContextPlan_countIncreaseFromNotSet(t *testing.T) {
|
func TestContextPlan_countIncreaseFromNotSet(t *testing.T) {
|
||||||
m := testModule(t, "plan-count-inc")
|
m := testModule(t, "plan-count-inc")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
59
terraform/eval_count.go
Normal file
59
terraform/eval_count.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvalCountFixZeroOneBoundary is an EvalNode that fixes up the state
|
||||||
|
// when there is a resource count with zero/one boundary, i.e. fixing
|
||||||
|
// a resource named "aws_instance.foo" to "aws_instance.foo.0" and vice-versa.
|
||||||
|
type EvalCountFixZeroOneBoundary struct {
|
||||||
|
Resource *config.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalCountFixZeroOneBoundary) Args() ([]EvalNode, []EvalType) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
func (n *EvalCountFixZeroOneBoundary) Eval(
|
||||||
|
ctx EvalContext, args []interface{}) (interface{}, error) {
|
||||||
|
// Get the count, important for knowing whether we're supposed to
|
||||||
|
// be adding the zero, or trimming it.
|
||||||
|
count, err := n.Resource.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure what to look for and what to replace it with
|
||||||
|
hunt := n.Resource.Id()
|
||||||
|
replace := hunt + ".0"
|
||||||
|
if count < 2 {
|
||||||
|
hunt, replace = replace, hunt
|
||||||
|
}
|
||||||
|
|
||||||
|
state, lock := ctx.State()
|
||||||
|
|
||||||
|
// Get a lock so we can access this instance and potentially make
|
||||||
|
// changes to it.
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
||||||
|
mod := state.ModuleByPath(ctx.Path())
|
||||||
|
if mod == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the resource state. If we don't have one, then it is okay.
|
||||||
|
if rs, ok := mod.Resources[hunt]; ok {
|
||||||
|
mod.Resources[replace] = rs
|
||||||
|
delete(mod.Resources, hunt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *EvalCountFixZeroOneBoundary) Type() EvalType {
|
||||||
|
return EvalTypeNull
|
||||||
|
}
|
@ -171,6 +171,11 @@ func (n *GraphNodeConfigProvider) ProviderName() 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
|
||||||
|
|
||||||
|
// If set to true, this represents a resource that can only be
|
||||||
|
// destroyed. It doesn't mean that the resource WILL be destroyed, only
|
||||||
|
// that logically this node is where it would happen.
|
||||||
|
Destroy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) DependableName() []string {
|
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||||
@ -199,19 +204,42 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) Name() string {
|
func (n *GraphNodeConfigResource) Name() string {
|
||||||
return n.Resource.Id()
|
result := n.Resource.Id()
|
||||||
|
if n.Destroy {
|
||||||
|
result += " (destroy)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeDynamicExpandable impl.
|
// GraphNodeDynamicExpandable impl.
|
||||||
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||||
// Build the graph
|
// Start creating the steps
|
||||||
b := &BasicGraphBuilder{
|
steps := make([]GraphTransformer, 0, 5)
|
||||||
Steps: []GraphTransformer{
|
steps = append(steps, &ResourceCountTransformer{
|
||||||
&ResourceCountTransformer{Resource: n.Resource},
|
Resource: n.Resource,
|
||||||
&RootTransformer{},
|
Destroy: n.Destroy,
|
||||||
},
|
})
|
||||||
|
|
||||||
|
// If we're destroying, then we care about adding orphans to
|
||||||
|
// the graph. Orphans in this case are the leftover resources when
|
||||||
|
// we decrease count.
|
||||||
|
if n.Destroy {
|
||||||
|
state, lock := ctx.State()
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
steps = append(steps, &OrphanTransformer{
|
||||||
|
State: state,
|
||||||
|
View: n.Resource.Id(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always end with the root being added
|
||||||
|
steps = append(steps, &RootTransformer{})
|
||||||
|
|
||||||
|
// Build the graph
|
||||||
|
b := &BasicGraphBuilder{Steps: steps}
|
||||||
return b.Build(ctx.Path())
|
return b.Build(ctx.Path())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +252,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode {
|
|||||||
Ops: []walkOperation{walkValidate},
|
Ops: []walkOperation{walkValidate},
|
||||||
Node: &EvalValidateCount{Resource: n.Resource},
|
Node: &EvalValidateCount{Resource: n.Resource},
|
||||||
},
|
},
|
||||||
|
&EvalCountFixZeroOneBoundary{Resource: n.Resource},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,17 +274,16 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
|
|||||||
|
|
||||||
// GraphNodeDestroyable
|
// GraphNodeDestroyable
|
||||||
func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex {
|
func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex {
|
||||||
return &GraphNodeConfigResourceDestroy{Resource: n.Resource}
|
// If we're already a destroy node, then don't do anything
|
||||||
}
|
if n.Destroy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GraphNodeConfigResourceDestroy represents the logical destroy step for
|
// Just make a copy that is set to destroy
|
||||||
// a resource.
|
result := *n
|
||||||
type GraphNodeConfigResourceDestroy struct {
|
result.Destroy = true
|
||||||
Resource *config.Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *GraphNodeConfigResourceDestroy) Name() string {
|
return &result
|
||||||
return fmt.Sprintf("%s (destroy)", n.Resource.Id())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// graphNodeModuleExpanded represents a module where the graph has
|
// graphNodeModuleExpanded represents a module where the graph has
|
||||||
|
@ -8,6 +8,12 @@ import (
|
|||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GraphNodeStateRepresentative is an interface that can be implemented by
|
||||||
|
// a node to say that it is representing a resource in the state.
|
||||||
|
type GraphNodeStateRepresentative interface {
|
||||||
|
StateId() []string
|
||||||
|
}
|
||||||
|
|
||||||
// OrphanTransformer is a GraphTransformer that adds orphans to the
|
// OrphanTransformer is a GraphTransformer that adds orphans to the
|
||||||
// graph. This transformer adds both resource and module orphans.
|
// graph. This transformer adds both resource and module orphans.
|
||||||
type OrphanTransformer struct {
|
type OrphanTransformer struct {
|
||||||
@ -18,6 +24,9 @@ type OrphanTransformer struct {
|
|||||||
// Module is the root module. We'll look up the proper configuration
|
// Module is the root module. We'll look up the proper configuration
|
||||||
// using the graph path.
|
// using the graph path.
|
||||||
Module *module.Tree
|
Module *module.Tree
|
||||||
|
|
||||||
|
// View, if non-nil will set a view on the module state.
|
||||||
|
View string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *OrphanTransformer) Transform(g *Graph) error {
|
func (t *OrphanTransformer) Transform(g *Graph) error {
|
||||||
@ -26,19 +35,42 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build up all our state representatives
|
||||||
|
resourceRep := make(map[string]struct{})
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
if sr, ok := v.(GraphNodeStateRepresentative); ok {
|
||||||
|
for _, k := range sr.StateId() {
|
||||||
|
resourceRep[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var config *config.Config
|
var config *config.Config
|
||||||
if module := t.Module.Child(g.Path[1:]); module != nil {
|
if t.Module != nil {
|
||||||
config = module.Config()
|
if module := t.Module.Child(g.Path[1:]); module != nil {
|
||||||
|
config = module.Config()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var resourceVertexes []dag.Vertex
|
var resourceVertexes []dag.Vertex
|
||||||
if state := t.State.ModuleByPath(g.Path); state != nil {
|
if state := t.State.ModuleByPath(g.Path); state != nil {
|
||||||
// If we have state, then we can have orphan resources
|
// If we have state, then we can have orphan resources
|
||||||
|
|
||||||
|
// If we have a view, get the view
|
||||||
|
if t.View != "" {
|
||||||
|
state = state.View(t.View)
|
||||||
|
}
|
||||||
|
|
||||||
// Go over each resource orphan and add it to the graph.
|
// Go over each resource orphan and add it to the graph.
|
||||||
resourceOrphans := state.Orphans(config)
|
resourceOrphans := state.Orphans(config)
|
||||||
resourceVertexes = make([]dag.Vertex, len(resourceOrphans))
|
resourceVertexes = make([]dag.Vertex, len(resourceOrphans))
|
||||||
for i, k := range resourceOrphans {
|
for i, k := range resourceOrphans {
|
||||||
|
// If this orphan is represented by some other node somehow,
|
||||||
|
// then ignore it.
|
||||||
|
if _, ok := resourceRep[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
rs := state.Resources[k]
|
rs := state.Resources[k]
|
||||||
|
|
||||||
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
// out for a specific resource.
|
// out for a specific resource.
|
||||||
type ResourceCountTransformer struct {
|
type ResourceCountTransformer struct {
|
||||||
Resource *config.Resource
|
Resource *config.Resource
|
||||||
|
Destroy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||||
@ -35,13 +36,21 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
|||||||
index = -1
|
index = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the node for later so we can do connections
|
// Save the node for later so we can do connections. Make the
|
||||||
nodes[i] = &graphNodeExpandedResource{
|
// proper node depending on if we're just a destroy node or if
|
||||||
|
// were a regular node.
|
||||||
|
var node dag.Vertex = &graphNodeExpandedResource{
|
||||||
Index: index,
|
Index: index,
|
||||||
Resource: t.Resource,
|
Resource: t.Resource,
|
||||||
}
|
}
|
||||||
|
if t.Destroy {
|
||||||
|
node = &graphNodeExpandedResourceDestroy{
|
||||||
|
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the node now
|
// Add the node now
|
||||||
|
nodes[i] = node
|
||||||
g.Add(nodes[i])
|
g.Add(nodes[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,3 +194,20 @@ func (n *graphNodeExpandedResource) stateId() string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
|
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// graphNodeExpandedResourceDestroy represents an expanded resource that
|
||||||
|
// is to be destroyed.
|
||||||
|
type graphNodeExpandedResourceDestroy struct {
|
||||||
|
*graphNodeExpandedResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *graphNodeExpandedResourceDestroy) Name() string {
|
||||||
|
return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeEvalable impl.
|
||||||
|
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
|
||||||
|
// TODO: We need an eval tree that destroys when there is a
|
||||||
|
// RequiresNew.
|
||||||
|
return EvalNoop{}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user