-
Notifications
You must be signed in to change notification settings - Fork 10k
Add ephemeral resources to the Terraform language #35728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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) | ||||||
} | ||||||
Comment on lines
+475
to
+487
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how much complexity/effort will be required to make conditions work for the new resource type but we could also just reserve these in the language initially. You are more likely to know the answer though, so I'm assuming if you are proposing to keep it like this, it's not too complex. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aside from optimistically hoping that the implementation isn't too difficult (these are still just a resource type after all), they also seem useful when the values are not easily extracted by the user for other debugging purposes. If there is any problem implementing later steps we can always reserve the block names to prevent their use. These first commits are all preliminary anyway, there will probably be some changes as all the pieces are integrated. |
||||||
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"}, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{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 | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
ephemeral "test_resource" "test" { | ||
lifecycle { | ||
create_before_destroy = true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ephemeral "test resource" "nope" { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this intentional or was it just to temporarily aid debugging in test environment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to import the internal logging package somewhere to initialize the default logger, so any package tested in isolation needs this to not print all logs during tests.