github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_validate_test.go (about) 1 package terraform 2 3 import ( 4 "errors" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hcltest" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 16 "github.com/hashicorp/terraform-plugin-sdk/internal/provisioners" 17 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 18 ) 19 20 func TestEvalValidateResource_managedResource(t *testing.T) { 21 mp := simpleMockProvider() 22 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 23 if got, want := req.TypeName, "test_object"; got != want { 24 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 25 } 26 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 27 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 28 } 29 return providers.ValidateResourceTypeConfigResponse{} 30 } 31 32 p := providers.Interface(mp) 33 rc := &configs.Resource{ 34 Mode: addrs.ManagedResourceMode, 35 Type: "test_object", 36 Name: "foo", 37 Config: configs.SynthBody("", map[string]cty.Value{ 38 "test_string": cty.StringVal("bar"), 39 }), 40 } 41 node := &EvalValidateResource{ 42 Addr: addrs.Resource{ 43 Mode: addrs.ManagedResourceMode, 44 Type: "aws_instance", 45 Name: "foo", 46 }, 47 Provider: &p, 48 Config: rc, 49 ProviderSchema: &mp.GetSchemaReturn, 50 } 51 52 ctx := &MockEvalContext{} 53 ctx.installSimpleEval() 54 55 _, err := node.Eval(ctx) 56 if err != nil { 57 t.Fatalf("err: %s", err) 58 } 59 60 if !mp.ValidateResourceTypeConfigCalled { 61 t.Fatal("Expected ValidateResourceTypeConfig to be called, but it was not!") 62 } 63 } 64 65 func TestEvalValidateResource_managedResourceCount(t *testing.T) { 66 mp := simpleMockProvider() 67 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 68 if got, want := req.TypeName, "test_object"; got != want { 69 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 70 } 71 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 72 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 73 } 74 return providers.ValidateResourceTypeConfigResponse{} 75 } 76 77 p := providers.Interface(mp) 78 rc := &configs.Resource{ 79 Mode: addrs.ManagedResourceMode, 80 Type: "test_object", 81 Name: "foo", 82 Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)), 83 Config: configs.SynthBody("", map[string]cty.Value{ 84 "test_string": cty.StringVal("bar"), 85 }), 86 } 87 node := &EvalValidateResource{ 88 Addr: addrs.Resource{ 89 Mode: addrs.ManagedResourceMode, 90 Type: "aws_instance", 91 Name: "foo", 92 }, 93 Provider: &p, 94 Config: rc, 95 ProviderSchema: &mp.GetSchemaReturn, 96 } 97 98 ctx := &MockEvalContext{} 99 ctx.installSimpleEval() 100 101 _, err := node.Eval(ctx) 102 if err != nil { 103 t.Fatalf("err: %s", err) 104 } 105 106 if !mp.ValidateResourceTypeConfigCalled { 107 t.Fatal("Expected ValidateResourceTypeConfig to be called, but it was not!") 108 } 109 } 110 111 func TestEvalValidateResource_dataSource(t *testing.T) { 112 mp := simpleMockProvider() 113 mp.ValidateDataSourceConfigFn = func(req providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { 114 if got, want := req.TypeName, "test_object"; got != want { 115 t.Fatalf("wrong resource type\ngot: %#v\nwant: %#v", got, want) 116 } 117 if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { 118 t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) 119 } 120 return providers.ValidateDataSourceConfigResponse{} 121 } 122 123 p := providers.Interface(mp) 124 rc := &configs.Resource{ 125 Mode: addrs.DataResourceMode, 126 Type: "test_object", 127 Name: "foo", 128 Config: configs.SynthBody("", map[string]cty.Value{ 129 "test_string": cty.StringVal("bar"), 130 }), 131 } 132 133 node := &EvalValidateResource{ 134 Addr: addrs.Resource{ 135 Mode: addrs.DataResourceMode, 136 Type: "aws_ami", 137 Name: "foo", 138 }, 139 Provider: &p, 140 Config: rc, 141 ProviderSchema: &mp.GetSchemaReturn, 142 } 143 144 ctx := &MockEvalContext{} 145 ctx.installSimpleEval() 146 147 _, err := node.Eval(ctx) 148 if err != nil { 149 t.Fatalf("err: %s", err) 150 } 151 152 if !mp.ValidateDataSourceConfigCalled { 153 t.Fatal("Expected ValidateDataSourceConfig to be called, but it was not!") 154 } 155 } 156 157 func TestEvalValidateResource_validReturnsNilError(t *testing.T) { 158 mp := simpleMockProvider() 159 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 160 return providers.ValidateResourceTypeConfigResponse{} 161 } 162 163 p := providers.Interface(mp) 164 rc := &configs.Resource{ 165 Mode: addrs.ManagedResourceMode, 166 Type: "test_object", 167 Name: "foo", 168 Config: configs.SynthBody("", map[string]cty.Value{}), 169 } 170 node := &EvalValidateResource{ 171 Addr: addrs.Resource{ 172 Mode: addrs.ManagedResourceMode, 173 Type: "test_object", 174 Name: "foo", 175 }, 176 Provider: &p, 177 Config: rc, 178 ProviderSchema: &mp.GetSchemaReturn, 179 } 180 181 ctx := &MockEvalContext{} 182 ctx.installSimpleEval() 183 184 _, err := node.Eval(ctx) 185 if err != nil { 186 t.Fatalf("Expected nil error, got: %s", err) 187 } 188 } 189 190 func TestEvalValidateResource_warningsAndErrorsPassedThrough(t *testing.T) { 191 mp := simpleMockProvider() 192 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 193 var diags tfdiags.Diagnostics 194 diags = diags.Append(tfdiags.SimpleWarning("warn")) 195 diags = diags.Append(errors.New("err")) 196 return providers.ValidateResourceTypeConfigResponse{ 197 Diagnostics: diags, 198 } 199 } 200 201 p := providers.Interface(mp) 202 rc := &configs.Resource{ 203 Mode: addrs.ManagedResourceMode, 204 Type: "test_object", 205 Name: "foo", 206 Config: configs.SynthBody("", map[string]cty.Value{}), 207 } 208 node := &EvalValidateResource{ 209 Addr: addrs.Resource{ 210 Mode: addrs.ManagedResourceMode, 211 Type: "test_object", 212 Name: "foo", 213 }, 214 Provider: &p, 215 Config: rc, 216 ProviderSchema: &mp.GetSchemaReturn, 217 } 218 219 ctx := &MockEvalContext{} 220 ctx.installSimpleEval() 221 222 _, err := node.Eval(ctx) 223 if err == nil { 224 t.Fatal("unexpected success; want error") 225 } 226 227 var diags tfdiags.Diagnostics 228 diags = diags.Append(err) 229 bySeverity := map[tfdiags.Severity]tfdiags.Diagnostics{} 230 for _, diag := range diags { 231 bySeverity[diag.Severity()] = append(bySeverity[diag.Severity()], diag) 232 } 233 if len(bySeverity[tfdiags.Warning]) != 1 || bySeverity[tfdiags.Warning][0].Description().Summary != "warn" { 234 t.Errorf("Expected 1 warning 'warn', got: %s", bySeverity[tfdiags.Warning].ErrWithWarnings()) 235 } 236 if len(bySeverity[tfdiags.Error]) != 1 || bySeverity[tfdiags.Error][0].Description().Summary != "err" { 237 t.Errorf("Expected 1 error 'err', got: %s", bySeverity[tfdiags.Error].Err()) 238 } 239 } 240 241 func TestEvalValidateResource_ignoreWarnings(t *testing.T) { 242 mp := simpleMockProvider() 243 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 244 var diags tfdiags.Diagnostics 245 diags = diags.Append(tfdiags.SimpleWarning("warn")) 246 return providers.ValidateResourceTypeConfigResponse{ 247 Diagnostics: diags, 248 } 249 } 250 251 p := providers.Interface(mp) 252 rc := &configs.Resource{ 253 Mode: addrs.ManagedResourceMode, 254 Type: "test_object", 255 Name: "foo", 256 Config: configs.SynthBody("", map[string]cty.Value{}), 257 } 258 node := &EvalValidateResource{ 259 Addr: addrs.Resource{ 260 Mode: addrs.ManagedResourceMode, 261 Type: "test-object", 262 Name: "foo", 263 }, 264 Provider: &p, 265 Config: rc, 266 ProviderSchema: &mp.GetSchemaReturn, 267 268 IgnoreWarnings: true, 269 } 270 271 ctx := &MockEvalContext{} 272 ctx.installSimpleEval() 273 274 _, err := node.Eval(ctx) 275 if err != nil { 276 t.Fatalf("Expected no error, got: %s", err) 277 } 278 } 279 280 func TestEvalValidateResource_invalidDependsOn(t *testing.T) { 281 mp := simpleMockProvider() 282 mp.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { 283 return providers.ValidateResourceTypeConfigResponse{} 284 } 285 286 // We'll check a _valid_ config first, to make sure we're not failing 287 // for some other reason, and then make it invalid. 288 p := providers.Interface(mp) 289 rc := &configs.Resource{ 290 Mode: addrs.ManagedResourceMode, 291 Type: "test_object", 292 Name: "foo", 293 Config: configs.SynthBody("", map[string]cty.Value{}), 294 DependsOn: []hcl.Traversal{ 295 // Depending on path.module is pointless, since it is immediately 296 // available, but we allow all of the referencable addrs here 297 // for consistency: referencing them is harmless, and avoids the 298 // need for us to document a different subset of addresses that 299 // are valid in depends_on. 300 // For the sake of this test, it's a valid address we can use that 301 // doesn't require something else to exist in the configuration. 302 { 303 hcl.TraverseRoot{ 304 Name: "path", 305 }, 306 hcl.TraverseAttr{ 307 Name: "module", 308 }, 309 }, 310 }, 311 } 312 node := &EvalValidateResource{ 313 Addr: addrs.Resource{ 314 Mode: addrs.ManagedResourceMode, 315 Type: "aws_instance", 316 Name: "foo", 317 }, 318 Provider: &p, 319 Config: rc, 320 ProviderSchema: &mp.GetSchemaReturn, 321 } 322 323 ctx := &MockEvalContext{} 324 ctx.installSimpleEval() 325 326 _, err := node.Eval(ctx) 327 if err != nil { 328 t.Fatalf("error for supposedly-valid config: %s", err) 329 } 330 331 // Now we'll make it invalid by adding additional traversal steps at 332 // the end of what we're referencing. This is intended to catch the 333 // situation where the user tries to depend on e.g. a specific resource 334 // attribute, rather than the whole resource, like aws_instance.foo.id. 335 rc.DependsOn = append(rc.DependsOn, hcl.Traversal{ 336 hcl.TraverseRoot{ 337 Name: "path", 338 }, 339 hcl.TraverseAttr{ 340 Name: "module", 341 }, 342 hcl.TraverseAttr{ 343 Name: "extra", 344 }, 345 }) 346 347 _, err = node.Eval(ctx) 348 if err == nil { 349 t.Fatal("no error for invalid depends_on") 350 } 351 if got, want := err.Error(), "Invalid depends_on reference"; !strings.Contains(got, want) { 352 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 353 } 354 355 // Test for handling an unknown root without attribute, like a 356 // typo that omits the dot inbetween "path.module". 357 rc.DependsOn = append(rc.DependsOn, hcl.Traversal{ 358 hcl.TraverseRoot{ 359 Name: "pathmodule", 360 }, 361 }) 362 363 _, err = node.Eval(ctx) 364 if err == nil { 365 t.Fatal("no error for invalid depends_on") 366 } 367 if got, want := err.Error(), "Invalid depends_on reference"; !strings.Contains(got, want) { 368 t.Fatalf("wrong error\ngot: %s\nwant: Message containing %q", got, want) 369 } 370 } 371 372 func TestEvalValidateProvisioner_valid(t *testing.T) { 373 mp := &MockProvisioner{} 374 var p provisioners.Interface = mp 375 ctx := &MockEvalContext{} 376 ctx.installSimpleEval() 377 378 schema := &configschema.Block{} 379 380 node := &EvalValidateProvisioner{ 381 ResourceAddr: addrs.Resource{ 382 Mode: addrs.ManagedResourceMode, 383 Type: "foo", 384 Name: "bar", 385 }, 386 Provisioner: &p, 387 Schema: &schema, 388 Config: &configs.Provisioner{ 389 Type: "baz", 390 Config: hcl.EmptyBody(), 391 Connection: &configs.Connection{ 392 Config: configs.SynthBody("", map[string]cty.Value{ 393 "host": cty.StringVal("localhost"), 394 "type": cty.StringVal("ssh"), 395 }), 396 }, 397 }, 398 } 399 400 result, err := node.Eval(ctx) 401 if err != nil { 402 t.Fatalf("node.Eval failed: %s", err) 403 } 404 if result != nil { 405 t.Errorf("node.Eval returned non-nil result") 406 } 407 408 if !mp.ValidateProvisionerConfigCalled { 409 t.Fatalf("p.ValidateProvisionerConfig not called") 410 } 411 } 412 413 func TestEvalValidateProvisioner_warning(t *testing.T) { 414 mp := &MockProvisioner{} 415 var p provisioners.Interface = mp 416 ctx := &MockEvalContext{} 417 ctx.installSimpleEval() 418 419 schema := &configschema.Block{ 420 Attributes: map[string]*configschema.Attribute{ 421 "type": { 422 Type: cty.String, 423 Optional: true, 424 }, 425 }, 426 } 427 428 node := &EvalValidateProvisioner{ 429 ResourceAddr: addrs.Resource{ 430 Mode: addrs.ManagedResourceMode, 431 Type: "foo", 432 Name: "bar", 433 }, 434 Provisioner: &p, 435 Schema: &schema, 436 Config: &configs.Provisioner{ 437 Type: "baz", 438 Config: hcl.EmptyBody(), 439 Connection: &configs.Connection{ 440 Config: configs.SynthBody("", map[string]cty.Value{ 441 "host": cty.StringVal("localhost"), 442 "type": cty.StringVal("ssh"), 443 }), 444 }, 445 }, 446 } 447 448 { 449 var diags tfdiags.Diagnostics 450 diags = diags.Append(tfdiags.SimpleWarning("foo is deprecated")) 451 mp.ValidateProvisionerConfigResponse = provisioners.ValidateProvisionerConfigResponse{ 452 Diagnostics: diags, 453 } 454 } 455 456 _, err := node.Eval(ctx) 457 if err == nil { 458 t.Fatalf("node.Eval succeeded; want error") 459 } 460 461 var diags tfdiags.Diagnostics 462 diags = diags.Append(err) 463 if len(diags) != 1 { 464 t.Fatalf("wrong number of diagnostics in %s; want one warning", diags.ErrWithWarnings()) 465 } 466 467 if got, want := diags[0].Description().Summary, mp.ValidateProvisionerConfigResponse.Diagnostics[0].Description().Summary; got != want { 468 t.Fatalf("wrong warning %q; want %q", got, want) 469 } 470 } 471 472 func TestEvalValidateProvisioner_connectionInvalid(t *testing.T) { 473 var p provisioners.Interface = &MockProvisioner{} 474 ctx := &MockEvalContext{} 475 ctx.installSimpleEval() 476 477 schema := &configschema.Block{ 478 Attributes: map[string]*configschema.Attribute{ 479 "type": { 480 Type: cty.String, 481 Optional: true, 482 }, 483 }, 484 } 485 486 node := &EvalValidateProvisioner{ 487 ResourceAddr: addrs.Resource{ 488 Mode: addrs.ManagedResourceMode, 489 Type: "foo", 490 Name: "bar", 491 }, 492 Provisioner: &p, 493 Schema: &schema, 494 Config: &configs.Provisioner{ 495 Type: "baz", 496 Config: hcl.EmptyBody(), 497 Connection: &configs.Connection{ 498 Config: configs.SynthBody("", map[string]cty.Value{ 499 "type": cty.StringVal("ssh"), 500 "bananananananana": cty.StringVal("foo"), 501 "bazaz": cty.StringVal("bar"), 502 }), 503 }, 504 }, 505 } 506 507 _, err := node.Eval(ctx) 508 if err == nil { 509 t.Fatalf("node.Eval succeeded; want error") 510 } 511 512 var diags tfdiags.Diagnostics 513 diags = diags.Append(err) 514 if len(diags) != 3 { 515 t.Fatalf("wrong number of diagnostics; want two errors\n\n%s", diags.Err()) 516 } 517 518 errStr := diags.Err().Error() 519 if !(strings.Contains(errStr, "bananananananana") && strings.Contains(errStr, "bazaz")) { 520 t.Fatalf("wrong errors %q; want something about each of our invalid connInfo keys", errStr) 521 } 522 }