2018-02-02 05:33:06 +01:00
package configs
import (
2018-04-24 19:06:51 +02:00
"fmt"
2019-09-10 00:58:44 +02:00
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
2018-02-02 05:33:06 +01:00
)
// ModuleCall represents a "module" block in a module or file.
type ModuleCall struct {
2018-02-03 02:22:25 +01:00
Name string
SourceAddr string
SourceAddrRange hcl . Range
2018-02-07 03:05:14 +01:00
SourceSet bool
2018-02-03 02:22:25 +01:00
Config hcl . Body
2018-02-02 05:33:06 +01:00
Version VersionConstraint
Count hcl . Expression
ForEach hcl . Expression
2018-04-24 19:06:51 +02:00
Providers [ ] PassedProviderConfig
2018-02-03 02:22:25 +01:00
DependsOn [ ] hcl . Traversal
2018-02-02 05:33:06 +01:00
DeclRange hcl . Range
}
2018-02-03 02:22:25 +01:00
2018-02-07 03:05:14 +01:00
func decodeModuleBlock ( block * hcl . Block , override bool ) ( * ModuleCall , hcl . Diagnostics ) {
2018-02-03 02:22:25 +01:00
mc := & ModuleCall {
Name : block . Labels [ 0 ] ,
DeclRange : block . DefRange ,
}
2018-02-07 03:05:14 +01:00
schema := moduleBlockSchema
if override {
schema = schemaForOverrides ( schema )
}
content , remain , diags := block . Body . PartialContent ( schema )
2018-02-03 02:22:25 +01:00
mc . Config = remain
if ! hclsyntax . ValidIdentifier ( mc . Name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid module instance name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ] ,
} )
}
if attr , exists := content . Attributes [ "source" ] ; exists {
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & mc . SourceAddr )
diags = append ( diags , valDiags ... )
mc . SourceAddrRange = attr . Expr . Range ( )
2018-02-07 03:05:14 +01:00
mc . SourceSet = true
2018-02-03 02:22:25 +01:00
}
if attr , exists := content . Attributes [ "version" ] ; exists {
var versionDiags hcl . Diagnostics
mc . Version , versionDiags = decodeVersionConstraint ( attr )
diags = append ( diags , versionDiags ... )
}
if attr , exists := content . Attributes [ "count" ] ; exists {
mc . Count = attr . Expr
2018-11-20 20:53:45 +01:00
// We currently parse this, but don't yet do anything with it.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved argument name in module block" ,
Detail : fmt . Sprintf ( "The name %q is reserved for use in a future version of Terraform." , attr . Name ) ,
Subject : & attr . NameRange ,
} )
2018-02-03 02:22:25 +01:00
}
if attr , exists := content . Attributes [ "for_each" ] ; exists {
mc . ForEach = attr . Expr
2018-11-20 20:53:45 +01:00
// We currently parse this, but don't yet do anything with it.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved argument name in module block" ,
Detail : fmt . Sprintf ( "The name %q is reserved for use in a future version of Terraform." , attr . Name ) ,
Subject : & attr . NameRange ,
} )
2018-02-03 02:22:25 +01:00
}
if attr , exists := content . Attributes [ "depends_on" ] ; exists {
deps , depsDiags := decodeDependsOn ( attr )
diags = append ( diags , depsDiags ... )
mc . DependsOn = append ( mc . DependsOn , deps ... )
2018-11-20 20:53:45 +01:00
// We currently parse this, but don't yet do anything with it.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved argument name in module block" ,
Detail : fmt . Sprintf ( "The name %q is reserved for use in a future version of Terraform." , attr . Name ) ,
Subject : & attr . NameRange ,
} )
2018-02-03 02:22:25 +01:00
}
2018-04-24 19:06:51 +02:00
if attr , exists := content . Attributes [ "providers" ] ; exists {
seen := make ( map [ string ] hcl . Range )
pairs , pDiags := hcl . ExprMap ( attr . Expr )
diags = append ( diags , pDiags ... )
for _ , pair := range pairs {
key , keyDiags := decodeProviderConfigRef ( pair . Key , "providers" )
diags = append ( diags , keyDiags ... )
value , valueDiags := decodeProviderConfigRef ( pair . Value , "providers" )
diags = append ( diags , valueDiags ... )
if keyDiags . HasErrors ( ) || valueDiags . HasErrors ( ) {
continue
}
matchKey := key . String ( )
if prev , exists := seen [ matchKey ] ; exists {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate provider address" ,
Detail : fmt . Sprintf ( "A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once." , matchKey , prev ) ,
Subject : pair . Value . Range ( ) . Ptr ( ) ,
} )
continue
}
rng := hcl . RangeBetween ( pair . Key . Range ( ) , pair . Value . Range ( ) )
seen [ matchKey ] = rng
mc . Providers = append ( mc . Providers , PassedProviderConfig {
InChild : key ,
InParent : value ,
} )
}
}
2018-11-20 20:53:45 +01:00
// Reserved block types (all of them)
for _ , block := range content . Blocks {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved block type name in module block" ,
Detail : fmt . Sprintf ( "The block type name %q is reserved for use by Terraform in a future version." , block . Type ) ,
Subject : & block . TypeRange ,
} )
}
2018-02-03 02:22:25 +01:00
return mc , diags
}
2018-04-24 19:06:51 +02:00
// PassedProviderConfig represents a provider config explicitly passed down to
// a child module, possibly giving it a new local address in the process.
type PassedProviderConfig struct {
InChild * ProviderConfigRef
InParent * ProviderConfigRef
}
2018-02-03 02:22:25 +01:00
var moduleBlockSchema = & hcl . BodySchema {
Attributes : [ ] hcl . AttributeSchema {
{
Name : "source" ,
Required : true ,
} ,
{
Name : "version" ,
} ,
{
Name : "count" ,
} ,
{
Name : "for_each" ,
} ,
{
Name : "depends_on" ,
} ,
2018-04-24 19:06:51 +02:00
{
Name : "providers" ,
} ,
2018-02-03 02:22:25 +01:00
} ,
2018-11-20 20:53:45 +01:00
Blocks : [ ] hcl . BlockHeaderSchema {
// These are all reserved for future use.
{ Type : "lifecycle" } ,
{ Type : "locals" } ,
{ Type : "provider" , LabelNames : [ ] string { "type" } } ,
} ,
2018-02-03 02:22:25 +01:00
}