github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_diff_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 "strings" 10 "testing" 11 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/opentofu/opentofu/internal/addrs" 15 "github.com/opentofu/opentofu/internal/plans" 16 ) 17 18 func TestDiffTransformer_nilDiff(t *testing.T) { 19 g := Graph{Path: addrs.RootModuleInstance} 20 tf := &DiffTransformer{} 21 if err := tf.Transform(&g); err != nil { 22 t.Fatalf("err: %s", err) 23 } 24 25 if len(g.Vertices()) > 0 { 26 t.Fatal("graph should be empty") 27 } 28 } 29 30 func TestDiffTransformer(t *testing.T) { 31 g := Graph{Path: addrs.RootModuleInstance} 32 33 beforeVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String) 34 if err != nil { 35 t.Fatal(err) 36 } 37 afterVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 tf := &DiffTransformer{ 43 Changes: &plans.Changes{ 44 Resources: []*plans.ResourceInstanceChangeSrc{ 45 { 46 Addr: addrs.Resource{ 47 Mode: addrs.ManagedResourceMode, 48 Type: "aws_instance", 49 Name: "foo", 50 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 51 ProviderAddr: addrs.AbsProviderConfig{ 52 Provider: addrs.NewDefaultProvider("aws"), 53 Module: addrs.RootModule, 54 }, 55 ChangeSrc: plans.ChangeSrc{ 56 Action: plans.Update, 57 Before: beforeVal, 58 After: afterVal, 59 }, 60 }, 61 }, 62 }, 63 } 64 if err := tf.Transform(&g); err != nil { 65 t.Fatalf("err: %s", err) 66 } 67 68 actual := strings.TrimSpace(g.String()) 69 expected := strings.TrimSpace(testTransformDiffBasicStr) 70 if actual != expected { 71 t.Fatalf("bad:\n\n%s", actual) 72 } 73 } 74 75 func TestDiffTransformer_noOpChange(t *testing.T) { 76 // "No-op" changes are how we record explicitly in a plan that we did 77 // indeed visit a particular resource instance during the planning phase 78 // and concluded that no changes were needed, as opposed to the resource 79 // instance not existing at all or having been excluded from planning 80 // entirely. 81 // 82 // We must include nodes for resource instances with no-op changes in the 83 // apply graph, even though they won't take any external actions, because 84 // there are some secondary effects such as precondition/postcondition 85 // checks that can refer to objects elsewhere and so might have their 86 // results changed even if the resource instance they are attached to 87 // didn't actually change directly itself. 88 89 // aws_instance.foo has a precondition, so should be included in the final 90 // graph. aws_instance.bar has no conditions, so there is nothing to 91 // execute during apply and it should not be included in the graph. 92 m := testModuleInline(t, map[string]string{ 93 "main.tf": ` 94 resource "aws_instance" "bar" { 95 } 96 97 resource "aws_instance" "foo" { 98 test_string = "ok" 99 100 lifecycle { 101 precondition { 102 condition = self.test_string != "" 103 error_message = "resource error" 104 } 105 } 106 } 107 `}) 108 109 g := Graph{Path: addrs.RootModuleInstance} 110 111 beforeVal, err := plans.NewDynamicValue(cty.StringVal(""), cty.String) 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 tf := &DiffTransformer{ 117 Config: m, 118 Changes: &plans.Changes{ 119 Resources: []*plans.ResourceInstanceChangeSrc{ 120 { 121 Addr: addrs.Resource{ 122 Mode: addrs.ManagedResourceMode, 123 Type: "aws_instance", 124 Name: "foo", 125 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 126 ProviderAddr: addrs.AbsProviderConfig{ 127 Provider: addrs.NewDefaultProvider("aws"), 128 Module: addrs.RootModule, 129 }, 130 ChangeSrc: plans.ChangeSrc{ 131 // A "no-op" change has the no-op action and has the 132 // same object as both Before and After. 133 Action: plans.NoOp, 134 Before: beforeVal, 135 After: beforeVal, 136 }, 137 }, 138 { 139 Addr: addrs.Resource{ 140 Mode: addrs.ManagedResourceMode, 141 Type: "aws_instance", 142 Name: "bar", 143 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 144 ProviderAddr: addrs.AbsProviderConfig{ 145 Provider: addrs.NewDefaultProvider("aws"), 146 Module: addrs.RootModule, 147 }, 148 ChangeSrc: plans.ChangeSrc{ 149 // A "no-op" change has the no-op action and has the 150 // same object as both Before and After. 151 Action: plans.NoOp, 152 Before: beforeVal, 153 After: beforeVal, 154 }, 155 }, 156 }, 157 }, 158 } 159 if err := tf.Transform(&g); err != nil { 160 t.Fatalf("err: %s", err) 161 } 162 163 actual := strings.TrimSpace(g.String()) 164 expected := strings.TrimSpace(testTransformDiffBasicStr) 165 if actual != expected { 166 t.Fatalf("bad:\n\n%s", actual) 167 } 168 } 169 170 const testTransformDiffBasicStr = ` 171 aws_instance.foo 172 `