@ -4,17 +4,23 @@
package configs
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/zclconf/go-cty/cty"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2/hclsyntax"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders"
@ -163,6 +169,42 @@ func TestConfigProviderRequirements(t *testing.T) {
}
}
func TestConfigProviderRequirementsInclTests ( t * testing . T ) {
cfg , diags := testNestedModuleConfigFromDirWithTests ( t , "testdata/provider-reqs-with-tests" )
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount ( t , diags , 1 )
assertDiagnosticSummary ( t , diags , "Version constraints inside provider configuration blocks are deprecated" )
tlsProvider := addrs . NewProvider (
addrs . DefaultProviderRegistryHost ,
"hashicorp" , "tls" ,
)
nullProvider := addrs . NewDefaultProvider ( "null" )
randomProvider := addrs . NewDefaultProvider ( "random" )
impliedProvider := addrs . NewDefaultProvider ( "implied" )
terraformProvider := addrs . NewBuiltInProvider ( "terraform" )
configuredProvider := addrs . NewDefaultProvider ( "configured" )
got , diags := cfg . ProviderRequirements ( )
assertNoDiagnostics ( t , diags )
want := getproviders . Requirements {
// the nullProvider constraints from the two modules are merged
nullProvider : getproviders . MustParseVersionConstraints ( "~> 2.0.0" ) ,
randomProvider : getproviders . MustParseVersionConstraints ( "~> 1.2.0" ) ,
tlsProvider : getproviders . MustParseVersionConstraints ( "~> 3.0" ) ,
configuredProvider : getproviders . MustParseVersionConstraints ( "~> 1.4" ) ,
impliedProvider : nil ,
terraformProvider : nil ,
}
if diff := cmp . Diff ( want , got ) ; diff != "" {
t . Errorf ( "wrong result\n%s" , diff )
}
}
func TestConfigProviderRequirementsDuplicate ( t * testing . T ) {
_ , diags := testNestedModuleConfigFromDir ( t , "testdata/duplicate-local-name" )
assertDiagnosticCount ( t , diags , 3 )
@ -205,6 +247,37 @@ func TestConfigProviderRequirementsShallow(t *testing.T) {
}
}
func TestConfigProviderRequirementsShallowInclTests ( t * testing . T ) {
cfg , diags := testNestedModuleConfigFromDirWithTests ( t , "testdata/provider-reqs-with-tests" )
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount ( t , diags , 1 )
assertDiagnosticSummary ( t , diags , "Version constraints inside provider configuration blocks are deprecated" )
tlsProvider := addrs . NewProvider (
addrs . DefaultProviderRegistryHost ,
"hashicorp" , "tls" ,
)
impliedProvider := addrs . NewDefaultProvider ( "implied" )
terraformProvider := addrs . NewBuiltInProvider ( "terraform" )
configuredProvider := addrs . NewDefaultProvider ( "configured" )
got , diags := cfg . ProviderRequirementsShallow ( )
assertNoDiagnostics ( t , diags )
want := getproviders . Requirements {
tlsProvider : getproviders . MustParseVersionConstraints ( "~> 3.0" ) ,
configuredProvider : getproviders . MustParseVersionConstraints ( "~> 1.4" ) ,
impliedProvider : nil ,
terraformProvider : nil ,
}
if diff := cmp . Diff ( want , got ) ; diff != "" {
t . Errorf ( "wrong result\n%s" , diff )
}
}
func TestConfigProviderRequirementsByModule ( t * testing . T ) {
cfg , diags := testNestedModuleConfigFromDir ( t , "testdata/provider-reqs" )
// TODO: Version Constraint Deprecation.
@ -262,6 +335,69 @@ func TestConfigProviderRequirementsByModule(t *testing.T) {
grandchildProvider : nil ,
} ,
Children : map [ string ] * ModuleRequirements { } ,
Tests : make ( map [ string ] * TestFileModuleRequirements ) ,
} ,
} ,
Tests : make ( map [ string ] * TestFileModuleRequirements ) ,
} ,
} ,
Tests : make ( map [ string ] * TestFileModuleRequirements ) ,
}
ignore := cmpopts . IgnoreUnexported ( version . Constraint { } , cty . Value { } , hclsyntax . Body { } )
if diff := cmp . Diff ( want , got , ignore ) ; diff != "" {
t . Errorf ( "wrong result\n%s" , diff )
}
}
func TestConfigProviderRequirementsByModuleInclTests ( t * testing . T ) {
cfg , diags := testNestedModuleConfigFromDirWithTests ( t , "testdata/provider-reqs-with-tests" )
// TODO: Version Constraint Deprecation.
// Once we've removed the version argument from provider configuration
// blocks, this can go back to expected 0 diagnostics.
// assertNoDiagnostics(t, diags)
assertDiagnosticCount ( t , diags , 1 )
assertDiagnosticSummary ( t , diags , "Version constraints inside provider configuration blocks are deprecated" )
tlsProvider := addrs . NewProvider (
addrs . DefaultProviderRegistryHost ,
"hashicorp" , "tls" ,
)
nullProvider := addrs . NewDefaultProvider ( "null" )
randomProvider := addrs . NewDefaultProvider ( "random" )
impliedProvider := addrs . NewDefaultProvider ( "implied" )
terraformProvider := addrs . NewBuiltInProvider ( "terraform" )
configuredProvider := addrs . NewDefaultProvider ( "configured" )
got , diags := cfg . ProviderRequirementsByModule ( )
assertNoDiagnostics ( t , diags )
want := & ModuleRequirements {
Name : "" ,
SourceAddr : nil ,
SourceDir : "testdata/provider-reqs-with-tests" ,
Requirements : getproviders . Requirements {
// Only the root module's version is present here
tlsProvider : getproviders . MustParseVersionConstraints ( "~> 3.0" ) ,
impliedProvider : nil ,
terraformProvider : nil ,
} ,
Children : make ( map [ string ] * ModuleRequirements ) ,
Tests : map [ string ] * TestFileModuleRequirements {
"provider-reqs-root.tftest" : {
Requirements : getproviders . Requirements {
configuredProvider : getproviders . MustParseVersionConstraints ( "~> 1.4" ) ,
} ,
Runs : map [ string ] * ModuleRequirements {
"setup" : {
Name : "setup" ,
SourceAddr : addrs . ModuleSourceLocal ( "./setup" ) ,
SourceDir : "testdata/provider-reqs-with-tests/setup" ,
Requirements : getproviders . Requirements {
nullProvider : getproviders . MustParseVersionConstraints ( "~> 2.0.0" ) ,
randomProvider : getproviders . MustParseVersionConstraints ( "~> 1.2.0" ) ,
} ,
Children : make ( map [ string ] * ModuleRequirements ) ,
Tests : make ( map [ string ] * TestFileModuleRequirements ) ,
} ,
} ,
} ,
@ -421,7 +557,7 @@ func TestConfigAddProviderRequirements(t *testing.T) {
reqs := getproviders . Requirements {
addrs . NewDefaultProvider ( "null" ) : nil ,
}
diags = cfg . addProviderRequirements ( reqs , true )
diags = cfg . addProviderRequirements ( reqs , true , false )
assertNoDiagnostics ( t , diags )
}
@ -447,7 +583,7 @@ func TestConfigImportProviderClashesWithResources(t *testing.T) {
cfg , diags := testModuleConfigFromFile ( "testdata/invalid-import-files/import-and-resource-clash.tf" )
assertNoDiagnostics ( t , diags )
diags = cfg . addProviderRequirements ( getproviders . Requirements { } , true )
diags = cfg . addProviderRequirements ( getproviders . Requirements { } , true , false )
assertExactDiagnostics ( t , diags , [ ] string {
` testdata / invalid - import - files / import - and - resource - clash . tf : 9 , 3 - 19 : Invalid import provider argument ; The provider argument can only be specified in import blocks that will generate configuration .
@ -459,10 +595,235 @@ func TestConfigImportProviderWithNoResourceProvider(t *testing.T) {
cfg , diags := testModuleConfigFromFile ( "testdata/invalid-import-files/import-and-no-resource.tf" )
assertNoDiagnostics ( t , diags )
diags = cfg . addProviderRequirements ( getproviders . Requirements { } , true )
diags = cfg . addProviderRequirements ( getproviders . Requirements { } , true , false )
assertExactDiagnostics ( t , diags , [ ] string {
` testdata / invalid - import - files / import - and - no - resource . tf : 5 , 3 - 19 : Invalid import provider argument ; The provider argument can only be specified in import blocks that will generate configuration .
Use the provider argument in the target resource block to configure the provider for a resource with explicit provider configuration . ` ,
} )
}
func TestTransformForTest ( t * testing . T ) {
str := func ( providers map [ string ] string ) string {
var buffer bytes . Buffer
for key , config := range providers {
buffer . WriteString ( fmt . Sprintf ( "%s: %s\n" , key , config ) )
}
return buffer . String ( )
}
convertToProviders := func ( t * testing . T , contents map [ string ] string ) map [ string ] * Provider {
t . Helper ( )
providers := make ( map [ string ] * Provider )
for key , content := range contents {
parser := hclparse . NewParser ( )
file , diags := parser . ParseHCL ( [ ] byte ( content ) , fmt . Sprintf ( "%s.hcl" , key ) )
if diags . HasErrors ( ) {
t . Fatal ( diags . Error ( ) )
}
provider := & Provider {
Config : file . Body ,
}
parts := strings . Split ( key , "." )
provider . Name = parts [ 0 ]
if len ( parts ) > 1 {
provider . Alias = parts [ 1 ]
}
providers [ key ] = provider
}
return providers
}
validate := func ( t * testing . T , msg string , expected map [ string ] string , actual map [ string ] * Provider ) {
t . Helper ( )
converted := make ( map [ string ] string )
for key , provider := range actual {
content , err := provider . Config . Content ( & hcl . BodySchema {
Attributes : [ ] hcl . AttributeSchema {
{ Name : "source" , Required : true } ,
} ,
} )
if err != nil {
t . Fatal ( err )
}
source , diags := content . Attributes [ "source" ] . Expr . Value ( nil )
if diags . HasErrors ( ) {
t . Fatal ( diags . Error ( ) )
}
converted [ key ] = fmt . Sprintf ( "source = %q" , source . AsString ( ) )
}
if diff := cmp . Diff ( expected , converted ) ; len ( diff ) > 0 {
t . Errorf ( "%s\nexpected:\n%s\nactual:\n%s\ndiff:\n%s" , msg , str ( expected ) , str ( converted ) , diff )
}
}
tcs := map [ string ] struct {
configProviders map [ string ] string
fileProviders map [ string ] string
runProviders [ ] PassedProviderConfig
expectedProviders map [ string ] string
expectedErrors [ ] string
} {
"empty" : {
configProviders : make ( map [ string ] string ) ,
expectedProviders : make ( map [ string ] string ) ,
} ,
"only providers in config" : {
configProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"config\"" ,
} ,
expectedProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"config\"" ,
} ,
} ,
"only providers in test file" : {
configProviders : make ( map [ string ] string ) ,
fileProviders : map [ string ] string {
"foo" : "source = \"testfile\"" ,
"bar" : "source = \"testfile\"" ,
} ,
expectedProviders : map [ string ] string {
"foo" : "source = \"testfile\"" ,
"bar" : "source = \"testfile\"" ,
} ,
} ,
"only providers in run block" : {
configProviders : make ( map [ string ] string ) ,
runProviders : [ ] PassedProviderConfig {
{
InChild : & ProviderConfigRef {
Name : "foo" ,
} ,
InParent : & ProviderConfigRef {
Name : "bar" ,
} ,
} ,
} ,
expectedProviders : make ( map [ string ] string ) ,
expectedErrors : [ ] string {
":0,0-0: Missing provider definition for bar; This provider block references a provider definition that does not exist." ,
} ,
} ,
"subset of providers in test file" : {
configProviders : make ( map [ string ] string ) ,
fileProviders : map [ string ] string {
"bar" : "source = \"testfile\"" ,
} ,
runProviders : [ ] PassedProviderConfig {
{
InChild : & ProviderConfigRef {
Name : "foo" ,
} ,
InParent : & ProviderConfigRef {
Name : "bar" ,
} ,
} ,
} ,
expectedProviders : map [ string ] string {
"foo" : "source = \"testfile\"" ,
} ,
} ,
"overrides providers in config" : {
configProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"config\"" ,
} ,
fileProviders : map [ string ] string {
"bar" : "source = \"testfile\"" ,
} ,
expectedProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"testfile\"" ,
} ,
} ,
"overrides subset of providers in config" : {
configProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"config\"" ,
} ,
fileProviders : map [ string ] string {
"foo" : "source = \"testfile\"" ,
"bar" : "source = \"testfile\"" ,
} ,
runProviders : [ ] PassedProviderConfig {
{
InChild : & ProviderConfigRef {
Name : "bar" ,
} ,
InParent : & ProviderConfigRef {
Name : "bar" ,
} ,
} ,
} ,
expectedProviders : map [ string ] string {
"foo" : "source = \"config\"" ,
"bar" : "source = \"testfile\"" ,
} ,
} ,
"handles aliases" : {
configProviders : map [ string ] string {
"foo.primary" : "source = \"config\"" ,
"foo.secondary" : "source = \"config\"" ,
} ,
fileProviders : map [ string ] string {
"foo" : "source = \"testfile\"" ,
} ,
runProviders : [ ] PassedProviderConfig {
{
InChild : & ProviderConfigRef {
Name : "foo.secondary" ,
} ,
InParent : & ProviderConfigRef {
Name : "foo" ,
} ,
} ,
} ,
expectedProviders : map [ string ] string {
"foo.primary" : "source = \"config\"" ,
"foo.secondary" : "source = \"testfile\"" ,
} ,
} ,
}
for name , tc := range tcs {
t . Run ( name , func ( t * testing . T ) {
config := & Config {
Module : & Module {
ProviderConfigs : convertToProviders ( t , tc . configProviders ) ,
} ,
}
file := & TestFile {
Providers : convertToProviders ( t , tc . fileProviders ) ,
}
run := & TestRun {
Providers : tc . runProviders ,
}
reset , diags := config . TransformForTest ( run , file )
var actualErrs [ ] string
for _ , err := range diags . Errs ( ) {
actualErrs = append ( actualErrs , err . Error ( ) )
}
if diff := cmp . Diff ( actualErrs , tc . expectedErrors , cmpopts . IgnoreUnexported ( ) ) ; len ( diff ) > 0 {
t . Errorf ( "unmatched errors\nexpected:\n%s\nactual:\n%s\ndiff:\n%s" , strings . Join ( tc . expectedErrors , "\n" ) , strings . Join ( actualErrs , "\n" ) , diff )
}
validate ( t , "after transform mismatch" , tc . expectedProviders , config . Module . ProviderConfigs )
reset ( )
validate ( t , "after reset mismatch" , tc . configProviders , config . Module . ProviderConfigs )
} )
}
}