mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
partially expand triggers as well
This commit is contained in:
parent
b6a9990354
commit
dffeeec81e
14 changed files with 613 additions and 15 deletions
|
|
@ -16,12 +16,14 @@ type Actions struct {
|
|||
// Must hold this lock when accessing all fields after this one.
|
||||
mu sync.Mutex
|
||||
|
||||
actionInstances addrs.Map[addrs.AbsActionInstance, ActionData]
|
||||
actionInstances addrs.Map[addrs.AbsActionInstance, ActionData]
|
||||
partialExpandedActions addrs.Map[addrs.PartialExpandedAction, ActionData]
|
||||
}
|
||||
|
||||
func NewActions() *Actions {
|
||||
return &Actions{
|
||||
actionInstances: addrs.MakeMap[addrs.AbsActionInstance, ActionData](),
|
||||
actionInstances: addrs.MakeMap[addrs.AbsActionInstance, ActionData](),
|
||||
partialExpandedActions: addrs.MakeMap[addrs.PartialExpandedAction, ActionData](),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,3 +72,30 @@ func (a *Actions) GetActionInstanceKeys(addr addrs.AbsAction) []addrs.AbsActionI
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *Actions) AddPartialExpandedAction(addr addrs.PartialExpandedAction, configValue cty.Value, providerAddr addrs.AbsProviderConfig) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.partialExpandedActions.Has(addr) {
|
||||
panic("action instance already exists: " + addr.String())
|
||||
}
|
||||
|
||||
a.partialExpandedActions.Put(addr, ActionData{
|
||||
ConfigValue: configValue,
|
||||
ProviderAddr: providerAddr,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Actions) GetPartialExpandedAction(addr addrs.PartialExpandedAction) (*ActionData, bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
data, ok := a.partialExpandedActions.GetOk(addr)
|
||||
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &data, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -932,6 +932,10 @@ func (pea PartialExpandedAction) String() string {
|
|||
return pea.action.String() + "[*]"
|
||||
}
|
||||
|
||||
func (pea PartialExpandedAction) Equal(other PartialExpandedAction) bool {
|
||||
return pea.module.MatchesPartial(other.module.expandedPrefix.PartialModule()) && pea.action.Equal(other.action)
|
||||
}
|
||||
|
||||
func (pea PartialExpandedAction) UniqueKey() UniqueKey {
|
||||
// If this address is equivalent to an AbsAction address then we'll
|
||||
// return its instance key here so that function Equivalent will consider
|
||||
|
|
|
|||
|
|
@ -234,3 +234,103 @@ func MarshalDeferredActionInvocations(dais []*plans.DeferredActionInvocationSrc,
|
|||
}
|
||||
return deferredInvocations, nil
|
||||
}
|
||||
|
||||
func MarshalDeferredPartialActionInvocations(dais []*plans.DeferredPartialExpandedActionInvocationSrc, schemas *terraform.Schemas) ([]DeferredActionInvocation, error) {
|
||||
var deferredInvocations []DeferredActionInvocation
|
||||
|
||||
// sortedActions := append([]*plans.DeferredActionInvocationSrc{}, dais...)
|
||||
// sort.Slice(sortedActions, func(i, j int) bool {
|
||||
// return sortedActions[i].ActionInvocationInstanceSrc.Less(sortedActions[j].ActionInvocationInstanceSrc)
|
||||
// })
|
||||
|
||||
for _, daiSrc := range dais {
|
||||
ai, err := MarshalPartialActionInvocation(daiSrc.ActionInvocationInstanceSrc, schemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dai := DeferredActionInvocation{
|
||||
ActionInvocation: ai,
|
||||
}
|
||||
switch daiSrc.DeferredReason {
|
||||
case providers.DeferredReasonInstanceCountUnknown:
|
||||
dai.Reason = DeferredReasonInstanceCountUnknown
|
||||
case providers.DeferredReasonResourceConfigUnknown:
|
||||
dai.Reason = DeferredReasonResourceConfigUnknown
|
||||
case providers.DeferredReasonProviderConfigUnknown:
|
||||
dai.Reason = DeferredReasonProviderConfigUnknown
|
||||
case providers.DeferredReasonAbsentPrereq:
|
||||
dai.Reason = DeferredReasonAbsentPrereq
|
||||
case providers.DeferredReasonDeferredPrereq:
|
||||
dai.Reason = DeferredReasonDeferredPrereq
|
||||
default:
|
||||
// If we find a reason we don't know about, we'll just mark it as
|
||||
// unknown. This is a bit of a safety net to ensure that we don't
|
||||
// break if new reasons are introduced in future versions of the
|
||||
// provider protocol.
|
||||
dai.Reason = DeferredReasonUnknown
|
||||
}
|
||||
|
||||
deferredInvocations = append(deferredInvocations, dai)
|
||||
}
|
||||
return deferredInvocations, nil
|
||||
}
|
||||
|
||||
func MarshalPartialActionInvocation(action *plans.PartialExpandedActionInvocationInstanceSrc, schemas *terraform.Schemas) (ActionInvocation, error) {
|
||||
ai := ActionInvocation{
|
||||
Address: action.Addr.String(),
|
||||
Type: action.Addr.ConfigAction().Action.Type,
|
||||
Name: action.Addr.ConfigAction().Action.Name,
|
||||
ProviderName: action.ProviderAddr.Provider.String(),
|
||||
}
|
||||
schema := schemas.ActionTypeConfig(
|
||||
action.ProviderAddr.Provider,
|
||||
action.Addr.ConfigAction().Action.Type,
|
||||
)
|
||||
if schema.ConfigSchema == nil {
|
||||
return ai, fmt.Errorf("no schema found for %s (in provider %s)", action.Addr.ConfigAction().Action.Type, action.ProviderAddr.Provider)
|
||||
}
|
||||
|
||||
actionDec, err := action.Decode(&schema)
|
||||
if err != nil {
|
||||
return ai, fmt.Errorf("failed to decode action %s: %w", action.Addr, err)
|
||||
}
|
||||
|
||||
switch at := action.ActionTrigger.(type) {
|
||||
case plans.PartialLifecycleActionTrigger:
|
||||
ai.LifecycleActionTrigger = &LifecycleActionTrigger{
|
||||
TriggeringResourceAddress: at.TriggeringResourceAddr.String(),
|
||||
ActionTriggerEvent: at.TriggerEvent().String(),
|
||||
ActionTriggerBlockIndex: at.ActionTriggerBlockIndex,
|
||||
ActionsListIndex: at.ActionsListIndex,
|
||||
}
|
||||
default:
|
||||
return ai, fmt.Errorf("unsupported action trigger type: %T", at)
|
||||
}
|
||||
|
||||
if actionDec.ConfigValue != cty.NilVal {
|
||||
_, pvms := actionDec.ConfigValue.UnmarkDeepWithPaths()
|
||||
sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
|
||||
ephemeralPaths, otherMarks := marks.PathsWithMark(otherMarks, marks.Ephemeral)
|
||||
if len(ephemeralPaths) > 0 {
|
||||
return ai, fmt.Errorf("action %s has ephemeral config values, which are not supported in action invocations", action.Addr)
|
||||
}
|
||||
if len(otherMarks) > 0 {
|
||||
return ai, fmt.Errorf("action %s has config values with unsupported marks: %v", action.Addr, otherMarks)
|
||||
}
|
||||
|
||||
configValue := actionDec.ConfigValue
|
||||
if !configValue.IsWhollyKnown() {
|
||||
configValue = omitUnknowns(actionDec.ConfigValue)
|
||||
}
|
||||
cs := jsonstate.SensitiveAsBool(marks.MarkPaths(configValue, marks.Sensitive, sensitivePaths))
|
||||
configSensitive, err := ctyjson.Marshal(cs, cs.Type())
|
||||
if err != nil {
|
||||
return ai, err
|
||||
}
|
||||
|
||||
ai.ConfigValues = marshalConfigValues(configValue)
|
||||
ai.ConfigSensitive = configSensitive
|
||||
}
|
||||
return ai, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,6 +320,14 @@ func Marshal(
|
|||
}
|
||||
}
|
||||
|
||||
if p.DeferredPartialActionInvocations != nil {
|
||||
deferredPartialActionInvocations, err := MarshalDeferredPartialActionInvocations(p.DeferredPartialActionInvocations, schemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling deferred partial action invocations: %s", err)
|
||||
}
|
||||
output.DeferredActionInvocations = append(output.DeferredActionInvocations, deferredPartialActionInvocations...)
|
||||
}
|
||||
|
||||
// output.OutputChanges
|
||||
if output.OutputChanges, err = MarshalOutputChanges(p.Changes); err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling output changes: %s", err)
|
||||
|
|
|
|||
|
|
@ -180,3 +180,167 @@ func (ai *ActionInvocationInstance) DeepCopy() *ActionInvocationInstance {
|
|||
ret := *ai
|
||||
return &ret
|
||||
}
|
||||
|
||||
// PartialActionTrigger is the equivalent of ActionTrigger but allows the
|
||||
// triggering address to be only partially expanded. This is used during earlier
|
||||
// phases of planning when (for example) count/for_each expansions are not yet
|
||||
// fully resolved.
|
||||
type PartialActionTrigger interface {
|
||||
partialActionTriggerSigil()
|
||||
|
||||
TriggerEvent() configs.ActionTriggerEvent
|
||||
|
||||
String() string
|
||||
|
||||
Equals(other PartialActionTrigger) bool
|
||||
}
|
||||
|
||||
// PartialLifecycleActionTrigger is the partial-expanded form of
|
||||
// LifecycleActionTrigger. It differs only in that it stores a partial-expanded
|
||||
// resource instance address for the triggering resource.
|
||||
type PartialLifecycleActionTrigger struct {
|
||||
TriggeringResourceAddr addrs.PartialExpandedResource
|
||||
ActionTriggerEvent configs.ActionTriggerEvent
|
||||
ActionTriggerBlockIndex int
|
||||
ActionsListIndex int
|
||||
}
|
||||
|
||||
func (t PartialLifecycleActionTrigger) partialActionTriggerSigil() {}
|
||||
|
||||
func (t PartialLifecycleActionTrigger) TriggerEvent() configs.ActionTriggerEvent {
|
||||
return t.ActionTriggerEvent
|
||||
}
|
||||
|
||||
func (t PartialLifecycleActionTrigger) String() string {
|
||||
return t.TriggeringResourceAddr.String()
|
||||
}
|
||||
|
||||
func (t PartialLifecycleActionTrigger) Equals(other PartialActionTrigger) bool {
|
||||
o, ok := other.(*PartialLifecycleActionTrigger)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
pomt, tIsPartial := t.TriggeringResourceAddr.PartialExpandedModule()
|
||||
pemo, oIsPartial := o.TriggeringResourceAddr.PartialExpandedModule()
|
||||
|
||||
if tIsPartial != oIsPartial {
|
||||
return false
|
||||
}
|
||||
|
||||
return pomt.MatchesPartial(pemo) && t.TriggeringResourceAddr.Resource().Equal(o.TriggeringResourceAddr.Resource()) &&
|
||||
t.ActionTriggerEvent == o.ActionTriggerEvent &&
|
||||
t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex &&
|
||||
t.ActionsListIndex == o.ActionsListIndex
|
||||
}
|
||||
|
||||
var _ PartialActionTrigger = (*PartialLifecycleActionTrigger)(nil)
|
||||
|
||||
// PartialExpandedActionInvocationInstance mirrors ActionInvocationInstance
|
||||
// but keeps the action and/or trigger resource addresses in a
|
||||
// partial-expanded form until all dynamic expansions (count, for_each, etc.)
|
||||
// are resolved.
|
||||
type PartialExpandedActionInvocationInstance struct {
|
||||
Addr addrs.PartialExpandedAction
|
||||
ActionTrigger PartialActionTrigger
|
||||
ProviderAddr addrs.AbsProviderConfig
|
||||
ConfigValue cty.Value
|
||||
}
|
||||
|
||||
// DeepCopy creates a defensive copy of the partial-expanded invocation.
|
||||
func (pii *PartialExpandedActionInvocationInstance) DeepCopy() *PartialExpandedActionInvocationInstance {
|
||||
if pii == nil {
|
||||
return pii
|
||||
}
|
||||
ret := *pii
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Equals compares two partial-expanded invocation instances.
|
||||
func (pii *PartialExpandedActionInvocationInstance) Equals(other *PartialExpandedActionInvocationInstance) bool {
|
||||
if pii == nil || other == nil {
|
||||
return pii == other
|
||||
}
|
||||
// We compare the (partial) action address and the trigger (which may also
|
||||
// embed a partial address).
|
||||
addrEqual := pii.Addr.Equal(other.Addr)
|
||||
triggerEqual := false
|
||||
if pii.ActionTrigger == nil && other.ActionTrigger == nil {
|
||||
triggerEqual = true
|
||||
} else if pii.ActionTrigger != nil && other.ActionTrigger != nil {
|
||||
triggerEqual = pii.ActionTrigger.Equals(other.ActionTrigger)
|
||||
}
|
||||
return addrEqual && triggerEqual
|
||||
}
|
||||
|
||||
type PartialExpandedActionInvocationInstanceSrc struct {
|
||||
Addr addrs.PartialExpandedAction
|
||||
ActionTrigger PartialActionTrigger
|
||||
ProviderAddr addrs.AbsProviderConfig
|
||||
ConfigValue DynamicValue
|
||||
SensitiveConfigPaths []cty.Path
|
||||
}
|
||||
|
||||
// Encode produces a variant of the receiver that has its config value
|
||||
// serialized so it can be written to a plan file while action and trigger
|
||||
// addresses are still in their partial-expanded form. Pass the implied type
|
||||
// of the corresponding action schema for correct operation.
|
||||
func (pii *PartialExpandedActionInvocationInstance) Encode(schema *providers.ActionSchema) (*PartialExpandedActionInvocationInstanceSrc, error) {
|
||||
ret := &PartialExpandedActionInvocationInstanceSrc{
|
||||
Addr: pii.Addr,
|
||||
ActionTrigger: pii.ActionTrigger,
|
||||
ProviderAddr: pii.ProviderAddr,
|
||||
}
|
||||
|
||||
if pii.ConfigValue != cty.NilVal {
|
||||
ty := cty.DynamicPseudoType
|
||||
if schema != nil {
|
||||
ty = schema.ConfigSchema.ImpliedType()
|
||||
}
|
||||
|
||||
unmarkedConfigValue, pvms := pii.ConfigValue.UnmarkDeepWithPaths()
|
||||
sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
|
||||
if len(otherMarks) > 0 {
|
||||
return nil, fmt.Errorf("%s: error serializing partial-expanded action invocation with unexpected marks on config value: %#v. This is a bug in Terraform.", tfdiags.FormatCtyPath(otherMarks[0].Path), otherMarks[0].Marks)
|
||||
}
|
||||
|
||||
var err error
|
||||
ret.ConfigValue, err = NewDynamicValue(unmarkedConfigValue, ty)
|
||||
ret.SensitiveConfigPaths = sensitivePaths
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Decode produces an in-memory form of the serialized partial-expanded action
|
||||
// invocation instance using the provided schema to infer the original config
|
||||
// value type.
|
||||
func (src *PartialExpandedActionInvocationInstanceSrc) Decode(schema *providers.ActionSchema) (*PartialExpandedActionInvocationInstance, error) {
|
||||
ret := &PartialExpandedActionInvocationInstance{
|
||||
Addr: src.Addr,
|
||||
ActionTrigger: src.ActionTrigger,
|
||||
ProviderAddr: src.ProviderAddr,
|
||||
}
|
||||
|
||||
if src.ConfigValue != nil {
|
||||
ty := cty.DynamicPseudoType
|
||||
if schema != nil {
|
||||
ty = schema.ConfigSchema.ImpliedType()
|
||||
}
|
||||
|
||||
val, err := src.ConfigValue.Decode(ty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(src.SensitiveConfigPaths) > 0 {
|
||||
val = marks.MarkPaths(val, marks.Sensitive, src.SensitiveConfigPaths)
|
||||
}
|
||||
|
||||
ret.ConfigValue = val
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,3 +96,53 @@ func (dais *DeferredActionInvocationSrc) Decode(schema *providers.ActionSchema)
|
|||
ActionInvocationInstance: instance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeferredPartialExpandedActionInvocation tracks information about an action
|
||||
// invocation that has been deferred for some reason, where the underlying
|
||||
// ActionInvocationInstance contains a partially expanded address (and
|
||||
// LifecycleActionTrigger).
|
||||
type DeferredPartialExpandedActionInvocation struct {
|
||||
// DeferredReason is the reason why this action invocation was deferred.
|
||||
DeferredReason providers.DeferredReason
|
||||
|
||||
// ActionInvocationInstance is the (partially expanded) instance of the action
|
||||
// invocation that was deferred. Its Addr (and any embedded
|
||||
// LifecycleActionTrigger addresses) are partial.
|
||||
ActionInvocationInstance *PartialExpandedActionInvocationInstance
|
||||
}
|
||||
|
||||
func (dai *DeferredPartialExpandedActionInvocation) Encode(schema *providers.ActionSchema) (*DeferredPartialExpandedActionInvocationSrc, error) {
|
||||
src, err := dai.ActionInvocationInstance.Encode(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DeferredPartialExpandedActionInvocationSrc{
|
||||
DeferredReason: dai.DeferredReason,
|
||||
ActionInvocationInstanceSrc: src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeferredPartialExpandedActionInvocationSrc is the serialized form of
|
||||
// DeferredPartialExpandedActionInvocation.
|
||||
type DeferredPartialExpandedActionInvocationSrc struct {
|
||||
// DeferredReason is the reason why this action invocation was deferred.
|
||||
DeferredReason providers.DeferredReason
|
||||
|
||||
// ActionInvocationInstanceSrc is the (partially expanded) instance of the
|
||||
// action invocation that was deferred. Its Addr (and any embedded
|
||||
// LifecycleActionTrigger addresses) are partial.
|
||||
ActionInvocationInstanceSrc *PartialExpandedActionInvocationInstanceSrc
|
||||
}
|
||||
|
||||
func (dais *DeferredPartialExpandedActionInvocationSrc) Decode(schema *providers.ActionSchema) (*DeferredPartialExpandedActionInvocation, error) {
|
||||
instance, err := dais.ActionInvocationInstanceSrc.Decode(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DeferredPartialExpandedActionInvocation{
|
||||
DeferredReason: dais.DeferredReason,
|
||||
ActionInvocationInstance: instance,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ type Deferred struct {
|
|||
|
||||
partialExpandedActionsDeferred addrs.Map[addrs.ConfigAction, addrs.Map[addrs.PartialExpandedAction, *plans.DeferredResourceInstanceChange]]
|
||||
|
||||
partialExpandedActionInvocationsDeferred []*plans.DeferredPartialExpandedActionInvocation
|
||||
|
||||
// partialExpandedModulesDeferred tracks all of the partial-expanded module
|
||||
// prefixes we were notified about.
|
||||
//
|
||||
|
|
@ -155,6 +157,7 @@ func NewDeferred(enabled bool) *Deferred {
|
|||
partialExpandedDataSourcesDeferred: addrs.MakeMap[addrs.ConfigResource, addrs.Map[addrs.PartialExpandedResource, *plans.DeferredResourceInstanceChange]](),
|
||||
partialExpandedEphemeralResourceDeferred: addrs.MakeMap[addrs.ConfigResource, addrs.Map[addrs.PartialExpandedResource, *plans.DeferredResourceInstanceChange]](),
|
||||
partialExpandedActionsDeferred: addrs.MakeMap[addrs.ConfigAction, addrs.Map[addrs.PartialExpandedAction, *plans.DeferredResourceInstanceChange]](),
|
||||
partialExpandedActionInvocationsDeferred: []*plans.DeferredPartialExpandedActionInvocation{},
|
||||
partialExpandedModulesDeferred: addrs.MakeSet[addrs.PartialExpandedModule](),
|
||||
}
|
||||
}
|
||||
|
|
@ -196,6 +199,11 @@ func (d *Deferred) GetDeferredActionInvocations() []*plans.DeferredActionInvocat
|
|||
return d.actionInvocationDeferred
|
||||
}
|
||||
|
||||
// GetDeferredPartialActionInvocations returns a list of all deferred partial action invocations.
|
||||
func (d *Deferred) GetDeferredPartialActionInvocations() []*plans.DeferredPartialExpandedActionInvocation {
|
||||
return d.partialExpandedActionInvocationsDeferred
|
||||
}
|
||||
|
||||
// SetExternalDependencyDeferred modifies a freshly-constructed [Deferred]
|
||||
// so that it will consider all resource instances as needing their actions
|
||||
// deferred, even if there's no other reason to do that.
|
||||
|
|
@ -237,7 +245,8 @@ func (d *Deferred) HaveAnyDeferrals() bool {
|
|||
d.partialExpandedDataSourcesDeferred.Len() != 0 ||
|
||||
d.partialExpandedEphemeralResourceDeferred.Len() != 0 ||
|
||||
d.partialExpandedActionsDeferred.Len() != 0 ||
|
||||
len(d.partialExpandedModulesDeferred) != 0)
|
||||
len(d.partialExpandedModulesDeferred) != 0) ||
|
||||
len(d.partialExpandedActionInvocationsDeferred) != 0
|
||||
}
|
||||
|
||||
// GetDeferredResourceInstanceValue returns the deferred value for the given
|
||||
|
|
@ -323,6 +332,18 @@ func (d *Deferred) GetDeferredResourceInstances(addr addrs.AbsResource) map[addr
|
|||
return result
|
||||
}
|
||||
|
||||
func (d *Deferred) GetDeferredPartialExpandedResource(addr addrs.PartialExpandedResource) *plans.DeferredResourceInstanceChange {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
item, ok := d.partialExpandedResourcesDeferred.GetOk(addr.ConfigResource())
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return item.Get(addr)
|
||||
}
|
||||
|
||||
// ShouldDeferResourceInstanceChanges returns true if the receiver knows some
|
||||
// reason why the resource instance with the given address should have its
|
||||
// planned action deferred for a future plan/apply round.
|
||||
|
|
@ -708,6 +729,27 @@ func (d *Deferred) ReportActionDeferred(addr addrs.AbsActionInstance, reason pro
|
|||
configMap.Put(addr, reason)
|
||||
}
|
||||
|
||||
func (d *Deferred) ReportPartialActionInvocationDeferred(ai plans.PartialExpandedActionInvocationInstance, reason providers.DeferredReason) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
// Check if the action invocation is already deferred
|
||||
for _, deferred := range d.partialExpandedActionInvocationsDeferred {
|
||||
if deferred.ActionInvocationInstance.Equals(&ai) {
|
||||
// This indicates a bug in the caller, since our graph walk should
|
||||
// ensure that we visit and evaluate each distinct action invocation
|
||||
// only once.
|
||||
panic(fmt.Sprintf("duplicate deferral report for action %s invoked by %s", ai.Addr.String(), ai.ActionTrigger.TriggerEvent().String()))
|
||||
}
|
||||
}
|
||||
|
||||
d.partialExpandedActionInvocationsDeferred = append(d.partialExpandedActionInvocationsDeferred, &plans.DeferredPartialExpandedActionInvocation{
|
||||
ActionInvocationInstance: &ai,
|
||||
DeferredReason: reason,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// ShouldDeferActionInvocation returns true if there is a reason to defer the action invocation instance
|
||||
// We want to defer an action invocation if
|
||||
// a) the resource was deferred
|
||||
|
|
|
|||
|
|
@ -66,13 +66,14 @@ type Plan struct {
|
|||
VariableMarks map[string][]cty.PathValueMarks
|
||||
ApplyTimeVariables collections.Set[string]
|
||||
|
||||
Changes *ChangesSrc
|
||||
DriftedResources []*ResourceInstanceChangeSrc
|
||||
DeferredResources []*DeferredResourceInstanceChangeSrc
|
||||
DeferredActionInvocations []*DeferredActionInvocationSrc
|
||||
TargetAddrs []addrs.Targetable
|
||||
ActionTargetAddrs []addrs.Targetable
|
||||
ForceReplaceAddrs []addrs.AbsResourceInstance
|
||||
Changes *ChangesSrc
|
||||
DriftedResources []*ResourceInstanceChangeSrc
|
||||
DeferredResources []*DeferredResourceInstanceChangeSrc
|
||||
DeferredActionInvocations []*DeferredActionInvocationSrc
|
||||
DeferredPartialActionInvocations []*DeferredPartialExpandedActionInvocationSrc
|
||||
TargetAddrs []addrs.Targetable
|
||||
ActionTargetAddrs []addrs.Targetable
|
||||
ForceReplaceAddrs []addrs.AbsResourceInstance
|
||||
|
||||
Backend Backend
|
||||
StateStore StateStore
|
||||
|
|
|
|||
|
|
@ -885,6 +885,10 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
|||
deferredActionInvocations, deferredActionInvocationsDiags := c.deferredActionInvocations(schemas, walker.Deferrals.GetDeferredActionInvocations())
|
||||
diags = diags.Append(deferredActionInvocationsDiags)
|
||||
plan.DeferredActionInvocations = deferredActionInvocations
|
||||
|
||||
deferredPartialActionInvocations, deferredPartialActionInvocationsDiags := c.deferredPartialActionInvocations(schemas, walker.Deferrals.GetDeferredPartialActionInvocations())
|
||||
diags = diags.Append(deferredPartialActionInvocationsDiags)
|
||||
plan.DeferredPartialActionInvocations = deferredPartialActionInvocations
|
||||
}
|
||||
|
||||
// Our final rulings on whether the plan is "complete" and "applyable".
|
||||
|
|
@ -973,6 +977,26 @@ func (c *Context) deferredActionInvocations(schemas *Schemas, deferrals []*plans
|
|||
return deferredActionInvocations, diags
|
||||
}
|
||||
|
||||
func (c *Context) deferredPartialActionInvocations(schemas *Schemas, deferrals []*plans.DeferredPartialExpandedActionInvocation) ([]*plans.DeferredPartialExpandedActionInvocationSrc, tfdiags.Diagnostics) {
|
||||
var deferredPartialActionInvocations []*plans.DeferredPartialExpandedActionInvocationSrc
|
||||
var diags tfdiags.Diagnostics
|
||||
for _, deferral := range deferrals {
|
||||
schema := schemas.ActionTypeConfig(deferral.ActionInvocationInstance.ProviderAddr.Provider, deferral.ActionInvocationInstance.Addr.ConfigAction().Action.Type)
|
||||
deferralSrc, err := deferral.Encode(&schema)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to prepare deferred partial action invocation for plan",
|
||||
fmt.Sprintf("The deferred partial action invocation %q could not be serialized to store in the plan: %s.", deferral.ActionInvocationInstance.Addr, err)))
|
||||
continue
|
||||
}
|
||||
|
||||
deferredPartialActionInvocations = append(deferredPartialActionInvocations, deferralSrc)
|
||||
}
|
||||
|
||||
return deferredPartialActionInvocations, diags
|
||||
}
|
||||
|
||||
func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) {
|
||||
var externalProviderConfigs map[addrs.RootProviderConfig]providers.Interface
|
||||
if opts != nil {
|
||||
|
|
|
|||
|
|
@ -45,12 +45,13 @@ func (n *nodeExpandActionDeclaration) DynamicExpand(ctx EvalContext) (*Graph, tf
|
|||
pem := expander.UnknownModuleInstances(n.Addr.Module, false)
|
||||
|
||||
for _, moduleAddr := range pem {
|
||||
resourceAddr := moduleAddr.Action(n.Addr.Action)
|
||||
actionAddr := moduleAddr.Action(n.Addr.Action)
|
||||
|
||||
// And add a node to the graph for this action.
|
||||
g.Add(&NodeActionDeclarationPartialExpanded{
|
||||
addr: resourceAddr,
|
||||
addr: actionAddr,
|
||||
config: n.Config,
|
||||
Schema: n.Schema,
|
||||
resolvedProvider: n.ResolvedProvider,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ package terraform
|
|||
import (
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/instances"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// NodeActionDeclarationPartialExpanded is a graph node that stands in for
|
||||
|
|
@ -21,6 +24,7 @@ import (
|
|||
type NodeActionDeclarationPartialExpanded struct {
|
||||
addr addrs.PartialExpandedAction
|
||||
config configs.Action
|
||||
Schema *providers.ActionSchema
|
||||
resolvedProvider addrs.AbsProviderConfig
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +57,18 @@ func (n *NodeActionDeclarationPartialExpanded) ActionAddr() addrs.ConfigAction {
|
|||
|
||||
// Execute implements GraphNodeExecutable.
|
||||
func (n *NodeActionDeclarationPartialExpanded) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
ctx.Deferrals().ReportActionExpansionDeferred(n.addr)
|
||||
configVal := cty.NullVal(n.Schema.ConfigSchema.ImpliedType())
|
||||
if n.config.Config != nil {
|
||||
var configDiags tfdiags.Diagnostics
|
||||
configVal, _, configDiags = ctx.EvaluateBlock(n.config.Config, n.Schema.ConfigSchema.DeepCopy(), nil, instances.TotallyUnknownRepetitionData)
|
||||
|
||||
diags = diags.Append(configDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
ctx.Actions().AddPartialExpandedAction(n.addr, configVal, n.resolvedProvider)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,8 @@ type nodeActionTriggerPlanInstance struct {
|
|||
}
|
||||
|
||||
type lifecycleActionTriggerInstance struct {
|
||||
resourceAddress addrs.AbsResourceInstance
|
||||
events []configs.ActionTriggerEvent
|
||||
//condition hcl.Expression
|
||||
resourceAddress addrs.AbsResourceInstance
|
||||
events []configs.ActionTriggerEvent
|
||||
actionTriggerBlockIndex int
|
||||
actionListIndex int
|
||||
invokingSubject *hcl.Range
|
||||
|
|
|
|||
129
internal/terraform/node_action_trigger_partialexp.go
Normal file
129
internal/terraform/node_action_trigger_partialexp.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// NodeActionTriggerPartialExpanded is a graph node that stands in for
|
||||
// an unbounded set of potential action trigger instances that we don't yet know.
|
||||
//
|
||||
// Its job is to check the configuration as much as we can with the information
|
||||
// that's available (so we can raise an error early if something is clearly
|
||||
// wrong across _all_ potential instances) and to record a placeholder value
|
||||
// for use when evaluating other objects that refer to this resource.
|
||||
//
|
||||
// This is the partial-expanded equivalent of NodeActionTriggerInstance.
|
||||
type NodeActionTriggerPartialExpanded struct {
|
||||
addr addrs.PartialExpandedAction
|
||||
config *configs.Action
|
||||
resolvedProvider addrs.AbsProviderConfig
|
||||
lifecycleActionTrigger *lifecycleActionTriggerPartialExpanded
|
||||
}
|
||||
|
||||
type lifecycleActionTriggerPartialExpanded struct {
|
||||
resourceAddress addrs.PartialExpandedResource
|
||||
events []configs.ActionTriggerEvent
|
||||
actionTriggerBlockIndex int
|
||||
actionListIndex int
|
||||
invokingSubject *hcl.Range
|
||||
}
|
||||
|
||||
func (at *lifecycleActionTriggerPartialExpanded) Name() string {
|
||||
return fmt.Sprintf("%s.lifecycle.action_trigger[%d].actions[%d]", at.resourceAddress.String(), at.actionTriggerBlockIndex, at.actionListIndex)
|
||||
}
|
||||
|
||||
var (
|
||||
_ graphNodeEvalContextScope = (*NodeActionTriggerPartialExpanded)(nil)
|
||||
_ GraphNodeExecutable = (*NodeActionTriggerPartialExpanded)(nil)
|
||||
)
|
||||
|
||||
// Name implements [dag.NamedVertex].
|
||||
func (n *NodeActionTriggerPartialExpanded) Name() string {
|
||||
return n.addr.String()
|
||||
}
|
||||
|
||||
// Path implements graphNodeEvalContextScope.
|
||||
func (n *NodeActionTriggerPartialExpanded) Path() evalContextScope {
|
||||
if moduleAddr, ok := n.addr.ModuleInstance(); ok {
|
||||
return evalContextModuleInstance{Addr: moduleAddr}
|
||||
} else if moduleAddr, ok := n.addr.PartialExpandedModule(); ok {
|
||||
return evalContextPartialExpandedModule{Addr: moduleAddr}
|
||||
} else {
|
||||
// Should not get here: at least one of the two cases above
|
||||
// should always be true for any valid addrs.PartialExpandedResource
|
||||
panic("addrs.PartialExpandedResource has neither a partial-expanded or a fully-expanded module instance address")
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NodeActionTriggerPartialExpanded) ActionAddr() addrs.ConfigAction {
|
||||
return n.addr.ConfigAction()
|
||||
}
|
||||
|
||||
// Execute implements GraphNodeExecutable.
|
||||
func (n *NodeActionTriggerPartialExpanded) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
// We know that if the action is partially expanded, the triggering resource must also be partially expanded.
|
||||
partialResourceChange := ctx.Deferrals().GetDeferredPartialExpandedResource(n.lifecycleActionTrigger.resourceAddress)
|
||||
if partialResourceChange == nil {
|
||||
panic("partialResource is nil")
|
||||
}
|
||||
|
||||
triggeringEvent, isTriggered := actionIsTriggeredByEvent(n.lifecycleActionTrigger.events, partialResourceChange.Change.Action)
|
||||
if !isTriggered {
|
||||
return nil
|
||||
}
|
||||
|
||||
actionInstance, ok := ctx.Actions().GetPartialExpandedAction(n.addr)
|
||||
if !ok {
|
||||
panic("action is nil")
|
||||
}
|
||||
|
||||
provider, _, err := getProvider(ctx, actionInstance.ProviderAddr)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to get provider",
|
||||
Detail: fmt.Sprintf("Failed to get provider: %s", err),
|
||||
Subject: n.lifecycleActionTrigger.invokingSubject,
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// We remove the marks for planning, we will record the sensitive values in the plans.ActionInvocationInstance
|
||||
unmarkedConfig, _ := actionInstance.ConfigValue.UnmarkDeepWithPaths()
|
||||
|
||||
resp := provider.PlanAction(providers.PlanActionRequest{
|
||||
ActionType: n.addr.ConfigAction().Action.Type,
|
||||
ProposedActionData: unmarkedConfig,
|
||||
ClientCapabilities: ctx.ClientCapabilities(),
|
||||
})
|
||||
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
diags = diags.Append(resp.Diagnostics)
|
||||
return diags
|
||||
}
|
||||
|
||||
ctx.Deferrals().ReportPartialActionInvocationDeferred(plans.PartialExpandedActionInvocationInstance{
|
||||
Addr: n.addr,
|
||||
ProviderAddr: n.resolvedProvider,
|
||||
ActionTrigger: plans.PartialLifecycleActionTrigger{
|
||||
TriggeringResourceAddr: n.lifecycleActionTrigger.resourceAddress,
|
||||
ActionTriggerEvent: *triggeringEvent,
|
||||
ActionTriggerBlockIndex: n.lifecycleActionTrigger.actionTriggerBlockIndex,
|
||||
ActionsListIndex: n.lifecycleActionTrigger.actionListIndex,
|
||||
},
|
||||
ConfigValue: actionInstance.ConfigValue,
|
||||
}, providers.DeferredReasonInstanceCountUnknown)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -63,6 +63,37 @@ func (n *nodeActionTriggerPlanExpand) DynamicExpand(ctx EvalContext) (*Graph, tf
|
|||
}
|
||||
|
||||
expander := ctx.InstanceExpander()
|
||||
|
||||
// The possibility of partial-expanded modules and resources is guarded by a
|
||||
// top-level option for the whole plan, so that we can preserve mainline
|
||||
// behavior for the modules runtime. So, we currently branch off into an
|
||||
// entirely-separate codepath in those situations, at the expense of
|
||||
// duplicating some of the logic for behavior this method would normally
|
||||
// handle.
|
||||
if ctx.Deferrals().DeferralAllowed() {
|
||||
pem := expander.UnknownModuleInstances(n.Addr.Module, false)
|
||||
|
||||
for _, moduleAddr := range pem {
|
||||
actionAddr := moduleAddr.Action(n.Addr.Action)
|
||||
resourceAddr := moduleAddr.Resource(n.lifecycleActionTrigger.resourceAddress.Resource)
|
||||
|
||||
// And add a node to the graph for this action.
|
||||
g.Add(&NodeActionTriggerPartialExpanded{
|
||||
addr: actionAddr,
|
||||
config: n.Config,
|
||||
resolvedProvider: n.resolvedProvider,
|
||||
lifecycleActionTrigger: &lifecycleActionTriggerPartialExpanded{
|
||||
resourceAddress: resourceAddr,
|
||||
events: n.lifecycleActionTrigger.events,
|
||||
actionTriggerBlockIndex: n.lifecycleActionTrigger.actionTriggerBlockIndex,
|
||||
actionListIndex: n.lifecycleActionTrigger.actionListIndex,
|
||||
invokingSubject: n.lifecycleActionTrigger.invokingSubject,
|
||||
},
|
||||
})
|
||||
}
|
||||
addRootNodeToGraph(&g)
|
||||
}
|
||||
|
||||
// First we expand the module
|
||||
moduleInstances := expander.ExpandModule(n.lifecycleActionTrigger.resourceAddress.Module, false)
|
||||
for _, module := range moduleInstances {
|
||||
|
|
|
|||
Loading…
Reference in a new issue