github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/evaluate_test.go (about) 1 package terraform 2 3 import ( 4 "sync" 5 "testing" 6 7 "github.com/davecgh/go-spew/spew" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/internal/addrs" 11 "github.com/hashicorp/terraform/internal/configs" 12 "github.com/hashicorp/terraform/internal/configs/configschema" 13 "github.com/hashicorp/terraform/internal/lang/marks" 14 "github.com/hashicorp/terraform/internal/plans" 15 "github.com/hashicorp/terraform/internal/states" 16 "github.com/hashicorp/terraform/internal/tfdiags" 17 ) 18 19 func TestEvaluatorGetTerraformAttr(t *testing.T) { 20 evaluator := &Evaluator{ 21 Meta: &ContextMeta{ 22 Env: "foo", 23 }, 24 } 25 data := &evaluationStateData{ 26 Evaluator: evaluator, 27 } 28 scope := evaluator.Scope(data, nil) 29 30 t.Run("workspace", func(t *testing.T) { 31 want := cty.StringVal("foo") 32 got, diags := scope.Data.GetTerraformAttr(addrs.TerraformAttr{ 33 Name: "workspace", 34 }, tfdiags.SourceRange{}) 35 if len(diags) != 0 { 36 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 37 } 38 if !got.RawEquals(want) { 39 t.Errorf("wrong result %q; want %q", got, want) 40 } 41 }) 42 } 43 44 func TestEvaluatorGetPathAttr(t *testing.T) { 45 evaluator := &Evaluator{ 46 Meta: &ContextMeta{ 47 Env: "foo", 48 }, 49 Config: &configs.Config{ 50 Module: &configs.Module{ 51 SourceDir: "bar/baz", 52 }, 53 }, 54 } 55 data := &evaluationStateData{ 56 Evaluator: evaluator, 57 } 58 scope := evaluator.Scope(data, nil) 59 60 t.Run("module", func(t *testing.T) { 61 want := cty.StringVal("bar/baz") 62 got, diags := scope.Data.GetPathAttr(addrs.PathAttr{ 63 Name: "module", 64 }, tfdiags.SourceRange{}) 65 if len(diags) != 0 { 66 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 67 } 68 if !got.RawEquals(want) { 69 t.Errorf("wrong result %#v; want %#v", got, want) 70 } 71 }) 72 73 t.Run("root", func(t *testing.T) { 74 want := cty.StringVal("bar/baz") 75 got, diags := scope.Data.GetPathAttr(addrs.PathAttr{ 76 Name: "root", 77 }, tfdiags.SourceRange{}) 78 if len(diags) != 0 { 79 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 80 } 81 if !got.RawEquals(want) { 82 t.Errorf("wrong result %#v; want %#v", got, want) 83 } 84 }) 85 } 86 87 // This particularly tests that a sensitive attribute in config 88 // results in a value that has a "sensitive" cty Mark 89 func TestEvaluatorGetInputVariable(t *testing.T) { 90 evaluator := &Evaluator{ 91 Meta: &ContextMeta{ 92 Env: "foo", 93 }, 94 Config: &configs.Config{ 95 Module: &configs.Module{ 96 Variables: map[string]*configs.Variable{ 97 "some_var": { 98 Name: "some_var", 99 Sensitive: true, 100 Default: cty.StringVal("foo"), 101 }, 102 // Avoid double marking a value 103 "some_other_var": { 104 Name: "some_other_var", 105 Sensitive: true, 106 Default: cty.StringVal("bar"), 107 }, 108 }, 109 }, 110 }, 111 VariableValues: map[string]map[string]cty.Value{ 112 "": { 113 "some_var": cty.StringVal("bar"), 114 "some_other_var": cty.StringVal("boop").Mark(marks.Sensitive), 115 }, 116 }, 117 VariableValuesLock: &sync.Mutex{}, 118 } 119 120 data := &evaluationStateData{ 121 Evaluator: evaluator, 122 } 123 scope := evaluator.Scope(data, nil) 124 125 want := cty.StringVal("bar").Mark(marks.Sensitive) 126 got, diags := scope.Data.GetInputVariable(addrs.InputVariable{ 127 Name: "some_var", 128 }, tfdiags.SourceRange{}) 129 130 if len(diags) != 0 { 131 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 132 } 133 if !got.RawEquals(want) { 134 t.Errorf("wrong result %#v; want %#v", got, want) 135 } 136 137 want = cty.StringVal("boop").Mark(marks.Sensitive) 138 got, diags = scope.Data.GetInputVariable(addrs.InputVariable{ 139 Name: "some_other_var", 140 }, tfdiags.SourceRange{}) 141 142 if len(diags) != 0 { 143 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 144 } 145 if !got.RawEquals(want) { 146 t.Errorf("wrong result %#v; want %#v", got, want) 147 } 148 } 149 150 func TestEvaluatorGetResource(t *testing.T) { 151 stateSync := states.BuildState(func(ss *states.SyncState) { 152 ss.SetResourceInstanceCurrent( 153 addrs.Resource{ 154 Mode: addrs.ManagedResourceMode, 155 Type: "test_resource", 156 Name: "foo", 157 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 158 &states.ResourceInstanceObjectSrc{ 159 Status: states.ObjectReady, 160 AttrsJSON: []byte(`{"id":"foo", "nesting_list": [{"sensitive_value":"abc"}], "nesting_map": {"foo":{"foo":"x"}}, "nesting_set": [{"baz":"abc"}], "nesting_single": {"boop":"abc"}, "nesting_nesting": {"nesting_list":[{"sensitive_value":"abc"}]}, "value":"hello"}`), 161 }, 162 addrs.AbsProviderConfig{ 163 Provider: addrs.NewDefaultProvider("test"), 164 Module: addrs.RootModule, 165 }, 166 ) 167 }).SyncWrapper() 168 169 rc := &configs.Resource{ 170 Mode: addrs.ManagedResourceMode, 171 Type: "test_resource", 172 Name: "foo", 173 Config: configs.SynthBody("", map[string]cty.Value{ 174 "id": cty.StringVal("foo"), 175 }), 176 Provider: addrs.Provider{ 177 Hostname: addrs.DefaultProviderRegistryHost, 178 Namespace: "hashicorp", 179 Type: "test", 180 }, 181 } 182 183 evaluator := &Evaluator{ 184 Meta: &ContextMeta{ 185 Env: "foo", 186 }, 187 Changes: plans.NewChanges().SyncWrapper(), 188 Config: &configs.Config{ 189 Module: &configs.Module{ 190 ManagedResources: map[string]*configs.Resource{ 191 "test_resource.foo": rc, 192 }, 193 }, 194 }, 195 State: stateSync, 196 Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]*ProviderSchema{ 197 addrs.NewDefaultProvider("test"): { 198 Provider: &configschema.Block{}, 199 ResourceTypes: map[string]*configschema.Block{ 200 "test_resource": { 201 Attributes: map[string]*configschema.Attribute{ 202 "id": { 203 Type: cty.String, 204 Computed: true, 205 }, 206 "value": { 207 Type: cty.String, 208 Computed: true, 209 Sensitive: true, 210 }, 211 }, 212 BlockTypes: map[string]*configschema.NestedBlock{ 213 "nesting_list": { 214 Block: configschema.Block{ 215 Attributes: map[string]*configschema.Attribute{ 216 "value": {Type: cty.String, Optional: true}, 217 "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, 218 }, 219 }, 220 Nesting: configschema.NestingList, 221 }, 222 "nesting_map": { 223 Block: configschema.Block{ 224 Attributes: map[string]*configschema.Attribute{ 225 "foo": {Type: cty.String, Optional: true, Sensitive: true}, 226 }, 227 }, 228 Nesting: configschema.NestingMap, 229 }, 230 "nesting_set": { 231 Block: configschema.Block{ 232 Attributes: map[string]*configschema.Attribute{ 233 "baz": {Type: cty.String, Optional: true, Sensitive: true}, 234 }, 235 }, 236 Nesting: configschema.NestingSet, 237 }, 238 "nesting_single": { 239 Block: configschema.Block{ 240 Attributes: map[string]*configschema.Attribute{ 241 "boop": {Type: cty.String, Optional: true, Sensitive: true}, 242 }, 243 }, 244 Nesting: configschema.NestingSingle, 245 }, 246 "nesting_nesting": { 247 Block: configschema.Block{ 248 BlockTypes: map[string]*configschema.NestedBlock{ 249 "nesting_list": { 250 Block: configschema.Block{ 251 Attributes: map[string]*configschema.Attribute{ 252 "value": {Type: cty.String, Optional: true}, 253 "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, 254 }, 255 }, 256 Nesting: configschema.NestingList, 257 }, 258 }, 259 }, 260 Nesting: configschema.NestingSingle, 261 }, 262 }, 263 }, 264 }, 265 }, 266 }), 267 } 268 269 data := &evaluationStateData{ 270 Evaluator: evaluator, 271 } 272 scope := evaluator.Scope(data, nil) 273 274 want := cty.ObjectVal(map[string]cty.Value{ 275 "id": cty.StringVal("foo"), 276 "nesting_list": cty.ListVal([]cty.Value{ 277 cty.ObjectVal(map[string]cty.Value{ 278 "sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive), 279 "value": cty.NullVal(cty.String), 280 }), 281 }), 282 "nesting_map": cty.MapVal(map[string]cty.Value{ 283 "foo": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("x").Mark(marks.Sensitive)}), 284 }), 285 "nesting_nesting": cty.ObjectVal(map[string]cty.Value{ 286 "nesting_list": cty.ListVal([]cty.Value{ 287 cty.ObjectVal(map[string]cty.Value{ 288 "sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive), 289 "value": cty.NullVal(cty.String), 290 }), 291 }), 292 }), 293 "nesting_set": cty.SetVal([]cty.Value{ 294 cty.ObjectVal(map[string]cty.Value{ 295 "baz": cty.StringVal("abc").Mark(marks.Sensitive), 296 }), 297 }), 298 "nesting_single": cty.ObjectVal(map[string]cty.Value{ 299 "boop": cty.StringVal("abc").Mark(marks.Sensitive), 300 }), 301 "value": cty.StringVal("hello").Mark(marks.Sensitive), 302 }) 303 304 addr := addrs.Resource{ 305 Mode: addrs.ManagedResourceMode, 306 Type: "test_resource", 307 Name: "foo", 308 } 309 got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{}) 310 311 if len(diags) != 0 { 312 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 313 } 314 315 if !got.RawEquals(want) { 316 t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want) 317 } 318 } 319 320 // GetResource will return a planned object's After value 321 // if there is a change for that resource instance. 322 func TestEvaluatorGetResource_changes(t *testing.T) { 323 // Set up existing state 324 stateSync := states.BuildState(func(ss *states.SyncState) { 325 ss.SetResourceInstanceCurrent( 326 addrs.Resource{ 327 Mode: addrs.ManagedResourceMode, 328 Type: "test_resource", 329 Name: "foo", 330 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 331 &states.ResourceInstanceObjectSrc{ 332 Status: states.ObjectPlanned, 333 AttrsJSON: []byte(`{"id":"foo", "to_mark_val":"tacos", "sensitive_value":"abc"}`), 334 }, 335 addrs.AbsProviderConfig{ 336 Provider: addrs.NewDefaultProvider("test"), 337 Module: addrs.RootModule, 338 }, 339 ) 340 }).SyncWrapper() 341 342 // Create a change for the existing state resource, 343 // to exercise retrieving the After value of the change 344 changesSync := plans.NewChanges().SyncWrapper() 345 change := &plans.ResourceInstanceChange{ 346 Addr: mustResourceInstanceAddr("test_resource.foo"), 347 ProviderAddr: addrs.AbsProviderConfig{ 348 Module: addrs.RootModule, 349 Provider: addrs.NewDefaultProvider("test"), 350 }, 351 Change: plans.Change{ 352 Action: plans.Update, 353 // Provide an After value that contains a marked value 354 After: cty.ObjectVal(map[string]cty.Value{ 355 "id": cty.StringVal("foo"), 356 "to_mark_val": cty.StringVal("pizza").Mark(marks.Sensitive), 357 "sensitive_value": cty.StringVal("abc"), 358 "sensitive_collection": cty.MapVal(map[string]cty.Value{ 359 "boop": cty.StringVal("beep"), 360 }), 361 }), 362 }, 363 } 364 365 // Set up our schemas 366 schemas := &Schemas{ 367 Providers: map[addrs.Provider]*ProviderSchema{ 368 addrs.NewDefaultProvider("test"): { 369 Provider: &configschema.Block{}, 370 ResourceTypes: map[string]*configschema.Block{ 371 "test_resource": { 372 Attributes: map[string]*configschema.Attribute{ 373 "id": { 374 Type: cty.String, 375 Computed: true, 376 }, 377 "to_mark_val": { 378 Type: cty.String, 379 Computed: true, 380 }, 381 "sensitive_value": { 382 Type: cty.String, 383 Computed: true, 384 Sensitive: true, 385 }, 386 "sensitive_collection": { 387 Type: cty.Map(cty.String), 388 Computed: true, 389 Sensitive: true, 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 } 397 398 // The resource we'll inspect 399 addr := addrs.Resource{ 400 Mode: addrs.ManagedResourceMode, 401 Type: "test_resource", 402 Name: "foo", 403 } 404 schema, _ := schemas.ResourceTypeConfig(addrs.NewDefaultProvider("test"), addr.Mode, addr.Type) 405 // This encoding separates out the After's marks into its AfterValMarks 406 csrc, _ := change.Encode(schema.ImpliedType()) 407 changesSync.AppendResourceInstanceChange(csrc) 408 409 evaluator := &Evaluator{ 410 Meta: &ContextMeta{ 411 Env: "foo", 412 }, 413 Changes: changesSync, 414 Config: &configs.Config{ 415 Module: &configs.Module{ 416 ManagedResources: map[string]*configs.Resource{ 417 "test_resource.foo": { 418 Mode: addrs.ManagedResourceMode, 419 Type: "test_resource", 420 Name: "foo", 421 Provider: addrs.Provider{ 422 Hostname: addrs.DefaultProviderRegistryHost, 423 Namespace: "hashicorp", 424 Type: "test", 425 }, 426 }, 427 }, 428 }, 429 }, 430 State: stateSync, 431 Plugins: schemaOnlyProvidersForTesting(schemas.Providers), 432 } 433 434 data := &evaluationStateData{ 435 Evaluator: evaluator, 436 } 437 scope := evaluator.Scope(data, nil) 438 439 want := cty.ObjectVal(map[string]cty.Value{ 440 "id": cty.StringVal("foo"), 441 "to_mark_val": cty.StringVal("pizza").Mark(marks.Sensitive), 442 "sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive), 443 "sensitive_collection": cty.MapVal(map[string]cty.Value{ 444 "boop": cty.StringVal("beep"), 445 }).Mark(marks.Sensitive), 446 }) 447 448 got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{}) 449 450 if len(diags) != 0 { 451 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 452 } 453 454 if !got.RawEquals(want) { 455 t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want) 456 } 457 } 458 459 func TestEvaluatorGetModule(t *testing.T) { 460 // Create a new evaluator with an existing state 461 stateSync := states.BuildState(func(ss *states.SyncState) { 462 ss.SetOutputValue( 463 addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}), 464 cty.StringVal("bar"), 465 true, 466 ) 467 }).SyncWrapper() 468 evaluator := evaluatorForModule(stateSync, plans.NewChanges().SyncWrapper()) 469 data := &evaluationStateData{ 470 Evaluator: evaluator, 471 } 472 scope := evaluator.Scope(data, nil) 473 want := cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("bar").Mark(marks.Sensitive)}) 474 got, diags := scope.Data.GetModule(addrs.ModuleCall{ 475 Name: "mod", 476 }, tfdiags.SourceRange{}) 477 478 if len(diags) != 0 { 479 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 480 } 481 if !got.RawEquals(want) { 482 t.Errorf("wrong result %#v; want %#v", got, want) 483 } 484 485 // Changes should override the state value 486 changesSync := plans.NewChanges().SyncWrapper() 487 change := &plans.OutputChange{ 488 Addr: addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}), 489 Sensitive: true, 490 Change: plans.Change{ 491 After: cty.StringVal("baz"), 492 }, 493 } 494 cs, _ := change.Encode() 495 changesSync.AppendOutputChange(cs) 496 evaluator = evaluatorForModule(stateSync, changesSync) 497 data = &evaluationStateData{ 498 Evaluator: evaluator, 499 } 500 scope = evaluator.Scope(data, nil) 501 want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)}) 502 got, diags = scope.Data.GetModule(addrs.ModuleCall{ 503 Name: "mod", 504 }, tfdiags.SourceRange{}) 505 506 if len(diags) != 0 { 507 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 508 } 509 if !got.RawEquals(want) { 510 t.Errorf("wrong result %#v; want %#v", got, want) 511 } 512 513 // Test changes with empty state 514 evaluator = evaluatorForModule(states.NewState().SyncWrapper(), changesSync) 515 data = &evaluationStateData{ 516 Evaluator: evaluator, 517 } 518 scope = evaluator.Scope(data, nil) 519 want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)}) 520 got, diags = scope.Data.GetModule(addrs.ModuleCall{ 521 Name: "mod", 522 }, tfdiags.SourceRange{}) 523 524 if len(diags) != 0 { 525 t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) 526 } 527 if !got.RawEquals(want) { 528 t.Errorf("wrong result %#v; want %#v", got, want) 529 } 530 } 531 532 func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync) *Evaluator { 533 return &Evaluator{ 534 Meta: &ContextMeta{ 535 Env: "foo", 536 }, 537 Config: &configs.Config{ 538 Module: &configs.Module{ 539 ModuleCalls: map[string]*configs.ModuleCall{ 540 "mod": { 541 Name: "mod", 542 }, 543 }, 544 }, 545 Children: map[string]*configs.Config{ 546 "mod": { 547 Path: addrs.Module{"module.mod"}, 548 Module: &configs.Module{ 549 Outputs: map[string]*configs.Output{ 550 "out": { 551 Name: "out", 552 Sensitive: true, 553 }, 554 }, 555 }, 556 }, 557 }, 558 }, 559 State: stateSync, 560 Changes: changesSync, 561 } 562 }