ephemerals: allow root ephemeral outputs in stacks and test (#37813)

* ephemerals: allow root ephemeral outputs in stacks and test

* remember to set new opt for apply stage
pull/37821/head
Liam Cervante 6 months ago committed by GitHub
parent d4e8f17ded
commit 411f18e6ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'test: allow ephemeral outputs in root modules'
time: 2025-10-24T16:44:34.197847+02:00
custom:
Issue: "37813"

@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'stacks: allow ephemeral outputs in root modules'
time: 2025-10-24T16:44:48.264142+02:00
custom:
Issue: "37813"

@ -409,6 +409,9 @@ func TestTest_Runs(t *testing.T) {
expectedOut: []string{"3 passed, 0 failed."},
code: 0,
},
"ephemeral_output": {
code: 0,
},
"no-tests": {
code: 0,
},

@ -0,0 +1,8 @@
variable "foo" {
ephemeral = true
type = string
}
output "value" {
value = var.foo
ephemeral = true
}

@ -0,0 +1,5 @@
run "validate_ephemeral_input" {
variables {
foo = "whaaat"
}
}

@ -1039,6 +1039,9 @@ func (c *Config) EffectiveRequiredProviderConfigs() addrs.Map[addrs.RootProvider
for _, rc := range c.Module.Actions {
maybePutLocal(rc.ProviderConfigAddr(), false)
}
for _, rc := range c.Module.EphemeralResources {
maybePutLocal(rc.ProviderConfigAddr(), false)
}
for _, ic := range c.Module.Import {
if ic.ProviderConfigRef != nil {
maybePutLocal(addrs.LocalProviderConfig{

@ -154,8 +154,9 @@ func apply(tfCtx *terraform.Context, run *configs.TestRun, module *configs.Confi
}
applyOpts := &terraform.ApplyOpts{
SetVariables: ephemeralVariables,
ExternalProviders: providers,
SetVariables: ephemeralVariables,
ExternalProviders: providers,
AllowRootEphemeralOutputs: true,
}
waiter.update(tfCtx, progress, created)

@ -126,13 +126,14 @@ func (n *NodeStateCleanup) restore(ctx *EvalContext, file *configs.TestFile, run
setVariables, _, _ := FilterVariablesToModule(module, variables)
planOpts := &terraform.PlanOpts{
Mode: plans.NormalMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run, file, mocks),
ExternalProviders: providers,
SkipRefresh: true,
OverridePreventDestroy: true,
DeferralAllowed: ctx.deferralAllowed,
Mode: plans.NormalMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run, file, mocks),
ExternalProviders: providers,
SkipRefresh: true,
OverridePreventDestroy: true,
DeferralAllowed: ctx.deferralAllowed,
AllowRootEphemeralOutputs: true,
}
tfCtx, _ := terraform.NewContext(n.opts.ContextOpts)
@ -177,13 +178,14 @@ func (n *NodeStateCleanup) destroy(ctx *EvalContext, file *configs.TestFile, run
setVariables, _, _ := FilterVariablesToModule(module, variables)
planOpts := &terraform.PlanOpts{
Mode: plans.DestroyMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run, file, mocks),
ExternalProviders: providers,
SkipRefresh: true,
OverridePreventDestroy: true,
DeferralAllowed: ctx.deferralAllowed,
Mode: plans.DestroyMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run, file, mocks),
ExternalProviders: providers,
SkipRefresh: true,
OverridePreventDestroy: true,
DeferralAllowed: ctx.deferralAllowed,
AllowRootEphemeralOutputs: true,
}
tfCtx, _ := terraform.NewContext(n.opts.ContextOpts)

@ -193,7 +193,8 @@ func (n *NodeTestRun) testValidate(providers map[addrs.RootProviderConfig]provid
}
waiter.update(tfCtx, moduletest.Running, nil)
validateDiags := tfCtx.Validate(config, &terraform.ValidateOpts{
ExternalProviders: providers,
ExternalProviders: providers,
AllowRootEphemeralOutputs: true,
})
run.Diagnostics = run.Diagnostics.Append(validateDiags)
if validateDiags.HasErrors() {

@ -127,14 +127,15 @@ func plan(ctx *EvalContext, tfCtx *terraform.Context, file *configs.TestFile, ru
return plans.NormalMode
}
}(),
Targets: targets,
ForceReplace: replaces,
SkipRefresh: !run.Options.Refresh,
SetVariables: variables,
ExternalReferences: references,
ExternalProviders: providers,
Overrides: mocking.PackageOverrides(run, file, mocks),
DeferralAllowed: ctx.deferralAllowed,
Targets: targets,
ForceReplace: replaces,
SkipRefresh: !run.Options.Refresh,
SetVariables: variables,
ExternalReferences: references,
ExternalProviders: providers,
Overrides: mocking.PackageOverrides(run, file, mocks),
DeferralAllowed: ctx.deferralAllowed,
AllowRootEphemeralOutputs: true,
}
waiter.update(tfCtx, moduletest.Running, nil)

@ -2229,6 +2229,69 @@ After applying this plan, Terraform will no longer manage these objects. You wil
},
},
},
"ephemeral-module-outputs": {
path: "ephemeral-module-output",
cycles: []TestCycle{
{
wantPlannedChanges: []stackplan.PlannedChange{
&stackplan.PlannedChangeApplyable{
Applyable: true,
},
&stackplan.PlannedChangeComponentInstance{
Addr: mustAbsComponentInstance("component.ephemeral_in"),
PlanApplyable: false,
PlanComplete: true,
Action: plans.Create,
RequiredComponents: collections.NewSet(mustAbsComponent("component.ephemeral_out")),
PlannedInputValues: make(map[string]plans.DynamicValue),
PlannedOutputValues: make(map[string]cty.Value),
PlannedCheckResults: new(states.CheckResults),
PlanTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeComponentInstance{
Addr: mustAbsComponentInstance("component.ephemeral_out"),
PlanApplyable: false,
PlanComplete: true,
Action: plans.Create,
PlannedInputValues: make(map[string]plans.DynamicValue),
PlannedOutputValues: map[string]cty.Value{
"value": cty.DynamicVal, // ephemeral
},
PlannedCheckResults: new(states.CheckResults),
PlanTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeHeader{
TerraformVersion: version.SemVer,
},
&stackplan.PlannedChangePlannedTimestamp{
PlannedTimestamp: fakePlanTimestamp,
},
},
wantAppliedChanges: []stackstate.AppliedChange{
&stackstate.AppliedChangeComponentInstance{
ComponentAddr: mustAbsComponent("component.ephemeral_in"),
ComponentInstanceAddr: mustAbsComponentInstance("component.ephemeral_in"),
Dependencies: collections.NewSet[stackaddrs.AbsComponent](
mustAbsComponent("component.ephemeral_out"),
),
OutputValues: make(map[addrs.OutputValue]cty.Value),
InputVariables: map[addrs.InputVariable]cty.Value{
mustInputVariable("input"): cty.UnknownVal(cty.String), // ephemeral
},
},
&stackstate.AppliedChangeComponentInstance{
ComponentAddr: mustAbsComponent("component.ephemeral_out"),
ComponentInstanceAddr: mustAbsComponentInstance("component.ephemeral_out"),
Dependents: collections.NewSet[stackaddrs.AbsComponent](
mustAbsComponent("component.ephemeral_in"),
),
OutputValues: make(map[addrs.OutputValue]cty.Value),
InputVariables: make(map[addrs.InputVariable]cty.Value),
},
},
},
},
},
}
for name, tc := range tcs {

@ -378,7 +378,8 @@ func (c *ComponentConfig) checkValid(ctx context.Context, phase EvalPhase) tfdia
}()
diags = diags.Append(tfCtx.Validate(moduleTree, &terraform.ValidateOpts{
ExternalProviders: providerClients,
ExternalProviders: providerClients,
AllowRootEphemeralOutputs: true,
}))
return diags, nil
})

@ -171,6 +171,8 @@ func (c *ComponentInstance) PlanOpts(ctx context.Context, mode plans.Mode, skipR
ExternalProviders: providerClients,
ExternalDependencyDeferred: c.deferred,
DeferralAllowed: true,
AllowRootEphemeralOutputs: true,
// We want the same plantimestamp between all components and the stacks language
ForcePlanTimestamp: &plantimestamp,
}, nil

@ -196,7 +196,8 @@ func (r *RemovedComponentConfig) CheckValid(ctx context.Context, phase EvalPhase
}()
diags = diags.Append(tfCtx.Validate(moduleTree, &terraform.ValidateOpts{
ExternalProviders: providerClients,
ExternalProviders: providerClients,
AllowRootEphemeralOutputs: true,
}))
return diags, nil
})

@ -135,6 +135,7 @@ func (r *RemovedComponentInstance) ModuleTreePlan(ctx context.Context) (*plans.P
DeferralAllowed: true,
ExternalDependencyDeferred: deferred,
Forget: forget,
AllowRootEphemeralOutputs: true,
// We want the same plantimestamp between all components and the stacks language
ForcePlanTimestamp: &plantimestamp,

@ -0,0 +1,28 @@
required_providers {
testing = {
source = "hashicorp/testing"
version = "0.1.0"
}
}
provider "testing" "main" {}
component "ephemeral_out" {
source = "./ephemeral-output"
providers = {
testing = provider.testing.main
}
}
component "ephemeral_in" {
source = "./ephemeral-input"
providers = {
testing = provider.testing.main
}
inputs = {
input = component.ephemeral_out.value
}
}

@ -0,0 +1,7 @@
ephemeral "testing_resource" "resource" {}
output "value" {
value = ephemeral.testing_resource.resource.value
ephemeral = true
}

@ -28,6 +28,17 @@ var (
},
}
TestingEphemeralResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
Computed: true,
},
},
},
}
DeferredResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
@ -199,6 +210,11 @@ func NewProviderWithData(t *testing.T, store *ResourceStore) *MockProvider {
Body: WriteOnlyDataSourceSchema.Body,
},
},
EphemeralResourceTypes: map[string]providers.Schema{
"testing_resource": {
Body: TestingEphemeralResourceSchema.Body,
},
},
Functions: map[string]providers.FunctionDecl{
"echo": {
Parameters: []providers.FunctionParam{
@ -299,6 +315,13 @@ func NewProviderWithData(t *testing.T, store *ResourceStore) *MockProvider {
Result: request.Arguments[0],
}
},
OpenEphemeralResourceFn: func(request providers.OpenEphemeralResourceRequest) providers.OpenEphemeralResourceResponse {
return providers.OpenEphemeralResourceResponse{
Result: cty.ObjectVal(map[string]cty.Value{
"value": cty.StringVal("secret"),
}),
}
},
},
ResourceStore: store,
}

@ -39,6 +39,13 @@ type ApplyOpts struct {
// values that were declared as ephemeral, because all other input
// values must retain the values that were specified during planning.
SetVariables InputValues
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
// ApplyOpts creates an [ApplyOpts] with copies of all of the elements that
@ -52,7 +59,8 @@ type ApplyOpts struct {
// as in test cases.
func (po *PlanOpts) ApplyOpts() *ApplyOpts {
return &ApplyOpts{
ExternalProviders: po.ExternalProviders,
ExternalProviders: po.ExternalProviders,
AllowRootEphemeralOutputs: po.AllowRootEphemeralOutputs,
}
}
@ -292,6 +300,10 @@ func checkApplyTimeVariables(needed collections.Set[string], gotValues InputValu
func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, opts *ApplyOpts, validate bool) (*Graph, walkOperation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if opts == nil {
opts = new(ApplyOpts)
}
variables := InputValues{}
for name, dyVal := range plan.VariableValues {
val, err := dyVal.Decode(cty.DynamicPseudoType)
@ -316,10 +328,8 @@ func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, opts *App
// FIXME: We should check that all of these match declared variables and
// that all of them are declared as ephemeral, because all non-ephemeral
// variables are supposed to come exclusively from plan.VariableValues.
if opts != nil {
for n, vv := range opts.SetVariables {
variables[n] = vv
}
for n, vv := range opts.SetVariables {
variables[n] = vv
}
if diags.HasErrors() {
return nil, walkApply, diags
@ -352,26 +362,22 @@ func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, opts *App
operation = walkDestroy
}
var externalProviderConfigs map[addrs.RootProviderConfig]providers.Interface
if opts != nil {
externalProviderConfigs = opts.ExternalProviders
}
graph, moreDiags := (&ApplyGraphBuilder{
Config: config,
Changes: plan.Changes,
DeferredChanges: plan.DeferredResources,
State: plan.PriorState,
RootVariableValues: variables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: plan.TargetAddrs,
ActionTargets: plan.ActionTargetAddrs,
ForceReplace: plan.ForceReplaceAddrs,
Operation: operation,
ExternalReferences: plan.ExternalReferences,
Overrides: plan.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
Config: config,
Changes: plan.Changes,
DeferredChanges: plan.DeferredResources,
State: plan.PriorState,
RootVariableValues: variables,
ExternalProviderConfigs: opts.ExternalProviders,
Plugins: c.plugins,
Targets: plan.TargetAddrs,
ActionTargets: plan.ActionTargetAddrs,
ForceReplace: plan.ForceReplaceAddrs,
Operation: operation,
ExternalReferences: plan.ExternalReferences,
Overrides: plan.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
AllowRootEphemeralOutputs: opts.AllowRootEphemeralOutputs,
}).Build(addrs.RootModuleInstance)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {

@ -153,6 +153,13 @@ type PlanOpts struct {
// attribute is set. This can only be set during a destroy plan, and should
// only be set during the test command.
OverridePreventDestroy bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
// Plan generates an execution plan by comparing the given configuration
@ -992,57 +999,60 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
return nil, walkPlan, diags
}
graph, diags := (&PlanGraphBuilder{
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: opts.Targets,
ForceReplace: opts.ForceReplace,
skipRefresh: opts.SkipRefresh,
preDestroyRefresh: opts.PreDestroyRefresh,
Operation: walkPlan,
ExternalReferences: opts.ExternalReferences,
Overrides: opts.Overrides,
ImportTargets: c.findImportTargets(config),
forgetResources: forgetResources,
forgetModules: forgetModules,
GenerateConfigPath: opts.GenerateConfigPath,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
queryPlan: opts.Query,
overridePreventDestroy: opts.OverridePreventDestroy,
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: opts.Targets,
ForceReplace: opts.ForceReplace,
skipRefresh: opts.SkipRefresh,
preDestroyRefresh: opts.PreDestroyRefresh,
Operation: walkPlan,
ExternalReferences: opts.ExternalReferences,
Overrides: opts.Overrides,
ImportTargets: c.findImportTargets(config),
forgetResources: forgetResources,
forgetModules: forgetModules,
GenerateConfigPath: opts.GenerateConfigPath,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
queryPlan: opts.Query,
overridePreventDestroy: opts.OverridePreventDestroy,
AllowRootEphemeralOutputs: opts.AllowRootEphemeralOutputs,
}).Build(addrs.RootModuleInstance)
return graph, walkPlan, diags
case plans.RefreshOnlyMode:
graph, diags := (&PlanGraphBuilder{
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: append(opts.Targets, opts.ActionTargets...),
ActionTargets: opts.ActionTargets,
skipRefresh: opts.SkipRefresh,
skipPlanChanges: true, // this activates "refresh only" mode.
Operation: walkPlan,
ExternalReferences: opts.ExternalReferences,
Overrides: opts.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: append(opts.Targets, opts.ActionTargets...),
ActionTargets: opts.ActionTargets,
skipRefresh: opts.SkipRefresh,
skipPlanChanges: true, // this activates "refresh only" mode.
Operation: walkPlan,
ExternalReferences: opts.ExternalReferences,
Overrides: opts.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
AllowRootEphemeralOutputs: opts.AllowRootEphemeralOutputs,
}).Build(addrs.RootModuleInstance)
return graph, walkPlan, diags
case plans.DestroyMode:
graph, diags := (&PlanGraphBuilder{
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: opts.Targets,
skipRefresh: opts.SkipRefresh,
Operation: walkPlanDestroy,
Overrides: opts.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
overridePreventDestroy: opts.OverridePreventDestroy,
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
ExternalProviderConfigs: externalProviderConfigs,
Plugins: c.plugins,
Targets: opts.Targets,
skipRefresh: opts.SkipRefresh,
Operation: walkPlanDestroy,
Overrides: opts.Overrides,
SkipGraphValidation: c.graphOpts.SkipGraphValidation,
overridePreventDestroy: opts.OverridePreventDestroy,
AllowRootEphemeralOutputs: opts.AllowRootEphemeralOutputs,
}).Build(addrs.RootModuleInstance)
return graph, walkPlanDestroy, diags
default:

@ -37,6 +37,13 @@ type ValidateOpts struct {
// When true, query files will also be validated.
Query bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
// Validate performs semantic validation of a configuration, and returns
@ -101,14 +108,15 @@ func (c *Context) Validate(config *configs.Config, opts *ValidateOpts) tfdiags.D
}
graph, moreDiags := (&PlanGraphBuilder{
Config: config,
Plugins: c.plugins,
State: states.NewState(),
RootVariableValues: varValues,
Operation: walkValidate,
ExternalProviderConfigs: opts.ExternalProviders,
ImportTargets: c.findImportTargets(config),
queryPlan: opts.Query,
Config: config,
Plugins: c.plugins,
State: states.NewState(),
RootVariableValues: varValues,
Operation: walkValidate,
ExternalProviderConfigs: opts.ExternalProviders,
ImportTargets: c.findImportTargets(config),
queryPlan: opts.Query,
AllowRootEphemeralOutputs: opts.AllowRootEphemeralOutputs,
}).Build(addrs.RootModuleInstance)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {

@ -81,6 +81,13 @@ type ApplyGraphBuilder struct {
// SkipGraphValidation indicates whether the graph builder should skip
// validation of the graph.
SkipGraphValidation bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
// See GraphBuilder
@ -137,9 +144,10 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
&variableValidationTransformer{},
&LocalTransformer{Config: b.Config},
&OutputTransformer{
Config: b.Config,
Destroying: b.Operation == walkDestroy,
Overrides: b.Overrides,
Config: b.Config,
Destroying: b.Operation == walkDestroy,
Overrides: b.Overrides,
AllowRootEphemeralOutputs: b.AllowRootEphemeralOutputs,
},
// Creates all the resource instances represented in the diff, along

@ -125,6 +125,13 @@ type PlanGraphBuilder struct {
// allows Terraform to ignore the configuration attribute prevent_destroy
// to destroy resources regardless.
overridePreventDestroy bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
// See GraphBuilder
@ -205,10 +212,11 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
},
&LocalTransformer{Config: b.Config},
&OutputTransformer{
Config: b.Config,
RefreshOnly: b.skipPlanChanges || b.preDestroyRefresh,
Destroying: b.Operation == walkPlanDestroy,
Overrides: b.Overrides,
Config: b.Config,
RefreshOnly: b.skipPlanChanges || b.preDestroyRefresh,
Destroying: b.Operation == walkPlanDestroy,
Overrides: b.Overrides,
AllowRootEphemeralOutputs: b.AllowRootEphemeralOutputs,
// NOTE: We currently treat anything built with the plan graph
// builder as "planning" for our purposes here, because we share

@ -40,6 +40,13 @@ type nodeExpandOutput struct {
// details.
Planning bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
// Overrides is the set of overrides applied by the testing framework. We
// may need to override the value for this output and if we do the value
// comes from here.
@ -125,14 +132,15 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagn
default:
node = &NodeApplyableOutput{
Addr: absAddr,
Config: n.Config,
Change: change,
RefreshOnly: n.RefreshOnly,
DestroyApply: n.Destroying,
Planning: n.Planning,
Override: n.getOverrideValue(absAddr.Module),
Dependencies: n.Dependencies,
Addr: absAddr,
Config: n.Config,
Change: change,
RefreshOnly: n.RefreshOnly,
DestroyApply: n.Destroying,
Planning: n.Planning,
Override: n.getOverrideValue(absAddr.Module),
Dependencies: n.Dependencies,
AllowRootEphemeralOutputs: n.AllowRootEphemeralOutputs,
}
}
@ -280,6 +288,13 @@ type NodeApplyableOutput struct {
// Dependencies is the full set of resources that are referenced by this
// output.
Dependencies []addrs.ConfigResource
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
}
var (
@ -391,7 +406,7 @@ func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags
val = n.Change.After
}
if n.Addr.Module.IsRoot() && n.Config.Ephemeral {
if (n.Addr.Module.IsRoot() && n.Config.Ephemeral) && !n.AllowRootEphemeralOutputs {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Ephemeral output not allowed",

@ -32,6 +32,13 @@ type OutputTransformer struct {
// so we need to record that we wish to remove them.
Destroying bool
// AllowRootEphemeralOutputs overrides a specific check made within the
// output nodes that they cannot be ephemeral at within root modules. This
// should be set to true for plans executing from within either the stacks
// or test runtimes, where the root modules as Terraform sees them aren't
// the actual root modules.
AllowRootEphemeralOutputs bool
// Overrides supplies the values for any output variables that should be
// overridden by the testing framework.
Overrides *mocking.Overrides
@ -60,13 +67,14 @@ func (t *OutputTransformer) transform(g *Graph, c *configs.Config) error {
addr := addrs.OutputValue{Name: o.Name}
node := &nodeExpandOutput{
Addr: addr,
Module: c.Path,
Config: o,
Destroying: t.Destroying,
RefreshOnly: t.RefreshOnly,
Planning: t.Planning,
Overrides: t.Overrides,
Addr: addr,
Module: c.Path,
Config: o,
Destroying: t.Destroying,
RefreshOnly: t.RefreshOnly,
Planning: t.Planning,
Overrides: t.Overrides,
AllowRootEphemeralOutputs: t.AllowRootEphemeralOutputs,
}
log.Printf("[TRACE] OutputTransformer: adding %s as %T", o.Name, node)

Loading…
Cancel
Save