github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_orphan_resource_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 "fmt" 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/states" 16 ) 17 18 func TestOrphanResourceInstanceTransformer(t *testing.T) { 19 mod := testModule(t, "transform-orphan-basic") 20 21 state := states.BuildState(func(s *states.SyncState) { 22 s.SetResourceInstanceCurrent( 23 addrs.Resource{ 24 Mode: addrs.ManagedResourceMode, 25 Type: "aws_instance", 26 Name: "web", 27 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 28 &states.ResourceInstanceObjectSrc{ 29 AttrsFlat: map[string]string{ 30 "id": "foo", 31 }, 32 Status: states.ObjectReady, 33 }, 34 addrs.AbsProviderConfig{ 35 Provider: addrs.NewDefaultProvider("aws"), 36 Module: addrs.RootModule, 37 }, 38 ) 39 40 // The orphan 41 s.SetResourceInstanceCurrent( 42 addrs.Resource{ 43 Mode: addrs.ManagedResourceMode, 44 Type: "aws_instance", 45 Name: "db", 46 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 47 &states.ResourceInstanceObjectSrc{ 48 AttrsFlat: map[string]string{ 49 "id": "foo", 50 }, 51 Status: states.ObjectReady, 52 }, 53 addrs.AbsProviderConfig{ 54 Provider: addrs.NewDefaultProvider("aws"), 55 Module: addrs.RootModule, 56 }, 57 ) 58 59 // A deposed orphan should not be handled by this transformer 60 s.SetResourceInstanceDeposed( 61 addrs.Resource{ 62 Mode: addrs.ManagedResourceMode, 63 Type: "test_instance", 64 Name: "deposed", 65 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 66 states.NewDeposedKey(), 67 &states.ResourceInstanceObjectSrc{ 68 AttrsFlat: map[string]string{ 69 "id": "foo", 70 }, 71 Status: states.ObjectReady, 72 }, 73 addrs.AbsProviderConfig{ 74 Provider: addrs.NewDefaultProvider("test"), 75 Module: addrs.RootModule, 76 }, 77 ) 78 }) 79 80 g := Graph{Path: addrs.RootModuleInstance} 81 { 82 tf := &ConfigTransformer{Config: mod} 83 if err := tf.Transform(&g); err != nil { 84 t.Fatalf("err: %s", err) 85 } 86 } 87 88 { 89 tf := &OrphanResourceInstanceTransformer{ 90 Concrete: testOrphanResourceConcreteFunc, 91 State: state, 92 Config: mod, 93 } 94 if err := tf.Transform(&g); err != nil { 95 t.Fatalf("err: %s", err) 96 } 97 } 98 99 actual := strings.TrimSpace(g.String()) 100 expected := strings.TrimSpace(testTransformOrphanResourceBasicStr) 101 if actual != expected { 102 t.Fatalf("bad:\n\n%s", actual) 103 } 104 } 105 106 func TestOrphanResourceInstanceTransformer_countGood(t *testing.T) { 107 mod := testModule(t, "transform-orphan-count") 108 109 state := states.BuildState(func(s *states.SyncState) { 110 s.SetResourceInstanceCurrent( 111 addrs.Resource{ 112 Mode: addrs.ManagedResourceMode, 113 Type: "aws_instance", 114 Name: "foo", 115 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 116 &states.ResourceInstanceObjectSrc{ 117 AttrsFlat: map[string]string{ 118 "id": "foo", 119 }, 120 Status: states.ObjectReady, 121 }, 122 addrs.AbsProviderConfig{ 123 Provider: addrs.NewDefaultProvider("aws"), 124 Module: addrs.RootModule, 125 }, 126 ) 127 s.SetResourceInstanceCurrent( 128 addrs.Resource{ 129 Mode: addrs.ManagedResourceMode, 130 Type: "aws_instance", 131 Name: "foo", 132 }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), 133 &states.ResourceInstanceObjectSrc{ 134 AttrsFlat: map[string]string{ 135 "id": "foo", 136 }, 137 Status: states.ObjectReady, 138 }, 139 addrs.AbsProviderConfig{ 140 Provider: addrs.NewDefaultProvider("aws"), 141 Module: addrs.RootModule, 142 }, 143 ) 144 }) 145 146 g := Graph{Path: addrs.RootModuleInstance} 147 { 148 tf := &ConfigTransformer{Config: mod} 149 if err := tf.Transform(&g); err != nil { 150 t.Fatalf("err: %s", err) 151 } 152 } 153 154 { 155 tf := &OrphanResourceInstanceTransformer{ 156 Concrete: testOrphanResourceConcreteFunc, 157 State: state, 158 Config: mod, 159 } 160 if err := tf.Transform(&g); err != nil { 161 t.Fatalf("err: %s", err) 162 } 163 } 164 165 actual := strings.TrimSpace(g.String()) 166 expected := strings.TrimSpace(testTransformOrphanResourceCountStr) 167 if actual != expected { 168 t.Fatalf("bad:\n\n%s", actual) 169 } 170 } 171 172 func TestOrphanResourceInstanceTransformer_countBad(t *testing.T) { 173 mod := testModule(t, "transform-orphan-count-empty") 174 state := states.BuildState(func(s *states.SyncState) { 175 s.SetResourceInstanceCurrent( 176 addrs.Resource{ 177 Mode: addrs.ManagedResourceMode, 178 Type: "aws_instance", 179 Name: "foo", 180 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 181 &states.ResourceInstanceObjectSrc{ 182 AttrsFlat: map[string]string{ 183 "id": "foo", 184 }, 185 Status: states.ObjectReady, 186 }, 187 addrs.AbsProviderConfig{ 188 Provider: addrs.NewDefaultProvider("aws"), 189 Module: addrs.RootModule, 190 }, 191 ) 192 s.SetResourceInstanceCurrent( 193 addrs.Resource{ 194 Mode: addrs.ManagedResourceMode, 195 Type: "aws_instance", 196 Name: "foo", 197 }.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance), 198 &states.ResourceInstanceObjectSrc{ 199 AttrsFlat: map[string]string{ 200 "id": "foo", 201 }, 202 Status: states.ObjectReady, 203 }, 204 addrs.AbsProviderConfig{ 205 Provider: addrs.NewDefaultProvider("aws"), 206 Module: addrs.RootModule, 207 }, 208 ) 209 }) 210 211 g := Graph{Path: addrs.RootModuleInstance} 212 { 213 tf := &ConfigTransformer{Config: mod} 214 if err := tf.Transform(&g); err != nil { 215 t.Fatalf("err: %s", err) 216 } 217 } 218 219 { 220 tf := &OrphanResourceInstanceTransformer{ 221 Concrete: testOrphanResourceConcreteFunc, 222 State: state, 223 Config: mod, 224 } 225 if err := tf.Transform(&g); err != nil { 226 t.Fatalf("err: %s", err) 227 } 228 } 229 230 actual := strings.TrimSpace(g.String()) 231 expected := strings.TrimSpace(testTransformOrphanResourceCountBadStr) 232 if actual != expected { 233 t.Fatalf("bad:\n\n%s", actual) 234 } 235 } 236 237 func TestOrphanResourceInstanceTransformer_modules(t *testing.T) { 238 mod := testModule(t, "transform-orphan-modules") 239 state := states.BuildState(func(s *states.SyncState) { 240 s.SetResourceInstanceCurrent( 241 addrs.Resource{ 242 Mode: addrs.ManagedResourceMode, 243 Type: "aws_instance", 244 Name: "foo", 245 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 246 &states.ResourceInstanceObjectSrc{ 247 AttrsFlat: map[string]string{ 248 "id": "foo", 249 }, 250 Status: states.ObjectReady, 251 }, 252 addrs.AbsProviderConfig{ 253 Provider: addrs.NewDefaultProvider("aws"), 254 Module: addrs.RootModule, 255 }, 256 ) 257 s.SetResourceInstanceCurrent( 258 addrs.Resource{ 259 Mode: addrs.ManagedResourceMode, 260 Type: "aws_instance", 261 Name: "web", 262 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)), 263 &states.ResourceInstanceObjectSrc{ 264 AttrsFlat: map[string]string{ 265 "id": "foo", 266 }, 267 Status: states.ObjectReady, 268 }, 269 addrs.AbsProviderConfig{ 270 Provider: addrs.NewDefaultProvider("aws"), 271 Module: addrs.RootModule, 272 }, 273 ) 274 }) 275 276 g := Graph{Path: addrs.RootModuleInstance} 277 { 278 tf := &ConfigTransformer{Config: mod} 279 if err := tf.Transform(&g); err != nil { 280 t.Fatalf("err: %s", err) 281 } 282 } 283 284 { 285 tf := &OrphanResourceInstanceTransformer{ 286 Concrete: testOrphanResourceConcreteFunc, 287 State: state, 288 Config: mod, 289 } 290 if err := tf.Transform(&g); err != nil { 291 t.Fatalf("err: %s", err) 292 } 293 } 294 295 got := strings.TrimSpace(g.String()) 296 want := strings.TrimSpace(testTransformOrphanResourceModulesStr) 297 if got != want { 298 t.Fatalf("wrong state result\ngot:\n%s\n\nwant:\n%s", got, want) 299 } 300 } 301 302 const testTransformOrphanResourceBasicStr = ` 303 aws_instance.db (orphan) 304 aws_instance.web 305 ` 306 307 const testTransformOrphanResourceCountStr = ` 308 aws_instance.foo 309 ` 310 311 const testTransformOrphanResourceCountBadStr = ` 312 aws_instance.foo[0] (orphan) 313 aws_instance.foo[1] (orphan) 314 ` 315 316 const testTransformOrphanResourceModulesStr = ` 317 aws_instance.foo 318 module.child.aws_instance.web (orphan) 319 ` 320 321 func testOrphanResourceConcreteFunc(a *NodeAbstractResourceInstance) dag.Vertex { 322 return &testOrphanResourceInstanceConcrete{a} 323 } 324 325 type testOrphanResourceInstanceConcrete struct { 326 *NodeAbstractResourceInstance 327 } 328 329 func (n *testOrphanResourceInstanceConcrete) Name() string { 330 return fmt.Sprintf("%s (orphan)", n.NodeAbstractResourceInstance.Name()) 331 }