mirror of
https://github.com/hashicorp/terraform.git
synced 2026-06-09 00:42:48 -04:00
add ephemeral resources to configs
This commit is contained in:
parent
95124dbe5a
commit
f08d610702
5 changed files with 219 additions and 4 deletions
|
|
@ -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…
Reference in a new issue