2020-09-16 11:17:17 -05:00
package terraform
import (
2020-12-11 12:18:49 -06:00
"fmt"
"strings"
2020-09-16 11:17:17 -05:00
"testing"
2020-12-11 12:18:49 -06:00
"github.com/hashicorp/hcl/v2"
2021-05-17 14:00:50 -05:00
"github.com/hashicorp/terraform/internal/addrs"
2021-05-17 14:17:09 -05:00
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
2021-06-24 16:53:43 -05:00
"github.com/hashicorp/terraform/internal/lang/marks"
2021-05-17 12:40:40 -05:00
"github.com/hashicorp/terraform/internal/providers"
2021-05-17 12:11:06 -05:00
"github.com/hashicorp/terraform/internal/tfdiags"
2020-09-16 11:17:17 -05:00
"github.com/zclconf/go-cty/cty"
)
func TestNodeApplyableProviderExecute ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
2021-06-25 07:48:47 -05:00
"user" : cty . StringVal ( "hello" ) ,
2020-09-16 11:17:17 -05:00
} ) ,
}
2021-06-25 07:48:47 -05:00
schema := & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"user" : {
Type : cty . String ,
Required : true ,
} ,
"pw" : {
Type : cty . String ,
Required : true ,
} ,
} ,
}
provider := mockProviderWithConfigSchema ( schema )
2020-09-16 11:17:17 -05:00
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
2021-06-25 07:48:47 -05:00
ctx . ProviderInputValues = map [ string ] cty . Value {
"pw" : cty . StringVal ( "so secret" ) ,
}
if diags := n . Execute ( ctx , walkApply ) ; diags . HasErrors ( ) {
t . Fatalf ( "err: %s" , diags . Err ( ) )
2020-09-16 11:17:17 -05:00
}
if ! ctx . ConfigureProviderCalled {
t . Fatal ( "should be called" )
}
gotObj := ctx . ConfigureProviderConfig
2021-06-25 07:48:47 -05:00
if ! gotObj . Type ( ) . HasAttribute ( "user" ) {
t . Fatal ( "configuration object does not have \"user\" attribute" )
}
if got , want := gotObj . GetAttr ( "user" ) , cty . StringVal ( "hello" ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong configuration value\ngot: %#v\nwant: %#v" , got , want )
}
if ! gotObj . Type ( ) . HasAttribute ( "pw" ) {
t . Fatal ( "configuration object does not have \"pw\" attribute" )
2020-09-16 11:17:17 -05:00
}
2021-06-25 07:48:47 -05:00
if got , want := gotObj . GetAttr ( "pw" ) , cty . StringVal ( "so secret" ) ; ! got . RawEquals ( want ) {
2020-09-16 11:17:17 -05:00
t . Errorf ( "wrong configuration value\ngot: %#v\nwant: %#v" , got , want )
}
}
func TestNodeApplyableProviderExecute_unknownImport ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"test_string" : cty . UnknownVal ( cty . String ) ,
} ) ,
}
provider := mockProviderWithConfigSchema ( simpleTestSchema ( ) )
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
2020-10-28 12:47:04 -05:00
diags := n . Execute ( ctx , walkImport )
if ! diags . HasErrors ( ) {
2020-09-16 11:17:17 -05:00
t . Fatal ( "expected error, got success" )
}
detail := ` Invalid provider configuration: The configuration for provider["registry.terraform.io/hashicorp/foo"] depends on values that cannot be determined until apply. `
2020-10-28 12:47:04 -05:00
if got , want := diags . Err ( ) . Error ( ) , detail ; got != want {
2020-09-16 11:17:17 -05:00
t . Errorf ( "wrong diagnostic detail\n got: %q\nwant: %q" , got , want )
}
if ctx . ConfigureProviderCalled {
t . Fatal ( "should not be called" )
}
}
func TestNodeApplyableProviderExecute_unknownApply ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"test_string" : cty . UnknownVal ( cty . String ) ,
} ) ,
}
provider := mockProviderWithConfigSchema ( simpleTestSchema ( ) )
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
if err := n . Execute ( ctx , walkApply ) ; err != nil {
t . Fatalf ( "err: %s" , err )
}
if ! ctx . ConfigureProviderCalled {
t . Fatal ( "should be called" )
}
gotObj := ctx . ConfigureProviderConfig
if ! gotObj . Type ( ) . HasAttribute ( "test_string" ) {
t . Fatal ( "configuration object does not have \"test_string\" attribute" )
}
if got , want := gotObj . GetAttr ( "test_string" ) , cty . UnknownVal ( cty . String ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong configuration value\ngot: %#v\nwant: %#v" , got , want )
}
}
2020-11-09 18:27:17 -06:00
2020-11-16 12:11:30 -06:00
func TestNodeApplyableProviderExecute_sensitive ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
2021-06-24 16:53:43 -05:00
"test_string" : cty . StringVal ( "hello" ) . Mark ( marks . Sensitive ) ,
2020-11-16 12:11:30 -06:00
} ) ,
}
provider := mockProviderWithConfigSchema ( simpleTestSchema ( ) )
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
if err := n . Execute ( ctx , walkApply ) ; err != nil {
t . Fatalf ( "err: %s" , err )
}
if ! ctx . ConfigureProviderCalled {
t . Fatal ( "should be called" )
}
gotObj := ctx . ConfigureProviderConfig
if ! gotObj . Type ( ) . HasAttribute ( "test_string" ) {
t . Fatal ( "configuration object does not have \"test_string\" attribute" )
}
if got , want := gotObj . GetAttr ( "test_string" ) , cty . StringVal ( "hello" ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong configuration value\ngot: %#v\nwant: %#v" , got , want )
}
}
func TestNodeApplyableProviderExecute_sensitiveValidate ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
2021-06-24 16:53:43 -05:00
"test_string" : cty . StringVal ( "hello" ) . Mark ( marks . Sensitive ) ,
2020-11-16 12:11:30 -06:00
} ) ,
}
provider := mockProviderWithConfigSchema ( simpleTestSchema ( ) )
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
if err := n . Execute ( ctx , walkValidate ) ; err != nil {
t . Fatalf ( "err: %s" , err )
}
2021-02-18 09:13:43 -06:00
if ! provider . ValidateProviderConfigCalled {
2020-11-16 12:11:30 -06:00
t . Fatal ( "should be called" )
}
2021-02-18 09:13:43 -06:00
gotObj := provider . ValidateProviderConfigRequest . Config
2020-11-16 12:11:30 -06:00
if ! gotObj . Type ( ) . HasAttribute ( "test_string" ) {
t . Fatal ( "configuration object does not have \"test_string\" attribute" )
}
if got , want := gotObj . GetAttr ( "test_string" ) , cty . StringVal ( "hello" ) ; ! got . RawEquals ( want ) {
t . Errorf ( "wrong configuration value\ngot: %#v\nwant: %#v" , got , want )
}
}
2020-12-06 11:47:24 -06:00
2020-11-09 18:27:17 -06:00
func TestNodeApplyableProviderExecute_emptyValidate ( t * testing . T ) {
config := & configs . Provider {
Name : "foo" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value { } ) ,
}
provider := mockProviderWithConfigSchema ( & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"test_string" : {
Type : cty . String ,
Required : true ,
} ,
} ,
} )
providerAddr := addrs . AbsProviderConfig {
Module : addrs . RootModule ,
Provider : addrs . NewDefaultProvider ( "foo" ) ,
}
n := & NodeApplyableProvider { & NodeAbstractProvider {
Addr : providerAddr ,
Config : config ,
} }
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
if err := n . Execute ( ctx , walkValidate ) ; err != nil {
t . Fatalf ( "err: %s" , err )
}
if ctx . ConfigureProviderCalled {
t . Fatal ( "should not be called" )
}
}
2020-12-14 16:39:48 -06:00
2020-12-11 12:18:49 -06:00
func TestNodeApplyableProvider_Validate ( t * testing . T ) {
2021-01-11 14:45:50 -06:00
provider := mockProviderWithConfigSchema ( & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"region" : {
Type : cty . String ,
Required : true ,
2020-12-11 12:18:49 -06:00
} ,
} ,
2021-01-11 14:45:50 -06:00
} )
2020-12-11 12:18:49 -06:00
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
t . Run ( "valid" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"region" : cty . StringVal ( "mars" ) ,
} ) ,
}
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
} ,
}
diags := node . ValidateProvider ( ctx , provider )
if diags . HasErrors ( ) {
t . Errorf ( "unexpected error with valid config: %s" , diags . Err ( ) )
}
} )
2020-12-14 16:39:48 -06:00
t . Run ( "invalid" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"region" : cty . MapValEmpty ( cty . String ) ,
} ) ,
}
2020-12-11 12:18:49 -06:00
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
2020-12-14 16:39:48 -06:00
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
2020-12-11 12:18:49 -06:00
} ,
}
diags := node . ValidateProvider ( ctx , provider )
if ! diags . HasErrors ( ) {
t . Error ( "missing expected error with invalid config" )
}
} )
2020-12-14 16:39:48 -06:00
t . Run ( "empty config" , func ( t * testing . T ) {
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
} ,
}
diags := node . ValidateProvider ( ctx , provider )
if diags . HasErrors ( ) {
t . Errorf ( "unexpected error with empty config: %s" , diags . Err ( ) )
}
} )
2020-12-11 12:18:49 -06:00
}
2022-08-17 13:46:02 -05:00
// This test specifically tests responses from the
// providers.ValidateProviderConfigFn. See
// TestNodeApplyableProvider_ConfigProvider_config_fn_err for
// providers.ConfigureProviderRequest responses.
2020-12-11 12:18:49 -06:00
func TestNodeApplyableProvider_ConfigProvider ( t * testing . T ) {
2021-01-11 14:45:50 -06:00
provider := mockProviderWithConfigSchema ( & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"region" : {
Type : cty . String ,
Optional : true ,
2020-12-11 12:18:49 -06:00
} ,
} ,
2021-01-11 14:45:50 -06:00
} )
2020-12-11 12:18:49 -06:00
// For this test, we're returning an error for an optional argument. This
// can happen for example if an argument is only conditionally required.
2021-02-18 09:13:43 -06:00
provider . ValidateProviderConfigFn = func ( req providers . ValidateProviderConfigRequest ) ( resp providers . ValidateProviderConfigResponse ) {
2020-12-11 12:18:49 -06:00
region := req . Config . GetAttr ( "region" )
if region . IsNull ( ) {
2021-04-28 15:38:14 -05:00
resp . Diagnostics = resp . Diagnostics . Append (
tfdiags . WholeContainingBody ( tfdiags . Error , "value is not found" , "you did not supply a required value" ) )
2020-12-11 12:18:49 -06:00
}
return
}
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
t . Run ( "valid" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"region" : cty . StringVal ( "mars" ) ,
} ) ,
}
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if diags . HasErrors ( ) {
t . Errorf ( "unexpected error with valid config: %s" , diags . Err ( ) )
}
} )
t . Run ( "missing required config (no config at all)" , func ( t * testing . T ) {
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if ! diags . HasErrors ( ) {
t . Fatal ( "missing expected error with nil config" )
}
if ! strings . Contains ( diags . Err ( ) . Error ( ) , "requires explicit configuration" ) {
t . Errorf ( "diagnostic is missing \"requires explicit configuration\" message: %s" , diags . Err ( ) )
}
} )
t . Run ( "missing required config" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : hcl . EmptyBody ( ) ,
}
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if ! diags . HasErrors ( ) {
t . Fatal ( "missing expected error with invalid config" )
}
2021-04-28 15:38:14 -05:00
if ! strings . Contains ( diags . Err ( ) . Error ( ) , "value is not found" ) {
2020-12-11 12:18:49 -06:00
t . Errorf ( "wrong diagnostic: %s" , diags . Err ( ) )
}
} )
}
2022-08-17 13:46:02 -05:00
// This test is similar to TestNodeApplyableProvider_ConfigProvider, but tests responses from the providers.ConfigureProviderRequest
2020-12-11 12:18:49 -06:00
func TestNodeApplyableProvider_ConfigProvider_config_fn_err ( t * testing . T ) {
2021-01-11 14:45:50 -06:00
provider := mockProviderWithConfigSchema ( & configschema . Block {
Attributes : map [ string ] * configschema . Attribute {
"region" : {
Type : cty . String ,
Optional : true ,
2020-12-11 12:18:49 -06:00
} ,
} ,
2021-01-11 14:45:50 -06:00
} )
2020-12-11 12:18:49 -06:00
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
// For this test, provider.PrepareConfigFn will succeed every time but the
// ctx.ConfigureProviderFn will return an error if a value is not found.
//
// This is an unlikely but real situation that occurs:
// https://github.com/hashicorp/terraform/issues/23087
ctx . ConfigureProviderFn = func ( addr addrs . AbsProviderConfig , cfg cty . Value ) ( diags tfdiags . Diagnostics ) {
if cfg . IsNull ( ) {
diags = diags . Append ( fmt . Errorf ( "no config provided" ) )
} else {
region := cfg . GetAttr ( "region" )
if region . IsNull ( ) {
diags = diags . Append ( fmt . Errorf ( "value is not found" ) )
}
}
return
}
t . Run ( "valid" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : configs . SynthBody ( "" , map [ string ] cty . Value {
"region" : cty . StringVal ( "mars" ) ,
} ) ,
}
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if diags . HasErrors ( ) {
t . Errorf ( "unexpected error with valid config: %s" , diags . Err ( ) )
}
} )
t . Run ( "missing required config (no config at all)" , func ( t * testing . T ) {
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if ! diags . HasErrors ( ) {
t . Fatal ( "missing expected error with nil config" )
}
if ! strings . Contains ( diags . Err ( ) . Error ( ) , "requires explicit configuration" ) {
t . Errorf ( "diagnostic is missing \"requires explicit configuration\" message: %s" , diags . Err ( ) )
}
} )
t . Run ( "missing required config" , func ( t * testing . T ) {
config := & configs . Provider {
Name : "test" ,
Config : hcl . EmptyBody ( ) ,
}
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : mustProviderConfig ( ` provider["registry.terraform.io/hashicorp/aws"] ` ) ,
Config : config ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
if ! diags . HasErrors ( ) {
t . Fatal ( "missing expected error with invalid config" )
}
if diags . Err ( ) . Error ( ) != "value is not found" {
t . Errorf ( "wrong diagnostic: %s" , diags . Err ( ) )
}
} )
}
2021-04-28 15:38:14 -05:00
func TestGetSchemaError ( t * testing . T ) {
provider := & MockProvider {
GetProviderSchemaResponse : & providers . GetProviderSchemaResponse {
Diagnostics : tfdiags . Diagnostics . Append ( nil , tfdiags . WholeContainingBody ( tfdiags . Error , "oops" , "error" ) ) ,
} ,
}
providerAddr := mustProviderConfig ( ` provider["terraform.io/some/provider"] ` )
ctx := & MockEvalContext { ProviderProvider : provider }
ctx . installSimpleEval ( )
node := NodeApplyableProvider {
NodeAbstractProvider : & NodeAbstractProvider {
Addr : providerAddr ,
} ,
}
diags := node . ConfigureProvider ( ctx , provider , false )
for _ , d := range diags {
desc := d . Description ( )
if desc . Address != providerAddr . String ( ) {
t . Fatalf ( "missing provider address from diagnostics: %#v" , desc )
}
}
}