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