github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_destroy_cbd_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/dag" 15 "github.com/opentofu/opentofu/internal/plans" 16 "github.com/opentofu/opentofu/internal/states" 17 ) 18 19 func cbdTestGraph(t *testing.T, mod string, changes *plans.Changes, state *states.State) *Graph { 20 module := testModule(t, mod) 21 22 applyBuilder := &ApplyGraphBuilder{ 23 Config: module, 24 Changes: changes, 25 Plugins: simpleMockPluginLibrary(), 26 State: state, 27 } 28 g, err := (&BasicGraphBuilder{ 29 Steps: cbdTestSteps(applyBuilder.Steps()), 30 Name: "ApplyGraphBuilder", 31 }).Build(addrs.RootModuleInstance) 32 if err != nil { 33 t.Fatalf("err: %s", err) 34 } 35 36 return filterInstances(g) 37 } 38 39 // override the apply graph builder to halt the process after CBD 40 func cbdTestSteps(steps []GraphTransformer) []GraphTransformer { 41 found := false 42 var i int 43 var t GraphTransformer 44 for i, t = range steps { 45 if _, ok := t.(*CBDEdgeTransformer); ok { 46 found = true 47 break 48 } 49 } 50 51 if !found { 52 panic("CBDEdgeTransformer not found") 53 } 54 55 // re-add the root node so we have a valid graph for a walk, then reduce 56 // the graph for less output 57 steps = append(steps[:i+1], &CloseRootModuleTransformer{}) 58 steps = append(steps, &TransitiveReductionTransformer{}) 59 60 return steps 61 } 62 63 // remove extra nodes for easier test comparisons 64 func filterInstances(g *Graph) *Graph { 65 for _, v := range g.Vertices() { 66 if _, ok := v.(GraphNodeResourceInstance); !ok { 67 // connect around the node to remove it without breaking deps 68 for _, down := range g.DownEdges(v) { 69 for _, up := range g.UpEdges(v) { 70 g.Connect(dag.BasicEdge(up, down)) 71 } 72 } 73 74 g.Remove(v) 75 } 76 77 } 78 return g 79 } 80 81 func TestCBDEdgeTransformer(t *testing.T) { 82 changes := &plans.Changes{ 83 Resources: []*plans.ResourceInstanceChangeSrc{ 84 { 85 Addr: mustResourceInstanceAddr("test_object.A"), 86 ChangeSrc: plans.ChangeSrc{ 87 Action: plans.CreateThenDelete, 88 }, 89 }, 90 { 91 Addr: mustResourceInstanceAddr("test_object.B"), 92 ChangeSrc: plans.ChangeSrc{ 93 Action: plans.Update, 94 }, 95 }, 96 }, 97 } 98 99 state := states.NewState() 100 root := state.EnsureModule(addrs.RootModuleInstance) 101 root.SetResourceInstanceCurrent( 102 mustResourceInstanceAddr("test_object.A").Resource, 103 &states.ResourceInstanceObjectSrc{ 104 Status: states.ObjectReady, 105 AttrsJSON: []byte(`{"id":"A"}`), 106 }, 107 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 108 ) 109 root.SetResourceInstanceCurrent( 110 mustResourceInstanceAddr("test_object.B").Resource, 111 &states.ResourceInstanceObjectSrc{ 112 Status: states.ObjectReady, 113 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 114 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 115 }, 116 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 117 ) 118 119 g := cbdTestGraph(t, "transform-destroy-cbd-edge-basic", changes, state) 120 g = filterInstances(g) 121 122 actual := strings.TrimSpace(g.String()) 123 expected := regexp.MustCompile(strings.TrimSpace(` 124 (?m)test_object.A 125 test_object.A \(destroy deposed \w+\) 126 test_object.B 127 test_object.B 128 test_object.A 129 `)) 130 131 if !expected.MatchString(actual) { 132 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 133 } 134 } 135 136 func TestCBDEdgeTransformerMulti(t *testing.T) { 137 changes := &plans.Changes{ 138 Resources: []*plans.ResourceInstanceChangeSrc{ 139 { 140 Addr: mustResourceInstanceAddr("test_object.A"), 141 ChangeSrc: plans.ChangeSrc{ 142 Action: plans.CreateThenDelete, 143 }, 144 }, 145 { 146 Addr: mustResourceInstanceAddr("test_object.B"), 147 ChangeSrc: plans.ChangeSrc{ 148 Action: plans.CreateThenDelete, 149 }, 150 }, 151 { 152 Addr: mustResourceInstanceAddr("test_object.C"), 153 ChangeSrc: plans.ChangeSrc{ 154 Action: plans.Update, 155 }, 156 }, 157 }, 158 } 159 160 state := states.NewState() 161 root := state.EnsureModule(addrs.RootModuleInstance) 162 root.SetResourceInstanceCurrent( 163 mustResourceInstanceAddr("test_object.A").Resource, 164 &states.ResourceInstanceObjectSrc{ 165 Status: states.ObjectReady, 166 AttrsJSON: []byte(`{"id":"A"}`), 167 }, 168 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 169 ) 170 root.SetResourceInstanceCurrent( 171 mustResourceInstanceAddr("test_object.B").Resource, 172 &states.ResourceInstanceObjectSrc{ 173 Status: states.ObjectReady, 174 AttrsJSON: []byte(`{"id":"B"}`), 175 }, 176 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 177 ) 178 root.SetResourceInstanceCurrent( 179 mustResourceInstanceAddr("test_object.C").Resource, 180 &states.ResourceInstanceObjectSrc{ 181 Status: states.ObjectReady, 182 AttrsJSON: []byte(`{"id":"C","test_list":["x"]}`), 183 Dependencies: []addrs.ConfigResource{ 184 mustConfigResourceAddr("test_object.A"), 185 mustConfigResourceAddr("test_object.B"), 186 }, 187 }, 188 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 189 ) 190 191 g := cbdTestGraph(t, "transform-destroy-cbd-edge-multi", changes, state) 192 g = filterInstances(g) 193 194 actual := strings.TrimSpace(g.String()) 195 expected := regexp.MustCompile(strings.TrimSpace(` 196 (?m)test_object.A 197 test_object.A \(destroy deposed \w+\) 198 test_object.C 199 test_object.B 200 test_object.B \(destroy deposed \w+\) 201 test_object.C 202 test_object.C 203 test_object.A 204 test_object.B 205 `)) 206 207 if !expected.MatchString(actual) { 208 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 209 } 210 } 211 212 func TestCBDEdgeTransformer_depNonCBDCount(t *testing.T) { 213 changes := &plans.Changes{ 214 Resources: []*plans.ResourceInstanceChangeSrc{ 215 { 216 Addr: mustResourceInstanceAddr("test_object.A"), 217 ChangeSrc: plans.ChangeSrc{ 218 Action: plans.CreateThenDelete, 219 }, 220 }, 221 { 222 Addr: mustResourceInstanceAddr("test_object.B[0]"), 223 ChangeSrc: plans.ChangeSrc{ 224 Action: plans.Update, 225 }, 226 }, 227 { 228 Addr: mustResourceInstanceAddr("test_object.B[1]"), 229 ChangeSrc: plans.ChangeSrc{ 230 Action: plans.Update, 231 }, 232 }, 233 }, 234 } 235 236 state := states.NewState() 237 root := state.EnsureModule(addrs.RootModuleInstance) 238 root.SetResourceInstanceCurrent( 239 mustResourceInstanceAddr("test_object.A").Resource, 240 &states.ResourceInstanceObjectSrc{ 241 Status: states.ObjectReady, 242 AttrsJSON: []byte(`{"id":"A"}`), 243 }, 244 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 245 ) 246 root.SetResourceInstanceCurrent( 247 mustResourceInstanceAddr("test_object.B[0]").Resource, 248 &states.ResourceInstanceObjectSrc{ 249 Status: states.ObjectReady, 250 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 251 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 252 }, 253 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 254 ) 255 root.SetResourceInstanceCurrent( 256 mustResourceInstanceAddr("test_object.B[1]").Resource, 257 &states.ResourceInstanceObjectSrc{ 258 Status: states.ObjectReady, 259 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 260 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 261 }, 262 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 263 ) 264 265 g := cbdTestGraph(t, "transform-cbd-destroy-edge-count", changes, state) 266 267 actual := strings.TrimSpace(g.String()) 268 expected := regexp.MustCompile(strings.TrimSpace(` 269 (?m)test_object.A 270 test_object.A \(destroy deposed \w+\) 271 test_object.B\[0\] 272 test_object.B\[1\] 273 test_object.B\[0\] 274 test_object.A 275 test_object.B\[1\] 276 test_object.A`)) 277 278 if !expected.MatchString(actual) { 279 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 280 } 281 } 282 283 func TestCBDEdgeTransformer_depNonCBDCountBoth(t *testing.T) { 284 changes := &plans.Changes{ 285 Resources: []*plans.ResourceInstanceChangeSrc{ 286 { 287 Addr: mustResourceInstanceAddr("test_object.A[0]"), 288 ChangeSrc: plans.ChangeSrc{ 289 Action: plans.CreateThenDelete, 290 }, 291 }, 292 { 293 Addr: mustResourceInstanceAddr("test_object.A[1]"), 294 ChangeSrc: plans.ChangeSrc{ 295 Action: plans.CreateThenDelete, 296 }, 297 }, 298 { 299 Addr: mustResourceInstanceAddr("test_object.B[0]"), 300 ChangeSrc: plans.ChangeSrc{ 301 Action: plans.Update, 302 }, 303 }, 304 { 305 Addr: mustResourceInstanceAddr("test_object.B[1]"), 306 ChangeSrc: plans.ChangeSrc{ 307 Action: plans.Update, 308 }, 309 }, 310 }, 311 } 312 313 state := states.NewState() 314 root := state.EnsureModule(addrs.RootModuleInstance) 315 root.SetResourceInstanceCurrent( 316 mustResourceInstanceAddr("test_object.A[0]").Resource, 317 &states.ResourceInstanceObjectSrc{ 318 Status: states.ObjectReady, 319 AttrsJSON: []byte(`{"id":"A"}`), 320 }, 321 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 322 ) 323 root.SetResourceInstanceCurrent( 324 mustResourceInstanceAddr("test_object.A[1]").Resource, 325 &states.ResourceInstanceObjectSrc{ 326 Status: states.ObjectReady, 327 AttrsJSON: []byte(`{"id":"A"}`), 328 }, 329 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 330 ) 331 root.SetResourceInstanceCurrent( 332 mustResourceInstanceAddr("test_object.B[0]").Resource, 333 &states.ResourceInstanceObjectSrc{ 334 Status: states.ObjectReady, 335 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 336 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 337 }, 338 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 339 ) 340 root.SetResourceInstanceCurrent( 341 mustResourceInstanceAddr("test_object.B[1]").Resource, 342 &states.ResourceInstanceObjectSrc{ 343 Status: states.ObjectReady, 344 AttrsJSON: []byte(`{"id":"B","test_list":["x"]}`), 345 Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")}, 346 }, 347 mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`), 348 ) 349 350 g := cbdTestGraph(t, "transform-cbd-destroy-edge-both-count", changes, state) 351 352 actual := strings.TrimSpace(g.String()) 353 expected := regexp.MustCompile(strings.TrimSpace(` 354 test_object.A\[0\] 355 test_object.A\[0\] \(destroy deposed \w+\) 356 test_object.B\[0\] 357 test_object.B\[1\] 358 test_object.A\[1\] 359 test_object.A\[1\] \(destroy deposed \w+\) 360 test_object.B\[0\] 361 test_object.B\[1\] 362 test_object.B\[0\] 363 test_object.A\[0\] 364 test_object.A\[1\] 365 test_object.B\[1\] 366 test_object.A\[0\] 367 test_object.A\[1\] 368 `)) 369 370 if !expected.MatchString(actual) { 371 t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected) 372 } 373 }