2019-12-06 18:14:54 +01:00
package configs
import (
2021-02-09 14:38:30 +01:00
"fmt"
2020-01-10 17:54:53 +01:00
version "github.com/hashicorp/go-version"
2019-12-06 18:14:54 +01:00
"github.com/hashicorp/hcl/v2"
2020-01-13 17:31:47 +01:00
"github.com/hashicorp/terraform/addrs"
2020-06-24 16:09:12 +02:00
"github.com/zclconf/go-cty/cty"
2019-12-06 18:14:54 +01:00
)
2020-01-13 17:31:47 +01:00
// RequiredProvider represents a declaration of a dependency on a particular
2020-03-12 17:00:00 +01:00
// provider version or source without actually configuring that provider. This
// is used in child modules that expect a provider to be passed in from their
// parent.
2020-01-13 17:31:47 +01:00
type RequiredProvider struct {
2019-12-06 18:14:54 +01:00
Name string
2020-05-06 19:35:35 +02:00
Source string
2020-04-24 16:54:24 +02:00
Type addrs . Provider
2019-12-06 18:14:54 +01:00
Requirement VersionConstraint
2020-04-24 16:54:24 +02:00
DeclRange hcl . Range
2021-02-09 14:38:30 +01:00
Aliases [ ] addrs . LocalProviderConfig
2019-12-06 18:14:54 +01:00
}
2020-04-24 16:54:24 +02:00
type RequiredProviders struct {
RequiredProviders map [ string ] * RequiredProvider
DeclRange hcl . Range
2020-03-12 17:00:00 +01:00
}
2020-04-24 16:54:24 +02:00
func decodeRequiredProvidersBlock ( block * hcl . Block ) ( * RequiredProviders , hcl . Diagnostics ) {
2019-12-06 18:14:54 +01:00
attrs , diags := block . Body . JustAttributes ( )
2021-02-09 14:38:30 +01:00
if diags . HasErrors ( ) {
return nil , diags
}
2020-04-24 16:54:24 +02:00
ret := & RequiredProviders {
RequiredProviders : make ( map [ string ] * RequiredProvider ) ,
DeclRange : block . DefRange ,
}
2020-05-14 15:00:58 +02:00
2021-02-09 14:38:30 +01:00
for name , attr := range attrs {
2020-04-24 16:54:24 +02:00
rp := & RequiredProvider {
Name : name ,
DeclRange : attr . Expr . Range ( ) ,
}
2021-02-09 14:38:30 +01:00
// Look for a single static string, in case we have the legacy version-only
// format in the configuration.
if expr , err := attr . Expr . Value ( nil ) ; err == nil && expr . Type ( ) . IsPrimitiveType ( ) {
2020-01-10 17:54:53 +01:00
vc , reqDiags := decodeVersionConstraint ( attr )
diags = append ( diags , reqDiags ... )
2021-02-09 14:38:30 +01:00
pType , err := addrs . ParseProviderPart ( rp . Name )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider name" ,
Detail : err . Error ( ) ,
Subject : attr . Expr . Range ( ) . Ptr ( ) ,
} )
continue
}
2020-04-24 16:54:24 +02:00
rp . Requirement = vc
2021-02-09 14:38:30 +01:00
rp . Type = addrs . ImpliedProviderForUnqualifiedType ( pType )
ret . RequiredProviders [ name ] = rp
continue
}
// verify that the local name is already localized or produce an error.
nameDiags := checkProviderNameNormalized ( name , attr . Expr . Range ( ) )
if nameDiags . HasErrors ( ) {
diags = append ( diags , nameDiags ... )
continue
}
2020-04-24 16:54:24 +02:00
2021-02-09 14:38:30 +01:00
kvs , mapDiags := hcl . ExprMap ( attr . Expr )
if mapDiags . HasErrors ( ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid required_providers object" ,
Detail : "required_providers entries must be strings or objects." ,
Subject : attr . Expr . Range ( ) . Ptr ( ) ,
} )
continue
}
for _ , kv := range kvs {
key , keyDiags := kv . Key . Value ( nil )
if keyDiags . HasErrors ( ) {
diags = append ( diags , keyDiags ... )
continue
}
if key . Type ( ) != cty . String {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid Attribute" ,
Detail : fmt . Sprintf ( "Invalid attribute value for provider requirement: %#v" , key ) ,
Subject : kv . Key . Range ( ) . Ptr ( ) ,
} )
continue
}
switch key . AsString ( ) {
case "version" :
2020-01-10 17:54:53 +01:00
vc := VersionConstraint {
DeclRange : attr . Range ,
}
2021-02-09 14:38:30 +01:00
constraint , valDiags := kv . Value . Value ( nil )
if valDiags . HasErrors ( ) || ! constraint . Type ( ) . Equals ( cty . String ) {
2020-01-10 17:54:53 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid version constraint" ,
2020-06-24 16:09:12 +02:00
Detail : "Version must be specified as a string." ,
2021-02-09 14:38:30 +01:00
Subject : kv . Value . Range ( ) . Ptr ( ) ,
2020-01-10 17:54:53 +01:00
} )
2021-02-09 14:38:30 +01:00
continue
2020-01-10 17:54:53 +01:00
}
2021-02-09 14:38:30 +01:00
constraintStr := constraint . AsString ( )
constraints , err := version . NewConstraint ( constraintStr )
if err != nil {
// NewConstraint doesn't return user-friendly errors, so we'll just
// ignore the provided error and produce our own generic one.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid version constraint" ,
Detail : "This string does not use correct version constraint syntax." ,
Subject : kv . Value . Range ( ) . Ptr ( ) ,
} )
continue
}
vc . Required = constraints
rp . Requirement = vc
case "source" :
source , err := kv . Value . Value ( nil )
if err != nil || ! source . Type ( ) . Equals ( cty . String ) {
2020-06-24 16:09:12 +02:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid source" ,
Detail : "Source must be specified as a string." ,
2021-02-09 14:38:30 +01:00
Subject : kv . Value . Range ( ) . Ptr ( ) ,
2020-06-24 16:09:12 +02:00
} )
2021-02-09 14:38:30 +01:00
continue
}
fqn , sourceDiags := addrs . ParseProviderSourceString ( source . AsString ( ) )
if sourceDiags . HasErrors ( ) {
hclDiags := sourceDiags . ToHCL ( )
// The diagnostics from ParseProviderSourceString don't contain
// source location information because it has no context to compute
// them from, and so we'll add those in quickly here before we
// return.
for _ , diag := range hclDiags {
if diag . Subject == nil {
diag . Subject = kv . Value . Range ( ) . Ptr ( )
2020-04-24 16:54:24 +02:00
}
}
2021-02-09 14:38:30 +01:00
diags = append ( diags , hclDiags ... )
continue
2020-04-24 16:54:24 +02:00
}
2021-02-09 14:38:30 +01:00
rp . Source = source . AsString ( )
rp . Type = fqn
case "configuration_aliases" :
exprs , listDiags := hcl . ExprList ( kv . Value )
if listDiags . HasErrors ( ) {
diags = append ( diags , listDiags ... )
2020-09-09 17:50:28 +02:00
continue
}
2021-02-09 14:38:30 +01:00
for _ , expr := range exprs {
traversal , travDiags := hcl . AbsTraversalForExpr ( expr )
if travDiags . HasErrors ( ) {
diags = append ( diags , travDiags ... )
continue
}
addr , cfgDiags := ParseProviderConfigCompact ( traversal )
if cfgDiags . HasErrors ( ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid configuration_aliases value" ,
Detail : ` Configuration aliases can only contain references to local provider configuration names in the format of provider.alias ` ,
Subject : kv . Value . Range ( ) . Ptr ( ) ,
} )
continue
}
if addr . LocalName != name {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid configuration_aliases value" ,
Detail : fmt . Sprintf ( ` Configuration aliases must be prefixed with the provider name. Expected %q, but found %q. ` , name , addr . LocalName ) ,
Subject : kv . Value . Range ( ) . Ptr ( ) ,
} )
continue
}
rp . Aliases = append ( rp . Aliases , addr )
}
default :
2020-09-09 17:50:28 +02:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid required_providers object" ,
2021-02-09 14:38:30 +01:00
Detail : ` required_providers objects can only contain "version", "source" and "configuration_aliases" attributes. To configure a provider, use a "provider" block. ` ,
Subject : kv . Key . Range ( ) . Ptr ( ) ,
2020-09-09 17:50:28 +02:00
} )
break
}
2020-04-24 16:54:24 +02:00
2019-12-06 18:14:54 +01:00
}
2020-04-24 16:54:24 +02:00
2021-02-09 14:38:30 +01:00
// finally add the required provider as long as there were no errors
if ! diags . HasErrors ( ) {
// if a source was not given, create an implied type
if rp . Type . IsZero ( ) {
pType , err := addrs . ParseProviderPart ( rp . Name )
if err != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider name" ,
Detail : err . Error ( ) ,
Subject : attr . Expr . Range ( ) . Ptr ( ) ,
} )
} else {
rp . Type = addrs . ImpliedProviderForUnqualifiedType ( pType )
}
2020-04-24 16:54:24 +02:00
}
2021-02-09 14:38:30 +01:00
ret . RequiredProviders [ rp . Name ] = rp
}
2019-12-06 18:14:54 +01:00
}
2020-04-24 16:54:24 +02:00
return ret , diags
2019-12-06 18:14:54 +01:00
}