2018-02-02 05:33:06 +01:00
package configs
import (
2018-02-03 02:22:25 +01:00
"fmt"
2018-04-06 20:07:39 +02:00
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-04-30 19:15:27 +02:00
2021-05-17 21:00:50 +02:00
"github.com/hashicorp/terraform/internal/addrs"
2018-02-02 05:33:06 +01:00
)
2018-04-06 20:07:39 +02:00
// Resource represents a "resource" or "data" block in a module or file.
type Resource struct {
Mode addrs . ResourceMode
2018-02-02 05:33:06 +01:00
Name string
Type string
Config hcl . Body
Count hcl . Expression
ForEach hcl . Expression
2018-02-03 02:22:25 +01:00
ProviderConfigRef * ProviderConfigRef
2020-03-16 19:36:16 +01:00
Provider addrs . Provider
2018-02-02 05:33:06 +01:00
2020-11-20 23:56:21 +01:00
Preconditions [ ] * CheckRule
Postconditions [ ] * CheckRule
2018-02-02 05:33:06 +01:00
DependsOn [ ] hcl . Traversal
2018-04-06 20:07:39 +02:00
// Managed is populated only for Mode = addrs.ManagedResourceMode,
// containing the additional fields that apply to managed resources.
// For all other resource modes, this field is nil.
Managed * ManagedResource
DeclRange hcl . Range
TypeRange hcl . Range
}
// ManagedResource represents a "resource" block in a module or file.
type ManagedResource struct {
2018-02-02 05:33:06 +01:00
Connection * Connection
Provisioners [ ] * Provisioner
CreateBeforeDestroy bool
PreventDestroy bool
IgnoreChanges [ ] hcl . Traversal
2018-02-15 20:25:06 +01:00
IgnoreAllChanges bool
2018-02-02 05:33:06 +01:00
2018-02-07 03:05:14 +01:00
CreateBeforeDestroySet bool
PreventDestroySet bool
2018-02-03 02:22:25 +01:00
}
2018-04-06 20:07:39 +02:00
func ( r * Resource ) moduleUniqueKey ( ) string {
2018-05-03 02:51:01 +02:00
return r . Addr ( ) . String ( )
2018-02-07 01:28:40 +01:00
}
2018-04-30 19:15:27 +02:00
// Addr returns a resource address for the receiver that is relative to the
// resource's containing module.
func ( r * Resource ) Addr ( ) addrs . Resource {
return addrs . Resource {
Mode : r . Mode ,
Type : r . Type ,
Name : r . Name ,
}
}
2020-03-18 13:58:20 +01:00
// ProviderConfigAddr returns the address for the provider configuration that
// should be used for this resource. This function returns a default provider
// config addr if an explicit "provider" argument was not provided.
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
func ( r * Resource ) ProviderConfigAddr ( ) addrs . LocalProviderConfig {
2018-03-28 02:22:51 +02:00
if r . ProviderConfigRef == nil {
configs: Fix provider lookup local name mismatch
When a resource has no `provider` argument specified, its provider is
derived from the implied provider type based on the resource type. For
example, a `boop_instance` resource has an implied provider local name
of `boop`. Correspondingly, its provider configuration is specified with
a `provider "boop"` block.
However, users can use the `required_providers` configuration to give a
different local name to a given provider than its defined type. For
example, a provider may be published at `foobar/beep`, but provide
resources such as `boop_instance`. The most convenient way to use this
provider is with a `required_providers` map:
terraform {
required_providers {
boop = {
source = "foobar/beep"
}
}
}
Once that local name is defined, it is used for provider configuration
(a `provider "boop"` block, not `provider "beep"`). It should also be
used when looking up a resource's provider configuration or provider.
This commit fixes a bug with this edge case, where previously we were
looking up the local provider configuration block using the resource's
assigned provider type. Instead, if no provider argument is specified,
we should be using the implied provider type, as that is what binds the
resource to the local provider configuration.
2020-11-10 21:12:45 +01:00
// If no specific "provider" argument is given, we want to look up the
// provider config where the local name matches the implied provider
// from the resource type. This may be different from the resource's
// provider type.
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
return addrs . LocalProviderConfig {
configs: Fix provider lookup local name mismatch
When a resource has no `provider` argument specified, its provider is
derived from the implied provider type based on the resource type. For
example, a `boop_instance` resource has an implied provider local name
of `boop`. Correspondingly, its provider configuration is specified with
a `provider "boop"` block.
However, users can use the `required_providers` configuration to give a
different local name to a given provider than its defined type. For
example, a provider may be published at `foobar/beep`, but provide
resources such as `boop_instance`. The most convenient way to use this
provider is with a `required_providers` map:
terraform {
required_providers {
boop = {
source = "foobar/beep"
}
}
}
Once that local name is defined, it is used for provider configuration
(a `provider "boop"` block, not `provider "beep"`). It should also be
used when looking up a resource's provider configuration or provider.
This commit fixes a bug with this edge case, where previously we were
looking up the local provider configuration block using the resource's
assigned provider type. Instead, if no provider argument is specified,
we should be using the implied provider type, as that is what binds the
resource to the local provider configuration.
2020-11-10 21:12:45 +01:00
LocalName : r . Addr ( ) . ImpliedProvider ( ) ,
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
}
2018-03-28 02:22:51 +02:00
}
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
return addrs . LocalProviderConfig {
LocalName : r . ProviderConfigRef . Name ,
Alias : r . ProviderConfigRef . Alias ,
2018-04-30 19:15:27 +02:00
}
2018-03-28 02:22:51 +02:00
}
2020-11-20 23:56:21 +01:00
func decodeResourceBlock ( block * hcl . Block , override bool ) ( * Resource , hcl . Diagnostics ) {
2019-11-12 21:10:29 +01:00
var diags hcl . Diagnostics
2018-04-06 20:07:39 +02:00
r := & Resource {
Mode : addrs . ManagedResourceMode ,
2018-02-03 02:22:25 +01:00
Type : block . Labels [ 0 ] ,
Name : block . Labels [ 1 ] ,
DeclRange : block . DefRange ,
TypeRange : block . LabelRanges [ 0 ] ,
2018-04-06 20:07:39 +02:00
Managed : & ManagedResource { } ,
2018-02-03 02:22:25 +01:00
}
2019-11-12 21:10:29 +01:00
content , remain , moreDiags := block . Body . PartialContent ( resourceBlockSchema )
diags = append ( diags , moreDiags ... )
2018-02-03 02:22:25 +01:00
r . Config = remain
if ! hclsyntax . ValidIdentifier ( r . Type ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid resource type name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ] ,
} )
}
if ! hclsyntax . ValidIdentifier ( r . Name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid resource name" ,
Detail : badIdentifierDetail ,
2018-03-01 21:18:07 +01:00
Subject : & block . LabelRanges [ 1 ] ,
2018-02-03 02:22:25 +01:00
} )
}
if attr , exists := content . Attributes [ "count" ] ; exists {
r . Count = attr . Expr
}
if attr , exists := content . Attributes [ "for_each" ] ; exists {
2018-11-20 20:53:45 +01:00
r . ForEach = attr . Expr
2019-06-12 17:07:32 +02:00
// Cannot have count and for_each on the same resource block
if r . Count != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : ` Invalid combination of "count" and "for_each" ` ,
Detail : ` The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created. ` ,
Subject : & attr . NameRange ,
} )
}
2018-02-03 02:22:25 +01:00
}
if attr , exists := content . Attributes [ "provider" ] ; exists {
var providerDiags hcl . Diagnostics
2018-04-24 19:06:51 +02:00
r . ProviderConfigRef , providerDiags = decodeProviderConfigRef ( attr . Expr , "provider" )
2018-02-03 02:22:25 +01:00
diags = append ( diags , providerDiags ... )
}
if attr , exists := content . Attributes [ "depends_on" ] ; exists {
deps , depsDiags := decodeDependsOn ( attr )
diags = append ( diags , depsDiags ... )
r . DependsOn = append ( r . DependsOn , deps ... )
}
var seenLifecycle * hcl . Block
var seenConnection * hcl . Block
2021-05-15 01:09:51 +02:00
var seenEscapeBlock * hcl . Block
2018-02-03 02:22:25 +01:00
for _ , block := range content . Blocks {
switch block . Type {
case "lifecycle" :
if seenLifecycle != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate lifecycle block" ,
Detail : fmt . Sprintf ( "This resource already has a lifecycle block at %s." , seenLifecycle . DefRange ) ,
Subject : & block . DefRange ,
} )
continue
}
seenLifecycle = block
lcContent , lcDiags := block . Body . Content ( resourceLifecycleBlockSchema )
diags = append ( diags , lcDiags ... )
if attr , exists := lcContent . Attributes [ "create_before_destroy" ] ; exists {
2018-04-06 20:07:39 +02:00
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & r . Managed . CreateBeforeDestroy )
2018-02-03 02:22:25 +01:00
diags = append ( diags , valDiags ... )
2018-04-06 20:07:39 +02:00
r . Managed . CreateBeforeDestroySet = true
2018-02-03 02:22:25 +01:00
}
if attr , exists := lcContent . Attributes [ "prevent_destroy" ] ; exists {
2018-04-06 20:07:39 +02:00
valDiags := gohcl . DecodeExpression ( attr . Expr , nil , & r . Managed . PreventDestroy )
2018-02-03 02:22:25 +01:00
diags = append ( diags , valDiags ... )
2018-04-06 20:07:39 +02:00
r . Managed . PreventDestroySet = true
2018-02-03 02:22:25 +01:00
}
if attr , exists := lcContent . Attributes [ "ignore_changes" ] ; exists {
2018-02-15 20:25:06 +01:00
// ignore_changes can either be a list of relative traversals
// or it can be just the keyword "all" to ignore changes to this
// resource entirely.
// ignore_changes = [ami, instance_type]
// ignore_changes = all
// We also allow two legacy forms for compatibility with earlier
// versions:
// ignore_changes = ["ami", "instance_type"]
// ignore_changes = ["*"]
kw := hcl . ExprAsKeyword ( attr . Expr )
switch {
case kw == "all" :
2018-04-06 20:07:39 +02:00
r . Managed . IgnoreAllChanges = true
2018-02-15 20:25:06 +01:00
default :
exprs , listDiags := hcl . ExprList ( attr . Expr )
diags = append ( diags , listDiags ... )
var ignoreAllRange hcl . Range
for _ , expr := range exprs {
// our expr might be the literal string "*", which
// we accept as a deprecated way of saying "all".
if shimIsIgnoreChangesStar ( expr ) {
2018-04-06 20:07:39 +02:00
r . Managed . IgnoreAllChanges = true
2018-02-15 20:25:06 +01:00
ignoreAllRange = expr . Range ( )
diags = append ( diags , & hcl . Diagnostic {
2021-02-19 16:19:27 +01:00
Severity : hcl . DiagError ,
Summary : "Invalid ignore_changes wildcard" ,
Detail : "The [\"*\"] form of ignore_changes wildcard is was deprecated and is now invalid. Use \"ignore_changes = all\" to ignore changes to all attributes." ,
2018-02-15 20:25:06 +01:00
Subject : attr . Expr . Range ( ) . Ptr ( ) ,
} )
continue
}
expr , shimDiags := shimTraversalInString ( expr , false )
diags = append ( diags , shimDiags ... )
traversal , travDiags := hcl . RelTraversalForExpr ( expr )
diags = append ( diags , travDiags ... )
if len ( traversal ) != 0 {
2018-04-06 20:07:39 +02:00
r . Managed . IgnoreChanges = append ( r . Managed . IgnoreChanges , traversal )
2018-02-15 20:25:06 +01:00
}
}
2018-02-15 19:17:36 +01:00
2018-04-06 20:07:39 +02:00
if r . Managed . IgnoreAllChanges && len ( r . Managed . IgnoreChanges ) != 0 {
2018-02-15 20:25:06 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid ignore_changes ruleset" ,
Detail : "Cannot mix wildcard string \"*\" with non-wildcard references." ,
Subject : & ignoreAllRange ,
Context : attr . Expr . Range ( ) . Ptr ( ) ,
} )
2018-02-03 02:22:25 +01:00
}
2018-02-15 20:25:06 +01:00
2018-02-03 02:22:25 +01:00
}
2018-02-15 20:25:06 +01:00
2018-02-03 02:22:25 +01:00
}
2020-11-20 23:56:21 +01:00
for _ , block := range lcContent . Blocks {
switch block . Type {
case "precondition" , "postcondition" :
cr , moreDiags := decodeCheckRuleBlock ( block , override )
diags = append ( diags , moreDiags ... )
2022-02-02 22:08:07 +01:00
moreDiags = cr . validateSelfReferences ( block . Type , r . Addr ( ) )
diags = append ( diags , moreDiags ... )
2020-11-20 23:56:21 +01:00
switch block . Type {
case "precondition" :
r . Preconditions = append ( r . Preconditions , cr )
case "postcondition" :
r . Postconditions = append ( r . Postconditions , cr )
}
default :
// The cases above should be exhaustive for all block types
// defined in the lifecycle schema, so this shouldn't happen.
panic ( fmt . Sprintf ( "unexpected lifecycle sub-block type %q" , block . Type ) )
}
}
2018-02-03 02:22:25 +01:00
case "connection" :
if seenConnection != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate connection block" ,
Detail : fmt . Sprintf ( "This resource already has a connection block at %s." , seenConnection . DefRange ) ,
Subject : & block . DefRange ,
} )
continue
}
seenConnection = block
2018-05-29 20:58:28 +02:00
r . Managed . Connection = & Connection {
Config : block . Body ,
DeclRange : block . DefRange ,
}
2018-02-03 02:22:25 +01:00
case "provisioner" :
pv , pvDiags := decodeProvisionerBlock ( block )
diags = append ( diags , pvDiags ... )
if pv != nil {
2018-04-06 20:07:39 +02:00
r . Managed . Provisioners = append ( r . Managed . Provisioners , pv )
2018-02-03 02:22:25 +01:00
}
2021-05-15 01:09:51 +02:00
case "_" :
if seenEscapeBlock != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate escaping block" ,
Detail : fmt . Sprintf (
"The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each resource block can have only one such block. The first escaping block was at %s." ,
seenEscapeBlock . DefRange ,
) ,
Subject : & block . DefRange ,
} )
continue
}
seenEscapeBlock = block
// When there's an escaping block its content merges with the
// existing config we extracted earlier, so later decoding
// will see a blend of both.
r . Config = hcl . MergeBodies ( [ ] hcl . Body { r . Config , block . Body } )
2018-02-03 02:22:25 +01:00
default :
2018-11-20 20:53:45 +01:00
// Any other block types are ones we've reserved for future use,
// so they get a generic message.
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved block type name in resource 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
}
}
2019-12-03 19:23:07 +01:00
// Now we can validate the connection block references if there are any destroy provisioners.
// TODO: should we eliminate standalone connection blocks?
if r . Managed . Connection != nil {
for _ , p := range r . Managed . Provisioners {
if p . When == ProvisionerWhenDestroy {
diags = append ( diags , onlySelfRefs ( r . Managed . Connection . Config ) ... )
break
}
}
}
2018-02-03 02:22:25 +01:00
return r , diags
2018-02-02 05:33:06 +01:00
}
2020-11-20 23:56:21 +01:00
func decodeDataBlock ( block * hcl . Block , override bool ) ( * Resource , hcl . Diagnostics ) {
2020-07-02 16:32:57 +02:00
var diags hcl . Diagnostics
2018-04-06 20:07:39 +02:00
r := & Resource {
Mode : addrs . DataResourceMode ,
2018-02-03 02:22:25 +01:00
Type : block . Labels [ 0 ] ,
Name : block . Labels [ 1 ] ,
DeclRange : block . DefRange ,
TypeRange : block . LabelRanges [ 0 ] ,
}
2018-02-02 05:33:06 +01:00
2020-07-02 16:32:57 +02:00
content , remain , moreDiags := block . Body . PartialContent ( dataBlockSchema )
diags = append ( diags , moreDiags ... )
2018-02-03 02:22:25 +01:00
r . Config = remain
2018-02-02 05:33:06 +01:00
2018-02-03 02:22:25 +01:00
if ! hclsyntax . ValidIdentifier ( r . Type ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid data source name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ] ,
} )
}
if ! hclsyntax . ValidIdentifier ( r . Name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid data resource name" ,
Detail : badIdentifierDetail ,
2018-03-01 21:18:07 +01:00
Subject : & block . LabelRanges [ 1 ] ,
2018-02-03 02:22:25 +01:00
} )
}
2018-02-02 05:33:06 +01:00
2018-02-03 02:22:25 +01:00
if attr , exists := content . Attributes [ "count" ] ; exists {
r . Count = attr . Expr
}
if attr , exists := content . Attributes [ "for_each" ] ; exists {
2018-11-20 20:53:45 +01:00
r . ForEach = attr . Expr
2019-07-25 17:51:55 +02:00
// Cannot have count and for_each on the same data block
if r . Count != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : ` Invalid combination of "count" and "for_each" ` ,
Detail : ` The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created. ` ,
Subject : & attr . NameRange ,
} )
}
2018-02-03 02:22:25 +01:00
}
if attr , exists := content . Attributes [ "provider" ] ; exists {
var providerDiags hcl . Diagnostics
2018-04-24 19:06:51 +02:00
r . ProviderConfigRef , providerDiags = decodeProviderConfigRef ( attr . Expr , "provider" )
2018-02-03 02:22:25 +01:00
diags = append ( diags , providerDiags ... )
}
if attr , exists := content . Attributes [ "depends_on" ] ; exists {
deps , depsDiags := decodeDependsOn ( attr )
diags = append ( diags , depsDiags ... )
r . DependsOn = append ( r . DependsOn , deps ... )
}
2021-05-15 01:09:51 +02:00
var seenEscapeBlock * hcl . Block
2020-11-20 23:56:21 +01:00
var seenLifecycle * hcl . Block
2018-02-03 02:22:25 +01:00
for _ , block := range content . Blocks {
2018-11-20 20:53:45 +01:00
switch block . Type {
2021-05-15 01:09:51 +02:00
case "_" :
if seenEscapeBlock != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate escaping block" ,
Detail : fmt . Sprintf (
"The special block type \"_\" can be used to force particular arguments to be interpreted as resource-type-specific rather than as meta-arguments, but each data block can have only one such block. The first escaping block was at %s." ,
seenEscapeBlock . DefRange ,
) ,
Subject : & block . DefRange ,
} )
continue
}
seenEscapeBlock = block
// When there's an escaping block its content merges with the
// existing config we extracted earlier, so later decoding
// will see a blend of both.
r . Config = hcl . MergeBodies ( [ ] hcl . Body { r . Config , block . Body } )
2018-11-20 20:53:45 +01:00
case "lifecycle" :
2020-11-20 23:56:21 +01:00
if seenLifecycle != nil {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Duplicate lifecycle block" ,
Detail : fmt . Sprintf ( "This resource already has a lifecycle block at %s." , seenLifecycle . DefRange ) ,
Subject : block . DefRange . Ptr ( ) ,
} )
continue
}
seenLifecycle = block
lcContent , lcDiags := block . Body . Content ( resourceLifecycleBlockSchema )
diags = append ( diags , lcDiags ... )
// All of the attributes defined for resource lifecycle are for
// managed resources only, so we can emit a common error message
// for any given attributes that HCL accepted.
for name , attr := range lcContent . Attributes {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid data resource lifecycle argument" ,
Detail : fmt . Sprintf ( "The lifecycle argument %q is defined only for managed resources (\"resource\" blocks), and is not valid for data resources." , name ) ,
Subject : attr . NameRange . Ptr ( ) ,
} )
}
for _ , block := range lcContent . Blocks {
switch block . Type {
case "precondition" , "postcondition" :
cr , moreDiags := decodeCheckRuleBlock ( block , override )
diags = append ( diags , moreDiags ... )
2022-02-02 22:08:07 +01:00
moreDiags = cr . validateSelfReferences ( block . Type , r . Addr ( ) )
diags = append ( diags , moreDiags ... )
2020-11-20 23:56:21 +01:00
switch block . Type {
case "precondition" :
r . Preconditions = append ( r . Preconditions , cr )
case "postcondition" :
r . Postconditions = append ( r . Postconditions , cr )
}
default :
// The cases above should be exhaustive for all block types
// defined in the lifecycle schema, so this shouldn't happen.
panic ( fmt . Sprintf ( "unexpected lifecycle sub-block type %q" , block . Type ) )
}
}
2021-05-15 01:09:51 +02:00
2018-11-20 20:53:45 +01:00
default :
2020-11-20 23:56:21 +01:00
// Any other block types are ones we're reserving for future use,
// but don't have any defined meaning today.
2018-11-20 20:53:45 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reserved block type name in data block" ,
Detail : fmt . Sprintf ( "The block type name %q is reserved for use by Terraform in a future version." , block . Type ) ,
2020-11-20 23:56:21 +01:00
Subject : block . TypeRange . Ptr ( ) ,
2018-11-20 20:53:45 +01:00
} )
}
2018-02-03 02:22:25 +01:00
}
return r , diags
2018-02-02 05:33:06 +01:00
}
2018-02-03 02:22:25 +01:00
type ProviderConfigRef struct {
Name string
NameRange hcl . Range
Alias string
AliasRange * hcl . Range // nil if alias not set
2021-04-16 18:37:50 +02:00
// TODO: this may not be set in some cases, so it is not yet suitable for
// use outside of this package. We currently only use it for internal
// validation, but once we verify that this can be set in all cases, we can
// export this so providers don't need to be re-resolved.
// This same field is also added to the Provider struct.
providerType addrs . Provider
2018-02-03 02:22:25 +01:00
}
2018-02-02 05:33:06 +01:00
2018-04-24 19:06:51 +02:00
func decodeProviderConfigRef ( expr hcl . Expression , argName string ) ( * ProviderConfigRef , hcl . Diagnostics ) {
2018-02-03 02:22:25 +01:00
var diags hcl . Diagnostics
2018-02-15 19:17:36 +01:00
2018-04-24 19:06:51 +02:00
var shimDiags hcl . Diagnostics
expr , shimDiags = shimTraversalInString ( expr , false )
2018-02-15 19:17:36 +01:00
diags = append ( diags , shimDiags ... )
traversal , travDiags := hcl . AbsTraversalForExpr ( expr )
2018-02-02 05:33:06 +01:00
2018-02-03 02:22:25 +01:00
// AbsTraversalForExpr produces only generic errors, so we'll discard
// the errors given and produce our own with extra context. If we didn't
// get any errors then we might still have warnings, though.
if ! travDiags . HasErrors ( ) {
diags = append ( diags , travDiags ... )
}
2018-02-02 05:33:06 +01:00
2018-05-05 04:25:16 +02:00
if len ( traversal ) < 1 || len ( traversal ) > 2 {
2018-02-03 02:22:25 +01:00
// A provider reference was given as a string literal in the legacy
// configuration language and there are lots of examples out there
// showing that usage, so we'll sniff for that situation here and
// produce a specialized error message for it to help users find
// the new correct form.
2018-04-24 19:06:51 +02:00
if exprIsNativeQuotedString ( expr ) {
2018-02-03 02:22:25 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration reference" ,
Detail : "A provider configuration reference must not be given in quotes." ,
2018-02-15 19:17:36 +01:00
Subject : expr . Range ( ) . Ptr ( ) ,
2018-02-03 02:22:25 +01:00
} )
return nil , diags
}
2018-02-02 05:33:06 +01:00
2018-02-03 02:22:25 +01:00
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration reference" ,
2018-04-24 19:06:51 +02:00
Detail : fmt . Sprintf ( "The %s argument requires a provider type name, optionally followed by a period and then a configuration alias." , argName ) ,
2018-02-15 19:17:36 +01:00
Subject : expr . Range ( ) . Ptr ( ) ,
2018-02-03 02:22:25 +01:00
} )
return nil , diags
}
2018-02-02 05:33:06 +01:00
2020-05-14 15:00:58 +02:00
// verify that the provider local name is normalized
name := traversal . RootName ( )
nameDiags := checkProviderNameNormalized ( name , traversal [ 0 ] . SourceRange ( ) )
diags = append ( diags , nameDiags ... )
2020-06-26 15:45:02 +02:00
if diags . HasErrors ( ) {
return nil , diags
}
2020-05-14 15:00:58 +02:00
2018-02-03 02:22:25 +01:00
ret := & ProviderConfigRef {
2020-05-14 15:00:58 +02:00
Name : name ,
2018-02-03 02:22:25 +01:00
NameRange : traversal [ 0 ] . SourceRange ( ) ,
}
if len ( traversal ) > 1 {
aliasStep , ok := traversal [ 1 ] . ( hcl . TraverseAttr )
if ! ok {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid provider configuration reference" ,
Detail : "Provider name must either stand alone or be followed by a period and then a configuration alias." ,
Subject : traversal [ 1 ] . SourceRange ( ) . Ptr ( ) ,
} )
return ret , diags
}
ret . Alias = aliasStep . Name
ret . AliasRange = aliasStep . SourceRange ( ) . Ptr ( )
}
return ret , diags
}
2018-04-30 19:15:27 +02:00
// Addr returns the provider config address corresponding to the receiving
// config reference.
//
// This is a trivial conversion, essentially just discarding the source
// location information and keeping just the addressing information.
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
func ( r * ProviderConfigRef ) Addr ( ) addrs . LocalProviderConfig {
return addrs . LocalProviderConfig {
LocalName : r . Name ,
Alias : r . Alias ,
2018-04-30 19:15:27 +02:00
}
}
2018-03-28 02:22:51 +02:00
func ( r * ProviderConfigRef ) String ( ) string {
if r == nil {
return "<nil>"
}
if r . Alias != "" {
return fmt . Sprintf ( "%s.%s" , r . Name , r . Alias )
}
return r . Name
}
2018-02-03 02:22:25 +01:00
var commonResourceAttributes = [ ] hcl . AttributeSchema {
{
Name : "count" ,
} ,
{
Name : "for_each" ,
} ,
{
Name : "provider" ,
} ,
{
Name : "depends_on" ,
} ,
}
var resourceBlockSchema = & hcl . BodySchema {
Attributes : commonResourceAttributes ,
Blocks : [ ] hcl . BlockHeaderSchema {
2018-11-20 20:53:45 +01:00
{ Type : "locals" } , // reserved for future use
{ Type : "lifecycle" } ,
{ Type : "connection" } ,
{ Type : "provisioner" , LabelNames : [ ] string { "type" } } ,
2021-05-15 01:09:51 +02:00
{ Type : "_" } , // meta-argument escaping block
2018-02-03 02:22:25 +01:00
} ,
}
var dataBlockSchema = & hcl . BodySchema {
Attributes : commonResourceAttributes ,
Blocks : [ ] hcl . BlockHeaderSchema {
2020-11-20 23:56:21 +01:00
{ Type : "lifecycle" } ,
{ Type : "locals" } , // reserved for future use
{ Type : "_" } , // meta-argument escaping block
2018-02-03 02:22:25 +01:00
} ,
}
var resourceLifecycleBlockSchema = & hcl . BodySchema {
2020-11-20 23:56:21 +01:00
// We tell HCL that these elements are all valid for both "resource"
// and "data" lifecycle blocks, but the rules are actually more restrictive
// than that. We deal with that after decoding so that we can return
// more specific error messages than HCL would typically return itself.
2018-02-03 02:22:25 +01:00
Attributes : [ ] hcl . AttributeSchema {
{
Name : "create_before_destroy" ,
} ,
{
Name : "prevent_destroy" ,
} ,
{
Name : "ignore_changes" ,
} ,
} ,
2020-11-20 23:56:21 +01:00
Blocks : [ ] hcl . BlockHeaderSchema {
{ Type : "precondition" } ,
{ Type : "postcondition" } ,
} ,
2018-02-03 02:22:25 +01:00
}