mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
419 lines
17 KiB
Go
419 lines
17 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackruntime
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackruntime/hooks"
|
|
)
|
|
|
|
type ExpectedHooks struct {
|
|
ComponentExpanded []*hooks.ComponentInstances
|
|
RemovedComponentExpanded []*hooks.RemovedComponentInstances
|
|
PendingComponentInstancePlan collections.Set[stackaddrs.AbsComponentInstance]
|
|
BeginComponentInstancePlan collections.Set[stackaddrs.AbsComponentInstance]
|
|
EndComponentInstancePlan collections.Set[stackaddrs.AbsComponentInstance]
|
|
ErrorComponentInstancePlan collections.Set[stackaddrs.AbsComponentInstance]
|
|
DeferComponentInstancePlan collections.Set[stackaddrs.AbsComponentInstance]
|
|
PendingComponentInstanceApply collections.Set[stackaddrs.AbsComponentInstance]
|
|
BeginComponentInstanceApply collections.Set[stackaddrs.AbsComponentInstance]
|
|
EndComponentInstanceApply collections.Set[stackaddrs.AbsComponentInstance]
|
|
ErrorComponentInstanceApply collections.Set[stackaddrs.AbsComponentInstance]
|
|
ReportResourceInstanceStatus []*hooks.ResourceInstanceStatusHookData
|
|
ReportResourceInstanceProvisionerStatus []*hooks.ResourceInstanceProvisionerHookData
|
|
ReportResourceInstanceDrift []*hooks.ResourceInstanceChange
|
|
ReportResourceInstancePlanned []*hooks.ResourceInstanceChange
|
|
ReportResourceInstanceDeferred []*hooks.DeferredResourceInstanceChange
|
|
ReportComponentInstancePlanned []*hooks.ComponentInstanceChange
|
|
ReportComponentInstanceApplied []*hooks.ComponentInstanceChange
|
|
ReportActionInvocationStatus []*hooks.ActionInvocationStatusHookData
|
|
}
|
|
|
|
func (eh *ExpectedHooks) Validate(t *testing.T, expectedHooks *ExpectedHooks) {
|
|
sort.SliceStable(expectedHooks.ComponentExpanded, func(i, j int) bool {
|
|
return expectedHooks.ComponentExpanded[i].ComponentAddr.String() < expectedHooks.ComponentExpanded[j].ComponentAddr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.RemovedComponentExpanded, func(i, j int) bool {
|
|
return expectedHooks.RemovedComponentExpanded[i].Source.String() < expectedHooks.RemovedComponentExpanded[j].Source.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportResourceInstanceStatus, func(i, j int) bool {
|
|
return expectedHooks.ReportResourceInstanceStatus[i].Addr.String() < expectedHooks.ReportResourceInstanceStatus[j].Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportResourceInstanceProvisionerStatus, func(i, j int) bool {
|
|
return expectedHooks.ReportResourceInstanceProvisionerStatus[i].Addr.String() < expectedHooks.ReportResourceInstanceProvisionerStatus[j].Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportResourceInstanceDrift, func(i, j int) bool {
|
|
return expectedHooks.ReportResourceInstanceDrift[i].Addr.String() < expectedHooks.ReportResourceInstanceDrift[j].Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportResourceInstancePlanned, func(i, j int) bool {
|
|
return expectedHooks.ReportResourceInstancePlanned[i].Addr.String() < expectedHooks.ReportResourceInstancePlanned[j].Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportResourceInstanceDeferred, func(i, j int) bool {
|
|
return expectedHooks.ReportResourceInstanceDeferred[i].Change.Addr.String() < expectedHooks.ReportResourceInstanceDeferred[j].Change.Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportComponentInstancePlanned, func(i, j int) bool {
|
|
return expectedHooks.ReportComponentInstancePlanned[i].Addr.String() < expectedHooks.ReportComponentInstancePlanned[j].Addr.String()
|
|
})
|
|
sort.SliceStable(expectedHooks.ReportComponentInstanceApplied, func(i, j int) bool {
|
|
return expectedHooks.ReportComponentInstanceApplied[i].Addr.String() < expectedHooks.ReportComponentInstanceApplied[j].Addr.String()
|
|
})
|
|
|
|
if diff := cmp.Diff(expectedHooks.ComponentExpanded, eh.ComponentExpanded); len(diff) > 0 {
|
|
t.Errorf("wrong ComponentExpanded hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.RemovedComponentExpanded, eh.RemovedComponentExpanded); len(diff) > 0 {
|
|
t.Errorf("wrong RemovedComponentExpanded hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.PendingComponentInstancePlan, eh.PendingComponentInstancePlan, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong PendingComponentInstancePlan hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.BeginComponentInstancePlan, eh.BeginComponentInstancePlan, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong BeginComponentInstancePlan hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.EndComponentInstancePlan, eh.EndComponentInstancePlan, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong EndComponentInstancePlan hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ErrorComponentInstancePlan, eh.ErrorComponentInstancePlan, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong ErrorComponentInstancePlan hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.DeferComponentInstancePlan, eh.DeferComponentInstancePlan, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong DeferComponentInstancePlan hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.PendingComponentInstanceApply, eh.PendingComponentInstanceApply, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong PendingComponentInstanceApply hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.BeginComponentInstanceApply, eh.BeginComponentInstanceApply, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong BeginComponentInstanceApply hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.EndComponentInstanceApply, eh.EndComponentInstanceApply, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong EndComponentInstanceApply hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ErrorComponentInstanceApply, eh.ErrorComponentInstanceApply, collections.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong ErrorComponentInstanceApply hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportResourceInstanceStatus, eh.ReportResourceInstanceStatus); len(diff) > 0 {
|
|
t.Errorf("wrong ReportResourceInstanceStatus hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportResourceInstanceProvisionerStatus, eh.ReportResourceInstanceProvisionerStatus); len(diff) > 0 {
|
|
t.Errorf("wrong ReportResourceInstanceProvisionerStatus hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportResourceInstanceDrift, eh.ReportResourceInstanceDrift); len(diff) > 0 {
|
|
t.Errorf("wrong ReportResourceInstanceDrift hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportResourceInstancePlanned, eh.ReportResourceInstancePlanned); len(diff) > 0 {
|
|
t.Errorf("wrong ReportResourceInstancePlanned hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportResourceInstanceDeferred, eh.ReportResourceInstanceDeferred); len(diff) > 0 {
|
|
t.Errorf("wrong ReportResourceInstanceDeferred hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportComponentInstancePlanned, eh.ReportComponentInstancePlanned); len(diff) > 0 {
|
|
t.Errorf("wrong ReportComponentInstancePlanned hooks: %s", diff)
|
|
}
|
|
if diff := cmp.Diff(expectedHooks.ReportComponentInstanceApplied, eh.ReportComponentInstanceApplied); len(diff) > 0 {
|
|
t.Errorf("wrong ReportComponentInstanceApplied hooks: %s", diff)
|
|
}
|
|
}
|
|
|
|
type CapturedHooks struct {
|
|
ExpectedHooks
|
|
|
|
sync.Mutex
|
|
Planning bool
|
|
}
|
|
|
|
func NewCapturedHooks(planning bool) *CapturedHooks {
|
|
return &CapturedHooks{
|
|
Planning: planning,
|
|
ExpectedHooks: ExpectedHooks{
|
|
PendingComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
BeginComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
EndComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
ErrorComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
DeferComponentInstancePlan: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
PendingComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
BeginComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
EndComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
ErrorComponentInstanceApply: collections.NewSet[stackaddrs.AbsComponentInstance](),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (ch *CapturedHooks) ComponentInstancePending(addr stackaddrs.AbsComponentInstance) bool {
|
|
if ch.Planning {
|
|
return ch.PendingComponentInstancePlan.Has(addr)
|
|
}
|
|
return ch.PendingComponentInstanceApply.Has(addr)
|
|
}
|
|
|
|
func (ch *CapturedHooks) ComponentInstanceBegun(addr stackaddrs.AbsComponentInstance) bool {
|
|
if ch.Planning {
|
|
return ch.BeginComponentInstancePlan.Has(addr)
|
|
}
|
|
return ch.BeginComponentInstanceApply.Has(addr)
|
|
}
|
|
|
|
func (ch *CapturedHooks) ComponentInstanceFinished(addr stackaddrs.AbsComponentInstance) bool {
|
|
if ch.Planning {
|
|
return ch.EndComponentInstancePlan.Has(addr) || ch.ErrorComponentInstancePlan.Has(addr) || ch.DeferComponentInstancePlan.Has(addr)
|
|
}
|
|
return ch.EndComponentInstanceApply.Has(addr) || ch.ErrorComponentInstanceApply.Has(addr)
|
|
}
|
|
|
|
func (ch *CapturedHooks) captureHooks() *Hooks {
|
|
return &Hooks{
|
|
ComponentExpanded: func(ctx context.Context, instances *hooks.ComponentInstances) {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
ch.ComponentExpanded = append(ch.ComponentExpanded, instances)
|
|
},
|
|
RemovedComponentExpanded: func(ctx context.Context, instances *hooks.RemovedComponentInstances) {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
ch.RemovedComponentExpanded = append(ch.RemovedComponentExpanded, instances)
|
|
},
|
|
PendingComponentInstancePlan: func(ctx context.Context, instance stackaddrs.AbsComponentInstance) {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
if ch.ComponentInstancePending(instance) {
|
|
panic("tried to add pending component instance plan twice")
|
|
}
|
|
ch.PendingComponentInstancePlan.Add(instance)
|
|
},
|
|
BeginComponentInstancePlan: func(ctx context.Context, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstancePending(instance) {
|
|
panic("tried to begin component instance plan before ending")
|
|
}
|
|
|
|
if ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to add begin component instance plan twice")
|
|
}
|
|
ch.BeginComponentInstancePlan.Add(instance)
|
|
return nil
|
|
},
|
|
EndComponentInstancePlan: func(ctx context.Context, a any, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.BeginComponentInstancePlan.Has(instance) {
|
|
panic("tried to end component instance plan before beginning")
|
|
}
|
|
|
|
if ch.EndComponentInstancePlan.Has(instance) || ch.ErrorComponentInstancePlan.Has(instance) || ch.DeferComponentInstancePlan.Has(instance) {
|
|
panic("tried to add end component instance plan twice")
|
|
}
|
|
ch.EndComponentInstancePlan.Add(instance)
|
|
return a
|
|
},
|
|
ErrorComponentInstancePlan: func(ctx context.Context, a any, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to end component instance plan before beginning")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(instance) {
|
|
panic("tried to add end component instance plan twice")
|
|
}
|
|
ch.ErrorComponentInstancePlan.Add(instance)
|
|
return a
|
|
},
|
|
DeferComponentInstancePlan: func(ctx context.Context, a any, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to end component instance plan before beginning")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(instance) {
|
|
panic("tried to add end component instance plan twice")
|
|
}
|
|
ch.DeferComponentInstancePlan.Add(instance)
|
|
return a
|
|
},
|
|
PendingComponentInstanceApply: func(ctx context.Context, instance stackaddrs.AbsComponentInstance) {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if ch.ComponentInstancePending(instance) {
|
|
panic("tried to add pending component instance apply twice")
|
|
}
|
|
ch.PendingComponentInstanceApply.Add(instance)
|
|
},
|
|
BeginComponentInstanceApply: func(ctx context.Context, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstancePending(instance) {
|
|
panic("tried to begin component before pending")
|
|
}
|
|
|
|
if ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to add begin component instance apply twice")
|
|
}
|
|
ch.BeginComponentInstanceApply.Add(instance)
|
|
return nil
|
|
},
|
|
EndComponentInstanceApply: func(ctx context.Context, a any, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to end component before beginning")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(instance) {
|
|
panic("tried to add end component instance apply twice")
|
|
}
|
|
ch.EndComponentInstanceApply.Add(instance)
|
|
return a
|
|
},
|
|
ErrorComponentInstanceApply: func(ctx context.Context, a any, instance stackaddrs.AbsComponentInstance) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(instance) {
|
|
panic("tried to end component before beginning")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(instance) {
|
|
panic("tried to add error component instance apply twice")
|
|
}
|
|
ch.ErrorComponentInstanceApply.Add(instance)
|
|
return a
|
|
},
|
|
ReportResourceInstanceStatus: func(ctx context.Context, a any, data *hooks.ResourceInstanceStatusHookData) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(data.Addr.Component) {
|
|
panic("tried to report resource instance status before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(data.Addr.Component) {
|
|
panic("tried to report resource instance status after component")
|
|
}
|
|
|
|
ch.ReportResourceInstanceStatus = append(ch.ReportResourceInstanceStatus, data)
|
|
return a
|
|
},
|
|
ReportResourceInstanceProvisionerStatus: func(ctx context.Context, a any, data *hooks.ResourceInstanceProvisionerHookData) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(data.Addr.Component) {
|
|
panic("tried to report resource instance provisioner status before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(data.Addr.Component) {
|
|
panic("tried to report resource instance provisioner status after component")
|
|
}
|
|
|
|
ch.ReportResourceInstanceProvisionerStatus = append(ch.ReportResourceInstanceProvisionerStatus, data)
|
|
return a
|
|
},
|
|
ReportResourceInstanceDrift: func(ctx context.Context, a any, change *hooks.ResourceInstanceChange) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(change.Addr.Component) {
|
|
panic("tried to report resource instance drift before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(change.Addr.Component) {
|
|
panic("tried to report resource instance drift after component")
|
|
}
|
|
|
|
ch.ReportResourceInstanceDrift = append(ch.ReportResourceInstanceDrift, change)
|
|
return a
|
|
},
|
|
ReportResourceInstancePlanned: func(ctx context.Context, a any, change *hooks.ResourceInstanceChange) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(change.Addr.Component) {
|
|
panic("tried to report resource instance planned before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(change.Addr.Component) {
|
|
panic("tried to report resource instance planned after component")
|
|
}
|
|
|
|
ch.ReportResourceInstancePlanned = append(ch.ReportResourceInstancePlanned, change)
|
|
return a
|
|
},
|
|
ReportResourceInstanceDeferred: func(ctx context.Context, a any, change *hooks.DeferredResourceInstanceChange) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(change.Change.Addr.Component) {
|
|
panic("tried to report resource instance deferred before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(change.Change.Addr.Component) {
|
|
panic("tried to report resource instance deferred after component")
|
|
}
|
|
|
|
ch.ReportResourceInstanceDeferred = append(ch.ReportResourceInstanceDeferred, change)
|
|
return a
|
|
},
|
|
ReportComponentInstancePlanned: func(ctx context.Context, a any, change *hooks.ComponentInstanceChange) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(change.Addr) {
|
|
panic("tried to report component instance planned before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(change.Addr) {
|
|
panic("tried to report component instance planned after component")
|
|
}
|
|
|
|
ch.ReportComponentInstancePlanned = append(ch.ReportComponentInstancePlanned, change)
|
|
return a
|
|
},
|
|
ReportComponentInstanceApplied: func(ctx context.Context, a any, change *hooks.ComponentInstanceChange) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(change.Addr) {
|
|
panic("tried to report component instance planned before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(change.Addr) {
|
|
panic("tried to report component instance planned after component")
|
|
}
|
|
|
|
ch.ReportComponentInstanceApplied = append(ch.ReportComponentInstanceApplied, change)
|
|
return a
|
|
},
|
|
ReportActionInvocationStatus: func(ctx context.Context, a any, data *hooks.ActionInvocationStatusHookData) any {
|
|
ch.Lock()
|
|
defer ch.Unlock()
|
|
|
|
if !ch.ComponentInstanceBegun(data.Addr.Component) {
|
|
panic("tried to report action invocation status before component")
|
|
}
|
|
|
|
if ch.ComponentInstanceFinished(data.Addr.Component) {
|
|
panic("tried to report action invocation status after component")
|
|
}
|
|
|
|
ch.ReportActionInvocationStatus = append(ch.ReportActionInvocationStatus, data)
|
|
return a
|
|
},
|
|
}
|
|
}
|