mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -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) {
|
||||
m := testModule(t, "plan-count-dec")
|
||||
p := testProvider("aws")
|
||||
@ -741,6 +740,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestContextPlan_countIncreaseFromNotSet(t *testing.T) {
|
||||
m := testModule(t, "plan-count-inc")
|
||||
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.
|
||||
type GraphNodeConfigResource struct {
|
||||
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 {
|
||||
@ -199,19 +204,42 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
|
||||
}
|
||||
|
||||
func (n *GraphNodeConfigResource) Name() string {
|
||||
return n.Resource.Id()
|
||||
result := n.Resource.Id()
|
||||
if n.Destroy {
|
||||
result += " (destroy)"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GraphNodeDynamicExpandable impl.
|
||||
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||
// Build the graph
|
||||
b := &BasicGraphBuilder{
|
||||
Steps: []GraphTransformer{
|
||||
&ResourceCountTransformer{Resource: n.Resource},
|
||||
&RootTransformer{},
|
||||
},
|
||||
// Start creating the steps
|
||||
steps := make([]GraphTransformer, 0, 5)
|
||||
steps = append(steps, &ResourceCountTransformer{
|
||||
Resource: n.Resource,
|
||||
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())
|
||||
}
|
||||
|
||||
@ -224,6 +252,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode {
|
||||
Ops: []walkOperation{walkValidate},
|
||||
Node: &EvalValidateCount{Resource: n.Resource},
|
||||
},
|
||||
&EvalCountFixZeroOneBoundary{Resource: n.Resource},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -245,17 +274,16 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
|
||||
|
||||
// GraphNodeDestroyable
|
||||
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
|
||||
// a resource.
|
||||
type GraphNodeConfigResourceDestroy struct {
|
||||
Resource *config.Resource
|
||||
}
|
||||
// Just make a copy that is set to destroy
|
||||
result := *n
|
||||
result.Destroy = true
|
||||
|
||||
func (n *GraphNodeConfigResourceDestroy) Name() string {
|
||||
return fmt.Sprintf("%s (destroy)", n.Resource.Id())
|
||||
return &result
|
||||
}
|
||||
|
||||
// graphNodeModuleExpanded represents a module where the graph has
|
||||
|
@ -8,6 +8,12 @@ import (
|
||||
"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
|
||||
// graph. This transformer adds both resource and module orphans.
|
||||
type OrphanTransformer struct {
|
||||
@ -18,6 +24,9 @@ type OrphanTransformer struct {
|
||||
// Module is the root module. We'll look up the proper configuration
|
||||
// using the graph path.
|
||||
Module *module.Tree
|
||||
|
||||
// View, if non-nil will set a view on the module state.
|
||||
View string
|
||||
}
|
||||
|
||||
func (t *OrphanTransformer) Transform(g *Graph) error {
|
||||
@ -26,19 +35,42 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
|
||||
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
|
||||
if module := t.Module.Child(g.Path[1:]); module != nil {
|
||||
config = module.Config()
|
||||
if t.Module != nil {
|
||||
if module := t.Module.Child(g.Path[1:]); module != nil {
|
||||
config = module.Config()
|
||||
}
|
||||
}
|
||||
|
||||
var resourceVertexes []dag.Vertex
|
||||
if state := t.State.ModuleByPath(g.Path); state != nil {
|
||||
// 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.
|
||||
resourceOrphans := state.Orphans(config)
|
||||
resourceVertexes = make([]dag.Vertex, len(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]
|
||||
|
||||
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
// out for a specific resource.
|
||||
type ResourceCountTransformer struct {
|
||||
Resource *config.Resource
|
||||
Destroy bool
|
||||
}
|
||||
|
||||
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||
@ -35,13 +36,21 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||
index = -1
|
||||
}
|
||||
|
||||
// Save the node for later so we can do connections
|
||||
nodes[i] = &graphNodeExpandedResource{
|
||||
// Save the node for later so we can do connections. Make the
|
||||
// proper node depending on if we're just a destroy node or if
|
||||
// were a regular node.
|
||||
var node dag.Vertex = &graphNodeExpandedResource{
|
||||
Index: index,
|
||||
Resource: t.Resource,
|
||||
}
|
||||
if t.Destroy {
|
||||
node = &graphNodeExpandedResourceDestroy{
|
||||
graphNodeExpandedResource: node.(*graphNodeExpandedResource),
|
||||
}
|
||||
}
|
||||
|
||||
// Add the node now
|
||||
nodes[i] = node
|
||||
g.Add(nodes[i])
|
||||
}
|
||||
|
||||
@ -185,3 +194,20 @@ func (n *graphNodeExpandedResource) stateId() string {
|
||||
|
||||
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