github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_provider_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/terraform/internal/addrs" 10 "github.com/hashicorp/terraform/internal/configs" 11 "github.com/hashicorp/terraform/internal/configs/configschema" 12 "github.com/hashicorp/terraform/internal/lang/marks" 13 "github.com/hashicorp/terraform/internal/providers" 14 "github.com/hashicorp/terraform/internal/tfdiags" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 func TestNodeApplyableProviderExecute(t *testing.T) { 19 config := &configs.Provider{ 20 Name: "foo", 21 Config: configs.SynthBody("", map[string]cty.Value{ 22 "user": cty.StringVal("hello"), 23 }), 24 } 25 26 schema := &configschema.Block{ 27 Attributes: map[string]*configschema.Attribute{ 28 "user": { 29 Type: cty.String, 30 Required: true, 31 }, 32 "pw": { 33 Type: cty.String, 34 Required: true, 35 }, 36 }, 37 } 38 provider := mockProviderWithConfigSchema(schema) 39 providerAddr := addrs.AbsProviderConfig{ 40 Module: addrs.RootModule, 41 Provider: addrs.NewDefaultProvider("foo"), 42 } 43 44 n := &NodeApplyableProvider{&NodeAbstractProvider{ 45 Addr: providerAddr, 46 Config: config, 47 }} 48 49 ctx := &MockEvalContext{ProviderProvider: provider} 50 ctx.installSimpleEval() 51 ctx.ProviderInputValues = map[string]cty.Value{ 52 "pw": cty.StringVal("so secret"), 53 } 54 55 if diags := n.Execute(ctx, walkApply); diags.HasErrors() { 56 t.Fatalf("err: %s", diags.Err()) 57 } 58 59 if !ctx.ConfigureProviderCalled { 60 t.Fatal("should be called") 61 } 62 63 gotObj := ctx.ConfigureProviderConfig 64 if !gotObj.Type().HasAttribute("user") { 65 t.Fatal("configuration object does not have \"user\" attribute") 66 } 67 if got, want := gotObj.GetAttr("user"), cty.StringVal("hello"); !got.RawEquals(want) { 68 t.Errorf("wrong configuration value\ngot: %#v\nwant: %#v", got, want) 69 } 70 71 if !gotObj.Type().HasAttribute("pw") { 72 t.Fatal("configuration object does not have \"pw\" attribute") 73 } 74 if got, want := gotObj.GetAttr("pw"), cty.StringVal("so secret"); !got.RawEquals(want) { 75 t.Errorf("wrong configuration value\ngot: %#v\nwant: %#v", got, want) 76 } 77 } 78 79 func TestNodeApplyableProviderExecute_unknownImport(t *testing.T) { 80 config := &configs.Provider{ 81 Name: "foo", 82 Config: configs.SynthBody("", map[string]cty.Value{ 83 "test_string": cty.UnknownVal(cty.String), 84 }), 85 } 86 provider := mockProviderWithConfigSchema(simpleTestSchema()) 87 providerAddr := addrs.AbsProviderConfig{ 88 Module: addrs.RootModule, 89 Provider: addrs.NewDefaultProvider("foo"), 90 } 91 n := &NodeApplyableProvider{&NodeAbstractProvider{ 92 Addr: providerAddr, 93 Config: config, 94 }} 95 96 ctx := &MockEvalContext{ProviderProvider: provider} 97 ctx.installSimpleEval() 98 99 diags := n.Execute(ctx, walkImport) 100 if !diags.HasErrors() { 101 t.Fatal("expected error, got success") 102 } 103 104 detail := `Invalid provider configuration: The configuration for provider["registry.terraform.io/hashicorp/foo"] depends on values that cannot be determined until apply.` 105 if got, want := diags.Err().Error(), detail; got != want { 106 t.Errorf("wrong diagnostic detail\n got: %q\nwant: %q", got, want) 107 } 108 109 if ctx.ConfigureProviderCalled { 110 t.Fatal("should not be called") 111 } 112 } 113 114 func TestNodeApplyableProviderExecute_unknownApply(t *testing.T) { 115 config := &configs.Provider{ 116 Name: "foo", 117 Config: configs.SynthBody("", map[string]cty.Value{ 118 "test_string": cty.UnknownVal(cty.String), 119 }), 120 } 121 provider := mockProviderWithConfigSchema(simpleTestSchema()) 122 providerAddr := addrs.AbsProviderConfig{ 123 Module: addrs.RootModule, 124 Provider: addrs.NewDefaultProvider("foo"), 125 } 126 n := &NodeApplyableProvider{&NodeAbstractProvider{ 127 Addr: providerAddr, 128 Config: config, 129 }} 130 ctx := &MockEvalContext{ProviderProvider: provider} 131 ctx.installSimpleEval() 132 133 if err := n.Execute(ctx, walkApply); err != nil { 134 t.Fatalf("err: %s", err) 135 } 136 137 if !ctx.ConfigureProviderCalled { 138 t.Fatal("should be called") 139 } 140 141 gotObj := ctx.ConfigureProviderConfig 142 if !gotObj.Type().HasAttribute("test_string") { 143 t.Fatal("configuration object does not have \"test_string\" attribute") 144 } 145 if got, want := gotObj.GetAttr("test_string"), cty.UnknownVal(cty.String); !got.RawEquals(want) { 146 t.Errorf("wrong configuration value\ngot: %#v\nwant: %#v", got, want) 147 } 148 } 149 150 func TestNodeApplyableProviderExecute_sensitive(t *testing.T) { 151 config := &configs.Provider{ 152 Name: "foo", 153 Config: configs.SynthBody("", map[string]cty.Value{ 154 "test_string": cty.StringVal("hello").Mark(marks.Sensitive), 155 }), 156 } 157 provider := mockProviderWithConfigSchema(simpleTestSchema()) 158 providerAddr := addrs.AbsProviderConfig{ 159 Module: addrs.RootModule, 160 Provider: addrs.NewDefaultProvider("foo"), 161 } 162 163 n := &NodeApplyableProvider{&NodeAbstractProvider{ 164 Addr: providerAddr, 165 Config: config, 166 }} 167 168 ctx := &MockEvalContext{ProviderProvider: provider} 169 ctx.installSimpleEval() 170 if err := n.Execute(ctx, walkApply); err != nil { 171 t.Fatalf("err: %s", err) 172 } 173 174 if !ctx.ConfigureProviderCalled { 175 t.Fatal("should be called") 176 } 177 178 gotObj := ctx.ConfigureProviderConfig 179 if !gotObj.Type().HasAttribute("test_string") { 180 t.Fatal("configuration object does not have \"test_string\" attribute") 181 } 182 if got, want := gotObj.GetAttr("test_string"), cty.StringVal("hello"); !got.RawEquals(want) { 183 t.Errorf("wrong configuration value\ngot: %#v\nwant: %#v", got, want) 184 } 185 } 186 187 func TestNodeApplyableProviderExecute_sensitiveValidate(t *testing.T) { 188 config := &configs.Provider{ 189 Name: "foo", 190 Config: configs.SynthBody("", map[string]cty.Value{ 191 "test_string": cty.StringVal("hello").Mark(marks.Sensitive), 192 }), 193 } 194 provider := mockProviderWithConfigSchema(simpleTestSchema()) 195 providerAddr := addrs.AbsProviderConfig{ 196 Module: addrs.RootModule, 197 Provider: addrs.NewDefaultProvider("foo"), 198 } 199 200 n := &NodeApplyableProvider{&NodeAbstractProvider{ 201 Addr: providerAddr, 202 Config: config, 203 }} 204 205 ctx := &MockEvalContext{ProviderProvider: provider} 206 ctx.installSimpleEval() 207 if err := n.Execute(ctx, walkValidate); err != nil { 208 t.Fatalf("err: %s", err) 209 } 210 211 if !provider.ValidateProviderConfigCalled { 212 t.Fatal("should be called") 213 } 214 215 gotObj := provider.ValidateProviderConfigRequest.Config 216 if !gotObj.Type().HasAttribute("test_string") { 217 t.Fatal("configuration object does not have \"test_string\" attribute") 218 } 219 if got, want := gotObj.GetAttr("test_string"), cty.StringVal("hello"); !got.RawEquals(want) { 220 t.Errorf("wrong configuration value\ngot: %#v\nwant: %#v", got, want) 221 } 222 } 223 224 func TestNodeApplyableProviderExecute_emptyValidate(t *testing.T) { 225 config := &configs.Provider{ 226 Name: "foo", 227 Config: configs.SynthBody("", map[string]cty.Value{}), 228 } 229 provider := mockProviderWithConfigSchema(&configschema.Block{ 230 Attributes: map[string]*configschema.Attribute{ 231 "test_string": { 232 Type: cty.String, 233 Required: true, 234 }, 235 }, 236 }) 237 providerAddr := addrs.AbsProviderConfig{ 238 Module: addrs.RootModule, 239 Provider: addrs.NewDefaultProvider("foo"), 240 } 241 242 n := &NodeApplyableProvider{&NodeAbstractProvider{ 243 Addr: providerAddr, 244 Config: config, 245 }} 246 247 ctx := &MockEvalContext{ProviderProvider: provider} 248 ctx.installSimpleEval() 249 if err := n.Execute(ctx, walkValidate); err != nil { 250 t.Fatalf("err: %s", err) 251 } 252 253 if ctx.ConfigureProviderCalled { 254 t.Fatal("should not be called") 255 } 256 } 257 258 func TestNodeApplyableProvider_Validate(t *testing.T) { 259 provider := mockProviderWithConfigSchema(&configschema.Block{ 260 Attributes: map[string]*configschema.Attribute{ 261 "region": { 262 Type: cty.String, 263 Required: true, 264 }, 265 }, 266 }) 267 ctx := &MockEvalContext{ProviderProvider: provider} 268 ctx.installSimpleEval() 269 270 t.Run("valid", func(t *testing.T) { 271 config := &configs.Provider{ 272 Name: "test", 273 Config: configs.SynthBody("", map[string]cty.Value{ 274 "region": cty.StringVal("mars"), 275 }), 276 } 277 278 node := NodeApplyableProvider{ 279 NodeAbstractProvider: &NodeAbstractProvider{ 280 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 281 Config: config, 282 }, 283 } 284 285 diags := node.ValidateProvider(ctx, provider) 286 if diags.HasErrors() { 287 t.Errorf("unexpected error with valid config: %s", diags.Err()) 288 } 289 }) 290 291 t.Run("invalid", func(t *testing.T) { 292 config := &configs.Provider{ 293 Name: "test", 294 Config: configs.SynthBody("", map[string]cty.Value{ 295 "region": cty.MapValEmpty(cty.String), 296 }), 297 } 298 299 node := NodeApplyableProvider{ 300 NodeAbstractProvider: &NodeAbstractProvider{ 301 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 302 Config: config, 303 }, 304 } 305 306 diags := node.ValidateProvider(ctx, provider) 307 if !diags.HasErrors() { 308 t.Error("missing expected error with invalid config") 309 } 310 }) 311 312 t.Run("empty config", func(t *testing.T) { 313 node := NodeApplyableProvider{ 314 NodeAbstractProvider: &NodeAbstractProvider{ 315 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 316 }, 317 } 318 319 diags := node.ValidateProvider(ctx, provider) 320 if diags.HasErrors() { 321 t.Errorf("unexpected error with empty config: %s", diags.Err()) 322 } 323 }) 324 } 325 326 // This test specifically tests responses from the 327 // providers.ValidateProviderConfigFn. See 328 // TestNodeApplyableProvider_ConfigProvider_config_fn_err for 329 // providers.ConfigureProviderRequest responses. 330 func TestNodeApplyableProvider_ConfigProvider(t *testing.T) { 331 provider := mockProviderWithConfigSchema(&configschema.Block{ 332 Attributes: map[string]*configschema.Attribute{ 333 "region": { 334 Type: cty.String, 335 Optional: true, 336 }, 337 }, 338 }) 339 // For this test, we're returning an error for an optional argument. This 340 // can happen for example if an argument is only conditionally required. 341 provider.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { 342 region := req.Config.GetAttr("region") 343 if region.IsNull() { 344 resp.Diagnostics = resp.Diagnostics.Append( 345 tfdiags.WholeContainingBody(tfdiags.Error, "value is not found", "you did not supply a required value")) 346 } 347 return 348 } 349 ctx := &MockEvalContext{ProviderProvider: provider} 350 ctx.installSimpleEval() 351 352 t.Run("valid", func(t *testing.T) { 353 config := &configs.Provider{ 354 Name: "test", 355 Config: configs.SynthBody("", map[string]cty.Value{ 356 "region": cty.StringVal("mars"), 357 }), 358 } 359 360 node := NodeApplyableProvider{ 361 NodeAbstractProvider: &NodeAbstractProvider{ 362 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 363 Config: config, 364 }, 365 } 366 367 diags := node.ConfigureProvider(ctx, provider, false) 368 if diags.HasErrors() { 369 t.Errorf("unexpected error with valid config: %s", diags.Err()) 370 } 371 }) 372 373 t.Run("missing required config (no config at all)", func(t *testing.T) { 374 node := NodeApplyableProvider{ 375 NodeAbstractProvider: &NodeAbstractProvider{ 376 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 377 }, 378 } 379 380 diags := node.ConfigureProvider(ctx, provider, false) 381 if !diags.HasErrors() { 382 t.Fatal("missing expected error with nil config") 383 } 384 if !strings.Contains(diags.Err().Error(), "requires explicit configuration") { 385 t.Errorf("diagnostic is missing \"requires explicit configuration\" message: %s", diags.Err()) 386 } 387 }) 388 389 t.Run("missing required config", func(t *testing.T) { 390 config := &configs.Provider{ 391 Name: "test", 392 Config: hcl.EmptyBody(), 393 } 394 node := NodeApplyableProvider{ 395 NodeAbstractProvider: &NodeAbstractProvider{ 396 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 397 Config: config, 398 }, 399 } 400 401 diags := node.ConfigureProvider(ctx, provider, false) 402 if !diags.HasErrors() { 403 t.Fatal("missing expected error with invalid config") 404 } 405 if !strings.Contains(diags.Err().Error(), "value is not found") { 406 t.Errorf("wrong diagnostic: %s", diags.Err()) 407 } 408 }) 409 410 } 411 412 // This test is similar to TestNodeApplyableProvider_ConfigProvider, but tests responses from the providers.ConfigureProviderRequest 413 func TestNodeApplyableProvider_ConfigProvider_config_fn_err(t *testing.T) { 414 provider := mockProviderWithConfigSchema(&configschema.Block{ 415 Attributes: map[string]*configschema.Attribute{ 416 "region": { 417 Type: cty.String, 418 Optional: true, 419 }, 420 }, 421 }) 422 ctx := &MockEvalContext{ProviderProvider: provider} 423 ctx.installSimpleEval() 424 // For this test, provider.PrepareConfigFn will succeed every time but the 425 // ctx.ConfigureProviderFn will return an error if a value is not found. 426 // 427 // This is an unlikely but real situation that occurs: 428 // https://github.com/hashicorp/terraform/issues/23087 429 ctx.ConfigureProviderFn = func(addr addrs.AbsProviderConfig, cfg cty.Value) (diags tfdiags.Diagnostics) { 430 if cfg.IsNull() { 431 diags = diags.Append(fmt.Errorf("no config provided")) 432 } else { 433 region := cfg.GetAttr("region") 434 if region.IsNull() { 435 diags = diags.Append(fmt.Errorf("value is not found")) 436 } 437 } 438 return 439 } 440 441 t.Run("valid", func(t *testing.T) { 442 config := &configs.Provider{ 443 Name: "test", 444 Config: configs.SynthBody("", map[string]cty.Value{ 445 "region": cty.StringVal("mars"), 446 }), 447 } 448 449 node := NodeApplyableProvider{ 450 NodeAbstractProvider: &NodeAbstractProvider{ 451 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 452 Config: config, 453 }, 454 } 455 456 diags := node.ConfigureProvider(ctx, provider, false) 457 if diags.HasErrors() { 458 t.Errorf("unexpected error with valid config: %s", diags.Err()) 459 } 460 }) 461 462 t.Run("missing required config (no config at all)", func(t *testing.T) { 463 node := NodeApplyableProvider{ 464 NodeAbstractProvider: &NodeAbstractProvider{ 465 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 466 }, 467 } 468 469 diags := node.ConfigureProvider(ctx, provider, false) 470 if !diags.HasErrors() { 471 t.Fatal("missing expected error with nil config") 472 } 473 if !strings.Contains(diags.Err().Error(), "requires explicit configuration") { 474 t.Errorf("diagnostic is missing \"requires explicit configuration\" message: %s", diags.Err()) 475 } 476 }) 477 478 t.Run("missing required config", func(t *testing.T) { 479 config := &configs.Provider{ 480 Name: "test", 481 Config: hcl.EmptyBody(), 482 } 483 node := NodeApplyableProvider{ 484 NodeAbstractProvider: &NodeAbstractProvider{ 485 Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), 486 Config: config, 487 }, 488 } 489 490 diags := node.ConfigureProvider(ctx, provider, false) 491 if !diags.HasErrors() { 492 t.Fatal("missing expected error with invalid config") 493 } 494 if diags.Err().Error() != "value is not found" { 495 t.Errorf("wrong diagnostic: %s", diags.Err()) 496 } 497 }) 498 } 499 500 func TestGetSchemaError(t *testing.T) { 501 provider := &MockProvider{ 502 GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ 503 Diagnostics: tfdiags.Diagnostics.Append(nil, tfdiags.WholeContainingBody(tfdiags.Error, "oops", "error")), 504 }, 505 } 506 507 providerAddr := mustProviderConfig(`provider["terraform.io/some/provider"]`) 508 ctx := &MockEvalContext{ProviderProvider: provider} 509 ctx.installSimpleEval() 510 node := NodeApplyableProvider{ 511 NodeAbstractProvider: &NodeAbstractProvider{ 512 Addr: providerAddr, 513 }, 514 } 515 516 diags := node.ConfigureProvider(ctx, provider, false) 517 for _, d := range diags { 518 desc := d.Description() 519 if desc.Address != providerAddr.String() { 520 t.Fatalf("missing provider address from diagnostics: %#v", desc) 521 } 522 } 523 524 }