blob: ca87960fe00b7496d6256c4ba235075f2baebb71 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package deferring
import (
"testing"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
)
func TestDeferred_externalDependency(t *testing.T) {
deferred := NewDeferred(true)
// This reports that something outside of the modules runtime knows that
// everything in this configuration depends on some elsewhere-action
// that has been deferred, and so the modules runtime must respect that
// even though it doesn't know the details of why it is so.
deferred.SetExternalDependencyDeferred()
// With the above flag set, now ShouldDeferResourceInstanceChanges should
// return true regardless of any other information.
got := deferred.ShouldDeferResourceInstanceChanges(addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "anything",
Name: "really-anything",
},
},
}, nil)
if !got {
t.Errorf("did not report that the instance should have its changes deferred; should have")
}
}
func TestDeferred_absResourceInstanceDeferred(t *testing.T) {
instAAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("foo", addrs.NoKey),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "a",
},
},
}
instBAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "a",
},
},
}
instCAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "c",
},
},
}
dependencies := addrs.MakeMap[addrs.ConfigResource, []addrs.ConfigResource](
addrs.MapElem[addrs.ConfigResource, []addrs.ConfigResource]{
Key: instCAddr.ConfigResource(),
Value: []addrs.ConfigResource{instBAddr.ConfigResource(), instAAddr.ConfigResource()},
})
deferred := NewDeferred(true)
// Before we report anything, all three addresses should indicate that
// they don't need to have their actions deferred.
t.Run("without any deferrals yet", func(t *testing.T) {
for _, instAddr := range []addrs.AbsResourceInstance{instAAddr, instBAddr, instCAddr} {
if deferred.ShouldDeferResourceInstanceChanges(instAddr, dependencies.Get(instAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be, yet", instAddr)
}
}
})
// Instance A has its Create action deferred for some reason.
deferred.ReportResourceInstanceDeferred(instAAddr, providers.DeferredReasonResourceConfigUnknown, &plans.ResourceInstanceChange{
Addr: instAAddr,
Change: plans.Change{
Action: plans.Create,
After: cty.DynamicVal,
},
})
t.Run("with one resource instance deferred", func(t *testing.T) {
if !deferred.ShouldDeferResourceInstanceChanges(instCAddr, dependencies.Get(instCAddr.ConfigResource())) {
t.Errorf("%s was not reported as needing deferred; should be deferred", instCAddr)
}
if deferred.ShouldDeferResourceInstanceChanges(instBAddr, dependencies.Get(instBAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be", instCAddr)
}
})
}
func TestDeferred_absDataSourceInstanceDeferred(t *testing.T) {
instAAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("foo", addrs.NoKey),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test",
Name: "a",
},
},
}
instBAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test",
Name: "b",
},
},
}
instCAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "c",
},
},
}
dependencies := addrs.MakeMap[addrs.ConfigResource, []addrs.ConfigResource](
addrs.MapElem[addrs.ConfigResource, []addrs.ConfigResource]{
Key: instCAddr.ConfigResource(),
Value: []addrs.ConfigResource{instBAddr.ConfigResource(), instAAddr.ConfigResource()},
})
deferred := NewDeferred(true)
// Before we report anything, all three addresses should indicate that
// they don't need to have their actions deferred.
t.Run("without any deferrals yet", func(t *testing.T) {
for _, instAddr := range []addrs.AbsResourceInstance{instAAddr, instBAddr, instCAddr} {
if deferred.ShouldDeferResourceInstanceChanges(instAddr, dependencies.Get(instAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be, yet", instAddr)
}
}
})
// Instance A has its Read action deferred for some reason.
deferred.ReportDataSourceInstanceDeferred(instAAddr, providers.DeferredReasonProviderConfigUnknown, &plans.ResourceInstanceChange{
Addr: instAAddr,
PrevRunAddr: instAAddr,
Change: plans.Change{
Action: plans.Read,
After: cty.DynamicVal,
},
ActionReason: plans.ResourceInstanceReadBecauseDependencyPending,
})
t.Run("with one resource instance deferred", func(t *testing.T) {
if !deferred.ShouldDeferResourceInstanceChanges(instCAddr, dependencies.Get(instCAddr.ConfigResource())) {
t.Errorf("%s was not reported as needing deferred; should be deferred", instCAddr)
}
if deferred.ShouldDeferResourceInstanceChanges(instBAddr, dependencies.Get(instBAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be", instCAddr)
}
})
}
func TestDeferred_absEphemeralResourceInstanceDeferred(t *testing.T) {
instAAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("foo", addrs.NoKey),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.EphemeralResourceMode,
Type: "test",
Name: "a",
},
},
}
instBAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.EphemeralResourceMode,
Type: "test",
Name: "b",
},
},
}
instCAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "c",
},
},
}
dependencies := addrs.MakeMap[addrs.ConfigResource, []addrs.ConfigResource](
addrs.MapElem[addrs.ConfigResource, []addrs.ConfigResource]{
Key: instCAddr.ConfigResource(),
Value: []addrs.ConfigResource{instBAddr.ConfigResource(), instAAddr.ConfigResource()},
})
deferred := NewDeferred(true)
// Before we report anything, all three addresses should indicate that
// they don't need to have their actions deferred.
t.Run("without any deferrals yet", func(t *testing.T) {
for _, instAddr := range []addrs.AbsResourceInstance{instAAddr, instBAddr, instCAddr} {
if deferred.ShouldDeferResourceInstanceChanges(instAddr, dependencies.Get(instAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be, yet", instAddr)
}
}
})
// Instance A has e.g. the open action deferred
deferred.ReportEphemeralResourceInstanceDeferred(instAAddr, providers.DeferredReasonProviderConfigUnknown)
t.Run("with one resource instance deferred", func(t *testing.T) {
if !deferred.ShouldDeferResourceInstanceChanges(instCAddr, dependencies.Get(instCAddr.ConfigResource())) {
t.Errorf("%s was not reported as needing deferred; should be deferred", instCAddr)
}
if deferred.ShouldDeferResourceInstanceChanges(instBAddr, dependencies.Get(instBAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be", instCAddr)
}
})
}
func TestDeferred_partialExpandedDatasource(t *testing.T) {
instAAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("foo", addrs.NoKey),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test",
Name: "a",
},
},
}
instBAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "a",
},
},
}
instCAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test",
Name: "c",
},
},
}
instAPartial := addrs.RootModuleInstance.
UnexpandedChild(addrs.ModuleCall{Name: "foo"}).
Resource(instAAddr.Resource.Resource)
dependencies := addrs.MakeMap[addrs.ConfigResource, []addrs.ConfigResource](
addrs.MapElem[addrs.ConfigResource, []addrs.ConfigResource]{
Key: instCAddr.ConfigResource(),
Value: []addrs.ConfigResource{instBAddr.ConfigResource(), instAAddr.ConfigResource()},
})
deferred := NewDeferred(true)
// Before we report anything, all three addresses should indicate that
// they don't need to have their actions deferred.
t.Run("without any deferrals yet", func(t *testing.T) {
for _, instAddr := range []addrs.AbsResourceInstance{instAAddr, instBAddr, instCAddr} {
if deferred.ShouldDeferResourceInstanceChanges(instAddr, dependencies.Get(instAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be, yet", instAddr)
}
}
})
// Resource A hasn't been expanded fully, so is deferred.
deferred.ReportDataSourceExpansionDeferred(instAPartial, &plans.ResourceInstanceChange{
Addr: instAAddr,
Change: plans.Change{
Action: plans.Read,
After: cty.DynamicVal,
},
})
t.Run("with one resource instance deferred", func(t *testing.T) {
if !deferred.ShouldDeferResourceInstanceChanges(instCAddr, dependencies.Get(instCAddr.ConfigResource())) {
t.Errorf("%s was not reported as needing deferred; should be deferred", instCAddr)
}
if deferred.ShouldDeferResourceInstanceChanges(instBAddr, dependencies.Get(instBAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be", instCAddr)
}
})
}
func TestDeferred_partialExpandedResource(t *testing.T) {
instAAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("foo", addrs.NoKey),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "a",
},
},
}
instBAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "a",
},
},
}
instCAddr := addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "c",
},
},
}
instAPartial := addrs.RootModuleInstance.
UnexpandedChild(addrs.ModuleCall{Name: "foo"}).
Resource(instAAddr.Resource.Resource)
dependencies := addrs.MakeMap[addrs.ConfigResource, []addrs.ConfigResource](
addrs.MapElem[addrs.ConfigResource, []addrs.ConfigResource]{
Key: instCAddr.ConfigResource(),
Value: []addrs.ConfigResource{instBAddr.ConfigResource(), instAAddr.ConfigResource()},
})
deferred := NewDeferred(true)
// Before we report anything, all three addresses should indicate that
// they don't need to have their actions deferred.
t.Run("without any deferrals yet", func(t *testing.T) {
for _, instAddr := range []addrs.AbsResourceInstance{instAAddr, instBAddr, instCAddr} {
if deferred.ShouldDeferResourceInstanceChanges(instAddr, dependencies.Get(instAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be, yet", instAddr)
}
}
})
// Resource A hasn't been expanded fully, so is deferred.
deferred.ReportResourceExpansionDeferred(instAPartial, &plans.ResourceInstanceChange{
Addr: instAAddr,
Change: plans.Change{
Action: plans.Create,
After: cty.DynamicVal,
},
})
t.Run("with one resource instance deferred", func(t *testing.T) {
if !deferred.ShouldDeferResourceInstanceChanges(instCAddr, dependencies.Get(instCAddr.ConfigResource())) {
t.Errorf("%s was not reported as needing deferred; should be deferred", instCAddr)
}
if deferred.ShouldDeferResourceInstanceChanges(instBAddr, dependencies.Get(instBAddr.ConfigResource())) {
t.Errorf("%s reported as needing deferred; should not be", instCAddr)
}
})
}