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