core ephemeral node implementation

Implement the core nodes for ephemeral resources. This constitutes
what's needed for execution, but does not yet include all the necessary
graph transformations.
pull/35764/head
James Bardin 2 years ago
parent dfdf27c7bc
commit 0d3530c815

@ -390,16 +390,10 @@ func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotN
}
}
// writeResourceState ensures that a suitable resource-level state record is
// present in the state, if that's required for the "each mode" of that
// resource.
//
// This is important primarily for the situation where count = 0, since this
// eval is the only change we get to set the resource "each mode" to list
// in that case, allowing expression evaluation to see it as a zero-element list
// rather than as not set at all.
func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.AbsResource) (diags tfdiags.Diagnostics) {
state := ctx.State()
// recordResourceData records some metadata for the resource as a whole in
// various locations. This currently includes adding resource expansion info to
// the instance expander, and recording the provider used in the state.
func (n *NodeAbstractResource) recordResourceData(ctx EvalContext, addr addrs.AbsResource) (diags tfdiags.Diagnostics) {
// We'll record our expansion decision in the shared "expander" object
// so that later operations (i.e. DynamicExpand and expression evaluation)
@ -422,7 +416,6 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab
return diags
}
state.SetResourceProvider(addr, n.ResolvedProvider)
if count >= 0 {
expander.SetResourceCount(addr.Module, n.Addr.Resource, count)
} else {
@ -439,7 +432,6 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab
// This method takes care of all of the business logic of updating this
// while ensuring that any existing instances are preserved, etc.
state.SetResourceProvider(addr, n.ResolvedProvider)
if known {
expander.SetResourceForEach(addr.Module, n.Addr.Resource, forEach)
} else {
@ -447,10 +439,17 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab
}
default:
state.SetResourceProvider(addr, n.ResolvedProvider)
expander.SetResourceSingle(addr.Module, n.Addr.Resource)
}
if addr.Resource.Mode == addrs.EphemeralResourceMode {
// ephemeral resources are not included in the state
return diags
}
state := ctx.State()
state.SetResourceProvider(addr, n.ResolvedProvider)
return diags
}

@ -54,7 +54,7 @@ func (n *nodeExpandApplyableResource) Execute(globalCtx EvalContext, op walkOper
moduleInstances := expander.ExpandModule(n.Addr.Module, false)
for _, module := range moduleInstances {
moduleCtx := evalContextForModuleInstance(globalCtx, module)
diags = diags.Append(n.writeResourceState(moduleCtx, n.Addr.Resource.Absolute(module)))
diags = diags.Append(n.recordResourceData(moduleCtx, n.Addr.Resource.Absolute(module)))
}
return diags

@ -0,0 +1,205 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package terraform
import (
"context"
"fmt"
"log"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans/objchange"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/resources/ephemeral"
"github.com/hashicorp/terraform/internal/tfdiags"
)
type ephemeralResourceInput struct {
addr addrs.AbsResourceInstance
config *configs.Resource
providerConfig addrs.AbsProviderConfig
}
// ephemeralResourceOpen implements the "open" step of the ephemeral resource
// instance lifecycle, which behaves the same way in both the plan and apply
// walks.
func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) tfdiags.Diagnostics {
log.Printf("[TRACE] ephemeralResourceOpen: opening %s", inp.addr)
var diags tfdiags.Diagnostics
provider, providerSchema, err := getProvider(ctx, inp.providerConfig)
if err != nil {
diags = diags.Append(err)
return diags
}
config := inp.config
schema, _ := providerSchema.SchemaForResourceAddr(inp.addr.ContainingResource().Resource)
if schema == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(
fmt.Errorf("provider %q does not support ephemeral resource %q",
inp.providerConfig, inp.addr.ContainingResource().Resource.Type,
),
)
return diags
}
resources := ctx.EphemeralResources()
allInsts := ctx.InstanceExpander()
keyData := allInsts.GetResourceInstanceRepetitionData(inp.addr)
checkDiags := evalCheckRules(
addrs.ResourcePrecondition,
config.Preconditions,
ctx, inp.addr, keyData,
tfdiags.Error,
)
diags = diags.Append(checkDiags)
if diags.HasErrors() {
return diags // failed preconditions prevent further evaluation
}
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData)
diags = diags.Append(configDiags)
if diags.HasErrors() {
return diags
}
unmarkedConfigVal, configMarks := configVal.UnmarkDeepWithPaths()
diags = diags.Append(provider.ValidateEphemeralResourceConfig(providers.ValidateEphemeralResourceConfigRequest{
TypeName: inp.addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
}))
if diags.HasErrors() {
return diags
}
resp := provider.OpenEphemeralResource(providers.OpenEphemeralResourceRequest{
TypeName: inp.addr.ContainingResource().Resource.Type,
Config: unmarkedConfigVal,
})
if resp.Deferred != nil {
// FIXME: Actually implement this.
diags = diags.Append(fmt.Errorf("we don't support deferral of ephemeral resource instances yet"))
}
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, inp.addr.String()))
if diags.HasErrors() {
return diags
}
resultVal := resp.Result.MarkWithPaths(configMarks)
errs := objchange.AssertPlanValid(schema, cty.NullVal(schema.ImpliedType()), configVal, resultVal)
for _, err := range errs {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Provider produced invalid ephemeral resource instance",
fmt.Sprintf(
"The provider for %s produced an inconsistent result: %s.",
inp.addr.Resource.Resource.Type,
tfdiags.FormatError(err),
),
nil,
)).InConfigBody(config.Config, inp.addr.String())
}
if diags.HasErrors() {
return diags
}
// We are going to wholesale mark the entire resource as ephemeral. This
// simplifies the model as any references to ephemeral resources can be
// considered as such. Any input values that don't need to be ephemeral can
// be referenced directly.
resultVal = resultVal.Mark(marks.Ephemeral)
impl := &ephemeralResourceInstImpl{
addr: inp.addr,
provider: provider,
internal: resp.InternalContext,
}
// TODO: What can we use as a signal to cancel the context we're passing in
// here, so that the object will stop renewing things when we start shutting
// down?
// TODO: The context Stopped channel should probably be updated
// finally to a Context
resources.RegisterInstance(context.TODO(), inp.addr, ephemeral.ResourceInstanceRegistration{
Value: resultVal,
ConfigBody: config.Config,
Impl: impl,
FirstRenewal: resp.Renew,
})
return diags
}
// nodeEphemeralResourceClose is the node type for closing the previously-opened
// instances of a particular ephemeral resource.
//
// Although ephemeral resource instances will always all get closed once a
// graph walk has completed anyway, the inclusion of explicit nodes for this
// allows closing ephemeral resource instances more promptly after all work
// that uses them has been completed, rather than always just waiting until
// the end of the graph walk.
//
// This is scoped to config-level resources rather than dynamic resource
// instances as a concession to allow using the same node type in both the plan
// and apply graphs, where the former only deals in whole resources while the
// latter contains individual instances.
type nodeEphemeralResourceClose struct {
addr addrs.ConfigResource
}
var _ GraphNodeExecutable = (*nodeEphemeralResourceClose)(nil)
var _ GraphNodeModulePath = (*nodeEphemeralResourceClose)(nil)
func (n *nodeEphemeralResourceClose) Name() string {
return n.addr.String() + " (close)"
}
// ModulePath implements GraphNodeModulePath.
func (n *nodeEphemeralResourceClose) ModulePath() addrs.Module {
return n.addr.Module
}
// Execute implements GraphNodeExecutable.
func (n *nodeEphemeralResourceClose) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
log.Printf("[TRACE] nodeEphemeralResourceClose: closing all instances of %s", n.addr)
resources := ctx.EphemeralResources()
return resources.CloseInstances(context.TODO(), n.addr)
}
// ephemeralResourceInstImpl implements ephemeral.ResourceInstance as an
// adapter to the relevant provider API calls.
type ephemeralResourceInstImpl struct {
addr addrs.AbsResourceInstance
provider providers.Interface
internal []byte
}
var _ ephemeral.ResourceInstance = (*ephemeralResourceInstImpl)(nil)
// Close implements ephemeral.ResourceInstance.
func (impl *ephemeralResourceInstImpl) Close(ctx context.Context) tfdiags.Diagnostics {
log.Printf("[TRACE] ephemeralResourceInstImpl: closing %s", impl.addr)
resp := impl.provider.CloseEphemeralResource(providers.CloseEphemeralResourceRequest{
TypeName: impl.addr.Resource.Resource.Type,
InternalContext: impl.internal,
})
return resp.Diagnostics
}
// Renew implements ephemeral.ResourceInstance.
func (impl *ephemeralResourceInstImpl) Renew(ctx context.Context, req providers.EphemeralRenew) (nextRenew *providers.EphemeralRenew, diags tfdiags.Diagnostics) {
log.Printf("[TRACE] ephemeralResourceInstImpl: renewing %s", impl.addr)
resp := impl.provider.RenewEphemeralResource(providers.RenewEphemeralResourceRequest{
TypeName: impl.addr.Resource.Resource.Type,
InternalContext: req.InternalContext,
})
return resp.RenewAgain, resp.Diagnostics
}

@ -216,7 +216,7 @@ func (n *nodeExpandPlannableResource) expandKnownModule(globalCtx EvalContext, r
moduleCtx := evalContextForModuleInstance(globalCtx, resAddr.Module)
moreDiags := n.writeResourceState(moduleCtx, resAddr)
moreDiags := n.recordResourceData(moduleCtx, resAddr)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, nil, nil, diags

@ -305,14 +305,18 @@ func (n *nodeExpandPlannableResource) validateExpandedImportTargets(expandedImpo
return diags
}
func (n *nodeExpandPlannableResource) dynamicExpand(ctx EvalContext, moduleInstances []addrs.ModuleInstance, imports addrs.Map[addrs.AbsResourceInstance, cty.Value]) (*Graph, tfdiags.Diagnostics) {
var g Graph
var diags tfdiags.Diagnostics
func (n *nodeExpandPlannableResource) findOrphans(ctx EvalContext, moduleInstances []addrs.ModuleInstance) []*states.Resource {
if n.Addr.Resource.Mode == addrs.EphemeralResourceMode {
// ephemeral resources don't exist in state
return nil
}
var orphans []*states.Resource
// Lock the state while we inspect it
state := ctx.State().Lock()
sMgr := ctx.State()
state := sMgr.Lock()
var orphans []*states.Resource
for _, res := range state.Resources(n.Addr) {
found := false
for _, m := range moduleInstances {
@ -327,11 +331,16 @@ func (n *nodeExpandPlannableResource) dynamicExpand(ctx EvalContext, moduleInsta
orphans = append(orphans, res)
}
}
sMgr.Unlock()
return orphans
}
func (n *nodeExpandPlannableResource) dynamicExpand(ctx EvalContext, moduleInstances []addrs.ModuleInstance, imports addrs.Map[addrs.AbsResourceInstance, cty.Value]) (*Graph, tfdiags.Diagnostics) {
var g Graph
var diags tfdiags.Diagnostics
// We'll no longer use the state directly here, and the other functions
// we'll call below may use it so we'll release the lock.
state = nil
ctx.State().Unlock()
orphans := n.findOrphans(ctx, moduleInstances)
for _, res := range orphans {
for key := range res.Instances {
@ -402,7 +411,7 @@ func (n *nodeExpandPlannableResource) expandResourceInstances(globalCtx EvalCont
// writeResourceState is responsible for informing the expander of what
// repetition mode this resource has, which allows expander.ExpandResource
// to work below.
moreDiags := n.writeResourceState(moduleCtx, resAddr)
moreDiags := n.recordResourceData(moduleCtx, resAddr)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags

@ -76,6 +76,8 @@ func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperatio
return n.managedResourceExecute(ctx)
case addrs.DataResourceMode:
return n.dataResourceExecute(ctx)
case addrs.EphemeralResourceMode:
return n.ephemeralResourceExecute(ctx)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
@ -152,6 +154,14 @@ func (n *NodePlannableResourceInstance) dataResourceExecute(ctx EvalContext) (di
return diags
}
func (n *NodePlannableResourceInstance) ephemeralResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
return ephemeralResourceOpen(ctx, ephemeralResourceInput{
addr: n.Addr,
config: n.Config,
providerConfig: n.ResolvedProvider,
})
}
func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
config := n.Config
addr := n.ResourceInstanceAddr()

@ -440,6 +440,9 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
resp := provider.ValidateDataResourceConfig(req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.EphemeralResourceMode:
// TODO!!
panic("not implemented")
}
return diags

Loading…
Cancel
Save