github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/plans/planfile/tfplan_test.go (about) 1 package planfile 2 3 import ( 4 "bytes" 5 "testing" 6 7 "github.com/go-test/deep" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 12 ) 13 14 func TestTFPlanRoundTrip(t *testing.T) { 15 objTy := cty.Object(map[string]cty.Type{ 16 "id": cty.String, 17 }) 18 19 plan := &plans.Plan{ 20 VariableValues: map[string]plans.DynamicValue{ 21 "foo": mustNewDynamicValueStr("foo value"), 22 }, 23 Changes: &plans.Changes{ 24 Outputs: []*plans.OutputChangeSrc{ 25 { 26 Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance), 27 ChangeSrc: plans.ChangeSrc{ 28 Action: plans.Create, 29 After: mustDynamicOutputValue("bar value"), 30 }, 31 Sensitive: false, 32 }, 33 { 34 Addr: addrs.OutputValue{Name: "baz"}.Absolute(addrs.RootModuleInstance), 35 ChangeSrc: plans.ChangeSrc{ 36 Action: plans.NoOp, 37 Before: mustDynamicOutputValue("baz value"), 38 After: mustDynamicOutputValue("baz value"), 39 }, 40 Sensitive: false, 41 }, 42 { 43 Addr: addrs.OutputValue{Name: "secret"}.Absolute(addrs.RootModuleInstance), 44 ChangeSrc: plans.ChangeSrc{ 45 Action: plans.Update, 46 Before: mustDynamicOutputValue("old secret value"), 47 After: mustDynamicOutputValue("new secret value"), 48 }, 49 Sensitive: true, 50 }, 51 }, 52 Resources: []*plans.ResourceInstanceChangeSrc{ 53 { 54 Addr: addrs.Resource{ 55 Mode: addrs.ManagedResourceMode, 56 Type: "test_thing", 57 Name: "woot", 58 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 59 ProviderAddr: addrs.ProviderConfig{ 60 Type: "test", 61 }.Absolute(addrs.RootModuleInstance), 62 ChangeSrc: plans.ChangeSrc{ 63 Action: plans.DeleteThenCreate, 64 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 65 "id": cty.StringVal("foo-bar-baz"), 66 }), objTy), 67 After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 68 "id": cty.UnknownVal(cty.String), 69 }), objTy), 70 }, 71 }, 72 { 73 Addr: addrs.Resource{ 74 Mode: addrs.ManagedResourceMode, 75 Type: "test_thing", 76 Name: "woot", 77 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 78 DeposedKey: "foodface", 79 ProviderAddr: addrs.ProviderConfig{ 80 Type: "test", 81 }.Absolute(addrs.RootModuleInstance), 82 ChangeSrc: plans.ChangeSrc{ 83 Action: plans.Delete, 84 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 85 "id": cty.StringVal("bar-baz-foo"), 86 }), objTy), 87 }, 88 }, 89 }, 90 }, 91 TargetAddrs: []addrs.Targetable{ 92 addrs.Resource{ 93 Mode: addrs.ManagedResourceMode, 94 Type: "test_thing", 95 Name: "woot", 96 }.Absolute(addrs.RootModuleInstance), 97 }, 98 ProviderSHA256s: map[string][]byte{ 99 "test": { 100 0xba, 0x5e, 0x1e, 0x55, 0xb0, 0x1d, 0xfa, 0xce, 101 0xef, 0xfe, 0xc7, 0xed, 0x1a, 0xbe, 0x11, 0xed, 102 0x5c, 0xa1, 0xab, 0x1e, 0xda, 0x7a, 0xba, 0x5e, 103 0x70, 0x7a, 0x11, 0xed, 0xb0, 0x07, 0xab, 0x1e, 104 }, 105 }, 106 Backend: plans.Backend{ 107 Type: "local", 108 Config: mustNewDynamicValue( 109 cty.ObjectVal(map[string]cty.Value{ 110 "foo": cty.StringVal("bar"), 111 }), 112 cty.Object(map[string]cty.Type{ 113 "foo": cty.String, 114 }), 115 ), 116 Workspace: "default", 117 }, 118 } 119 120 var buf bytes.Buffer 121 err := writeTfplan(plan, &buf) 122 if err != nil { 123 t.Fatal(err) 124 } 125 126 newPlan, err := readTfplan(&buf) 127 if err != nil { 128 t.Fatal(err) 129 } 130 131 { 132 oldDepth := deep.MaxDepth 133 oldCompare := deep.CompareUnexportedFields 134 deep.MaxDepth = 20 135 deep.CompareUnexportedFields = true 136 defer func() { 137 deep.MaxDepth = oldDepth 138 deep.CompareUnexportedFields = oldCompare 139 }() 140 } 141 for _, problem := range deep.Equal(newPlan, plan) { 142 t.Error(problem) 143 } 144 } 145 146 func mustDynamicOutputValue(val string) plans.DynamicValue { 147 ret, err := plans.NewDynamicValue(cty.StringVal(val), cty.DynamicPseudoType) 148 if err != nil { 149 panic(err) 150 } 151 return ret 152 } 153 154 func mustNewDynamicValue(val cty.Value, ty cty.Type) plans.DynamicValue { 155 ret, err := plans.NewDynamicValue(val, ty) 156 if err != nil { 157 panic(err) 158 } 159 return ret 160 } 161 162 func mustNewDynamicValueStr(val string) plans.DynamicValue { 163 realVal := cty.StringVal(val) 164 ret, err := plans.NewDynamicValue(realVal, cty.String) 165 if err != nil { 166 panic(err) 167 } 168 return ret 169 } 170 171 // TestTFPlanRoundTripDestroy ensures that encoding and decoding null values for 172 // destroy doesn't leave us with any nil values. 173 func TestTFPlanRoundTripDestroy(t *testing.T) { 174 objTy := cty.Object(map[string]cty.Type{ 175 "id": cty.String, 176 }) 177 178 plan := &plans.Plan{ 179 Changes: &plans.Changes{ 180 Outputs: []*plans.OutputChangeSrc{ 181 { 182 Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance), 183 ChangeSrc: plans.ChangeSrc{ 184 Action: plans.Delete, 185 Before: mustDynamicOutputValue("output"), 186 After: mustNewDynamicValue(cty.NullVal(cty.String), cty.String), 187 }, 188 }, 189 }, 190 Resources: []*plans.ResourceInstanceChangeSrc{ 191 { 192 Addr: addrs.Resource{ 193 Mode: addrs.ManagedResourceMode, 194 Type: "test_thing", 195 Name: "woot", 196 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 197 ProviderAddr: addrs.ProviderConfig{ 198 Type: "test", 199 }.Absolute(addrs.RootModuleInstance), 200 ChangeSrc: plans.ChangeSrc{ 201 Action: plans.Delete, 202 Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ 203 "id": cty.StringVal("foo-bar-baz"), 204 }), objTy), 205 After: mustNewDynamicValue(cty.NullVal(objTy), objTy), 206 }, 207 }, 208 }, 209 }, 210 TargetAddrs: []addrs.Targetable{ 211 addrs.Resource{ 212 Mode: addrs.ManagedResourceMode, 213 Type: "test_thing", 214 Name: "woot", 215 }.Absolute(addrs.RootModuleInstance), 216 }, 217 Backend: plans.Backend{ 218 Type: "local", 219 Config: mustNewDynamicValue( 220 cty.ObjectVal(map[string]cty.Value{ 221 "foo": cty.StringVal("bar"), 222 }), 223 cty.Object(map[string]cty.Type{ 224 "foo": cty.String, 225 }), 226 ), 227 Workspace: "default", 228 }, 229 } 230 231 var buf bytes.Buffer 232 err := writeTfplan(plan, &buf) 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 newPlan, err := readTfplan(&buf) 238 if err != nil { 239 t.Fatal(err) 240 } 241 242 for _, rics := range newPlan.Changes.Resources { 243 ric, err := rics.Decode(objTy) 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 if ric.After == cty.NilVal { 249 t.Fatalf("unexpected nil After value: %#v\n", ric) 250 } 251 } 252 for _, ocs := range newPlan.Changes.Outputs { 253 oc, err := ocs.Decode() 254 if err != nil { 255 t.Fatal(err) 256 } 257 258 if oc.After == cty.NilVal { 259 t.Fatalf("unexpected nil After value: %#v\n", ocs) 260 } 261 } 262 }