add ephemeral resources to configs

pull/35728/head
James Bardin 2 years ago
parent 95124dbe5a
commit f08d610702

@ -458,6 +458,7 @@ func (c *Config) addProviderRequirements(reqs providerreqs.Requirements, recurse
}
reqs[fqn] = nil
}
for _, rc := range c.Module.DataResources {
fqn := rc.Provider
if _, exists := reqs[fqn]; exists {
@ -467,6 +468,15 @@ func (c *Config) addProviderRequirements(reqs providerreqs.Requirements, recurse
reqs[fqn] = nil
}
for _, rc := range c.Module.EphemeralResources {
fqn := rc.Provider
if _, exists := reqs[fqn]; exists {
// Explicit dependency already present
continue
}
reqs[fqn] = nil
}
// Import blocks that are generating config may have a custom provider
// meta-argument. Like the provider meta-argument used in resource blocks,
// we use this opportunity to load any implicit providers.

@ -18,6 +18,8 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
_ "github.com/hashicorp/terraform/internal/logging"
)
func TestConfigProviderTypes(t *testing.T) {

@ -46,8 +46,9 @@ type Module struct {
ModuleCalls map[string]*ModuleCall
ManagedResources map[string]*Resource
DataResources map[string]*Resource
ManagedResources map[string]*Resource
DataResources map[string]*Resource
EphemeralResources map[string]*Resource
Moved []*Moved
Removed []*Removed
@ -86,8 +87,9 @@ type File struct {
ModuleCalls []*ModuleCall
ManagedResources []*Resource
DataResources []*Resource
ManagedResources []*Resource
DataResources []*Resource
EphemeralResources []*Resource
Moved []*Moved
Removed []*Removed
@ -124,6 +126,7 @@ func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) {
Outputs: map[string]*Output{},
ModuleCalls: map[string]*ModuleCall{},
ManagedResources: map[string]*Resource{},
EphemeralResources: map[string]*Resource{},
DataResources: map[string]*Resource{},
Checks: map[string]*Check{},
ProviderMetas: map[addrs.Provider]*ProviderMeta{},
@ -192,6 +195,8 @@ func (m *Module) ResourceByAddr(addr addrs.Resource) *Resource {
return m.ManagedResources[key]
case addrs.DataResourceMode:
return m.DataResources[key]
case addrs.EphemeralResourceMode:
return m.EphemeralResources[key]
default:
return nil
}
@ -372,6 +377,35 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
m.DataResources[key] = r
}
for _, r := range file.EphemeralResources {
key := r.moduleUniqueKey()
if existing, exists := m.EphemeralResources[key]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicate ephemeral %q configuration", existing.Type),
Detail: fmt.Sprintf("A %s ephemeral resource named %q was already declared at %s. Resource names must be unique per type in each module.", existing.Type, existing.Name, existing.DeclRange),
Subject: &r.DeclRange,
})
continue
}
m.EphemeralResources[key] = r
// set the provider FQN for the resource
if r.ProviderConfigRef != nil {
r.Provider = m.ProviderForLocalConfig(r.ProviderConfigAddr())
} else {
// an invalid resource name (for e.g. "null resource" instead of
// "null_resource") can cause a panic down the line in addrs:
// https://github.com/hashicorp/terraform/issues/25560
implied, err := addrs.ParseProviderPart(r.Addr().ImpliedProvider())
if err == nil {
r.Provider = m.ImpliedProviderForUnqualifiedType(implied)
}
// We don't return a diagnostic because the invalid resource name
// will already have been caught.
}
}
for _, c := range file.Checks {
if c.DataResource != nil {
key := c.DataResource.moduleUniqueKey()

@ -195,6 +195,13 @@ func parseConfigFile(body hcl.Body, diags hcl.Diagnostics, override, allowExperi
file.DataResources = append(file.DataResources, cfg)
}
case "ephemeral":
cfg, cfgDiags := decodeEphemeralBlock(block, override)
diags = append(diags, cfgDiags...)
if cfg != nil {
file.EphemeralResources = append(file.EphemeralResources, cfg)
}
case "moved":
cfg, cfgDiags := decodeMovedBlock(block)
diags = append(diags, cfgDiags...)
@ -310,6 +317,10 @@ var configFileSchema = &hcl.BodySchema{
Type: "data",
LabelNames: []string{"type", "name"},
},
{
Type: "ephemeral",
LabelNames: []string{"type", "name"},
},
{
Type: "moved",
},

@ -358,6 +358,155 @@ func decodeResourceBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagno
return r, diags
}
func decodeEphemeralBlock(block *hcl.Block, override bool) (*Resource, hcl.Diagnostics) {
var diags hcl.Diagnostics
r := &Resource{
Mode: addrs.EphemeralResourceMode,
Type: block.Labels[0],
Name: block.Labels[1],
DeclRange: block.DefRange,
TypeRange: block.LabelRanges[0],
}
content, remain, moreDiags := block.Body.PartialContent(ephemeralBlockSchema)
diags = append(diags, moreDiags...)
r.Config = remain
if !hclsyntax.ValidIdentifier(r.Type) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid ephemeral resource type",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[0],
})
}
if !hclsyntax.ValidIdentifier(r.Name) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid ephemeral resource name",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[1],
})
}
if attr, exists := content.Attributes["count"]; exists {
r.Count = attr.Expr
}
if attr, exists := content.Attributes["for_each"]; exists {
r.ForEach = attr.Expr
// Cannot have count and for_each on the same ephemeral 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,
})
}
}
if attr, exists := content.Attributes["provider"]; exists {
var providerDiags hcl.Diagnostics
r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
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 seenEscapeBlock *hcl.Block
var seenLifecycle *hcl.Block
for _, block := range content.Blocks {
switch block.Type {
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})
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.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 ephemeral resource lifecycle argument",
Detail: fmt.Sprintf("The lifecycle argument %q is defined only for managed resources (\"resource\" blocks), and is not valid for ephemeral 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...)
moreDiags = cr.validateSelfReferences(block.Type, r.Addr())
diags = append(diags, moreDiags...)
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))
}
}
default:
// Any other block types are ones we're reserving for future use,
// but don't have any defined meaning today.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reserved block type name in ephemeral block",
Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type),
Subject: block.TypeRange.Ptr(),
})
}
}
return r, diags
}
func decodeDataBlock(block *hcl.Block, override, nested bool) (*Resource, hcl.Diagnostics) {
var diags hcl.Diagnostics
r := &Resource{
@ -783,6 +932,15 @@ var dataBlockSchema = &hcl.BodySchema{
},
}
var ephemeralBlockSchema = &hcl.BodySchema{
Attributes: commonResourceAttributes,
Blocks: []hcl.BlockHeaderSchema{
{Type: "lifecycle"},
{Type: "locals"}, // reserved for future use
{Type: "_"}, // meta-argument escaping block
},
}
var resourceLifecycleBlockSchema = &hcl.BodySchema{
// We tell HCL that these elements are all valid for both "resource"
// and "data" lifecycle blocks, but the rules are actually more restrictive

Loading…
Cancel
Save