mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
bee7403f3e
Previously we would reject attempts to delete a workspace if its state contained any resources at all, even if none of the resources had any resource instance objects associated with it. Nowadays there isn't any situation where the normal Terraform workflow will leave behind resource husks, and so this isn't as problematic as it might've been in the v0.12 era, but nonetheless what we actually care about for this check is whether there might be any remote objects that this state is tracking, and for that it's more precise to look for non-nil resource instance objects, rather than whole resources. This also includes some adjustments to our error messaging to give more information about the problem and to use terminology more consistent with how we currently talk about this situation in our documentation and elsewhere in the UI. We were also using the old State.HasResources method as part of some of our tests. I considered preserving it to avoid changing the behavior of those tests, but the new check seemed close enough to the intent of those tests that it wasn't worth maintaining this method that wouldn't be used in any main code anymore. I've therefore updated those tests to use the new HasResourceInstanceObjects method instead.
12427 lines
345 KiB
Go
12427 lines
345 KiB
Go
package terraform
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/go-test/deep"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/provisioners"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/gocty"
|
|
)
|
|
|
|
func TestContext2Apply_basic(t *testing.T) {
|
|
m := testModule(t, "apply-good")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) < 2 {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_unstable(t *testing.T) {
|
|
// This tests behavior when the configuration contains an unstable value,
|
|
// such as the result of uuid() or timestamp(), where each call produces
|
|
// a different result.
|
|
//
|
|
// This is an important case to test because we need to ensure that
|
|
// we don't re-call the function during the apply phase: the value should
|
|
// be fixed during plan
|
|
|
|
m := testModule(t, "apply-unstable")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error during Plan: %s", diags.Err())
|
|
}
|
|
|
|
addr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block
|
|
rds := plan.Changes.ResourceInstance(addr)
|
|
rd, err := rds.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rd.After.GetAttr("random").IsKnown() {
|
|
t.Fatalf("Attribute 'random' has known value %#v; should be unknown in plan", rd.After.GetAttr("random"))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error during Apply: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.Module(addr.Module)
|
|
rss := state.ResourceInstance(addr)
|
|
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources))
|
|
}
|
|
|
|
rs, err := rss.Current.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
got := rs.Value.GetAttr("random")
|
|
if !got.IsKnown() {
|
|
t.Fatalf("random is still unknown after apply")
|
|
}
|
|
if got, want := len(got.AsString()), 36; got != want {
|
|
t.Fatalf("random string has wrong length %d; want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_escape(t *testing.T) {
|
|
m := testModule(t, "apply-escape")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = "bar"
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_resourceCountOneList(t *testing.T) {
|
|
m := testModule(t, "apply-resource-count-one-list")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(`null_resource.foo.0:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/null"]
|
|
|
|
Outputs:
|
|
|
|
test = [foo]`)
|
|
if got != want {
|
|
t.Fatalf("got:\n%s\n\nwant:\n%s\n", got, want)
|
|
}
|
|
}
|
|
func TestContext2Apply_resourceCountZeroList(t *testing.T) {
|
|
m := testModule(t, "apply-resource-count-zero-list")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(`<no state>
|
|
Outputs:
|
|
|
|
test = []`)
|
|
if got != want {
|
|
t.Fatalf("wrong state\n\ngot:\n%s\n\nwant:\n%s\n", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_resourceDependsOnModule(t *testing.T) {
|
|
m := testModule(t, "apply-resource-depends-on-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// verify the apply happens in the correct order
|
|
var mu sync.Mutex
|
|
var order []string
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
ami := req.PlannedState.GetAttr("ami").AsString()
|
|
switch ami {
|
|
case "child":
|
|
|
|
// make the child slower than the parent
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
mu.Lock()
|
|
order = append(order, "child")
|
|
mu.Unlock()
|
|
case "parent":
|
|
mu.Lock()
|
|
order = append(order, "parent")
|
|
mu.Unlock()
|
|
}
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !reflect.DeepEqual(order, []string{"child", "parent"}) {
|
|
t.Fatal("resources applied out of order")
|
|
}
|
|
|
|
checkStateString(t, state, testTerraformApplyResourceDependsOnModuleStr)
|
|
}
|
|
|
|
// Test that without a config, the Dependencies in the state are enough
|
|
// to maintain proper ordering.
|
|
func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) {
|
|
m := testModule(t, "apply-resource-depends-on-module-empty")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"parent"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.child")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.child").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"child"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
{
|
|
// verify the apply happens in the correct order
|
|
var mu sync.Mutex
|
|
var order []string
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
id := req.PriorState.GetAttr("id")
|
|
if id.IsKnown() && id.AsString() == "parent" {
|
|
// make the dep slower than the parent
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
mu.Lock()
|
|
order = append(order, "child")
|
|
mu.Unlock()
|
|
} else {
|
|
mu.Lock()
|
|
order = append(order, "parent")
|
|
mu.Unlock()
|
|
}
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !reflect.DeepEqual(order, []string{"child", "parent"}) {
|
|
t.Fatal("resources applied out of order")
|
|
}
|
|
|
|
checkStateString(t, state, "<no state>")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-resource-depends-on-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var globalState *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
globalState = state
|
|
}
|
|
|
|
{
|
|
// Wait for the dependency, sleep, and verify the graph never
|
|
// called a child.
|
|
var called int32
|
|
var checked bool
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
ami := req.PriorState.GetAttr("ami").AsString()
|
|
if ami == "parent" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("module child should not be called"))
|
|
return resp
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, globalState, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !checked {
|
|
t.Fatal("should check")
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) {
|
|
m := testModule(t, "apply-resource-depends-on-module-deep")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
{
|
|
// Wait for the dependency, sleep, and verify the graph never
|
|
// called a child.
|
|
var called int32
|
|
var checked bool
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
planned := req.PlannedState.AsValueMap()
|
|
if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("aws_instance.a should not be called"))
|
|
return resp
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !checked {
|
|
t.Fatal("should check")
|
|
}
|
|
|
|
checkStateString(t, state, testTerraformApplyResourceDependsOnModuleDeepStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) {
|
|
m := testModule(t, "apply-resource-depends-on-module-in-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
{
|
|
// Wait for the dependency, sleep, and verify the graph never
|
|
// called a child.
|
|
var called int32
|
|
var checked bool
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
planned := req.PlannedState.AsValueMap()
|
|
if ami, ok := planned["ami"]; ok && ami.AsString() == "grandchild" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("something else was applied before grandchild; grandchild should be first"))
|
|
return resp
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !checked {
|
|
t.Fatal("should check")
|
|
}
|
|
|
|
checkStateString(t, state, testTerraformApplyResourceDependsOnModuleInModuleStr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_mapVarBetweenModules(t *testing.T) {
|
|
m := testModule(t, "apply-map-var-through-module")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`<no state>
|
|
Outputs:
|
|
|
|
amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }
|
|
|
|
module.test:
|
|
null_resource.noop:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/null"]
|
|
|
|
Outputs:
|
|
|
|
amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`)
|
|
if actual != expected {
|
|
t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_refCount(t *testing.T) {
|
|
m := testModule(t, "apply-ref-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) < 2 {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyRefCountStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_providerAlias(t *testing.T) {
|
|
m := testModule(t, "apply-provider-alias")
|
|
|
|
// Each provider instance must be completely independent to ensure that we
|
|
// are verifying the correct state of each.
|
|
p := func() (providers.Interface, error) {
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
return p, nil
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): p,
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) < 2 {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProviderAliasStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// Two providers that are configured should both be configured prior to apply
|
|
func TestContext2Apply_providerAliasConfigure(t *testing.T) {
|
|
m := testModule(t, "apply-provider-alias-configure")
|
|
|
|
// Each provider instance must be completely independent to ensure that we
|
|
// are verifying the correct state of each.
|
|
p := func() (providers.Interface, error) {
|
|
p := testProvider("another")
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
return p, nil
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("another"): p,
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
// Configure to record calls AFTER Plan above
|
|
var configCount int32
|
|
p = func() (providers.Interface, error) {
|
|
p := testProvider("another")
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
atomic.AddInt32(&configCount, 1)
|
|
|
|
foo := req.Config.GetAttr("foo").AsString()
|
|
if foo != "bar" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("foo: %#v", foo))
|
|
}
|
|
|
|
return
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("another"): p,
|
|
},
|
|
})
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if configCount != 2 {
|
|
t.Fatalf("provider config expected 2 calls, got: %d", configCount)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProviderAliasConfigStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// GH-2870
|
|
func TestContext2Apply_providerWarning(t *testing.T) {
|
|
m := testModule(t, "apply-provider-warning")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("just a warning"))
|
|
return
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("got: \n%s\n\nexpected:\n%s", actual, expected)
|
|
}
|
|
|
|
if !p.ConfigureProviderCalled {
|
|
t.Fatalf("provider Configure() was never called!")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_emptyModule(t *testing.T) {
|
|
// A module with only outputs (no resources)
|
|
m := testModule(t, "apply-empty-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
actual = strings.Replace(actual, " ", "", -1)
|
|
expected := strings.TrimSpace(testTerraformApplyEmptyModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad: \n%s\nexpect:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_createBeforeDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-good-create-before")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if got, want := len(mod.Resources), 1; got != want {
|
|
t.Logf("state:\n%s", state)
|
|
t.Fatalf("wrong number of resources %d; want %d", got, want)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) {
|
|
m := testModule(t, "apply-good-create-before-update")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// signal that resource foo has started applying
|
|
fooChan := make(chan struct{})
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
switch id {
|
|
case "bar":
|
|
select {
|
|
case <-fooChan:
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("bar must be updated before foo is destroyed"))
|
|
return resp
|
|
case <-time.After(100 * time.Millisecond):
|
|
// wait a moment to ensure that foo is not going to be destroyed first
|
|
}
|
|
case "foo":
|
|
close(fooChan)
|
|
}
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
fooAddr := mustResourceInstanceAddr("aws_instance.foo")
|
|
root.SetResourceInstanceCurrent(
|
|
fooAddr.Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`),
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`),
|
|
CreateBeforeDestroy: true,
|
|
Dependencies: []addrs.ConfigResource{fooAddr.ContainingResource().Config()},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("bad: %s", state)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCreateBeforeUpdateStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// This tests that when a CBD resource depends on a non-CBD resource,
|
|
// we can still properly apply changes that require new for both.
|
|
func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) {
|
|
m := testModule(t, "apply-cbd-depends-non-cbd")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo", "require_new": "abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
require_new = yes
|
|
type = aws_instance
|
|
value = foo
|
|
|
|
Dependencies:
|
|
aws_instance.foo
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
require_new = yes
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) {
|
|
h := new(MockHook)
|
|
m := testModule(t, "apply-good-create-before")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
var actual []cty.Value
|
|
var actualLock sync.Mutex
|
|
h.PostApplyFn = func(addr addrs.AbsResourceInstance, gen states.Generation, sv cty.Value, e error) (HookAction, error) {
|
|
actualLock.Lock()
|
|
|
|
defer actualLock.Unlock()
|
|
actual = append(actual, sv)
|
|
return HookActionContinue, nil
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []cty.Value{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
"require_new": cty.StringVal("xyz"),
|
|
"type": cty.StringVal("aws_instance"),
|
|
}),
|
|
cty.NullVal(cty.DynamicPseudoType),
|
|
}
|
|
|
|
cmpOpt := cmp.Transformer("ctyshim", hcl2shim.ConfigValueFromHCL2)
|
|
if !cmp.Equal(actual, expected, cmpOpt) {
|
|
t.Fatalf("wrong state snapshot sequence\n%s", cmp.Diff(expected, actual, cmpOpt))
|
|
}
|
|
}
|
|
|
|
// Test that we can perform an apply with CBD in a count with deposed instances.
|
|
func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) {
|
|
m := testModule(t, "apply-cbd-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
states.NewDeposedKey(),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
states.NewDeposedKey(),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar.0:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
type = aws_instance
|
|
aws_instance.bar.1:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
// Test that when we have a deposed instance but a good primary, we still
|
|
// destroy the deposed instance.
|
|
func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) {
|
|
m := testModule(t, "apply-cbd-deposed-only")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceDeposed(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
states.NewDeposedKey(),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_destroyComputed(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-computed")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo", "output": "value"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("plan failed")
|
|
} else {
|
|
t.Logf("plan:\n\n%s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
}
|
|
|
|
// Test that the destroy operation uses depends_on as a source of ordering.
|
|
func TestContext2Apply_destroyDependsOn(t *testing.T) {
|
|
// It is possible for this to be racy, so we loop a number of times
|
|
// just to check.
|
|
for i := 0; i < 10; i++ {
|
|
testContext2Apply_destroyDependsOn(t)
|
|
}
|
|
}
|
|
|
|
func testContext2Apply_destroyDependsOn(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-depends-on")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
// Record the order we see Apply
|
|
var actual []string
|
|
var actualLock sync.Mutex
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
actualLock.Lock()
|
|
defer actualLock.Unlock()
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
actual = append(actual, id)
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Parallelism: 1, // To check ordering
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"foo", "bar"}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
// Test that destroy ordering is correct with dependencies only
|
|
// in the state.
|
|
func TestContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
|
|
newState := states.NewState()
|
|
root := newState.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
},
|
|
Module: root.Addr.Module(),
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
// It is possible for this to be racy, so we loop a number of times
|
|
// just to check.
|
|
for i := 0; i < 10; i++ {
|
|
t.Run("new", func(t *testing.T) {
|
|
testContext2Apply_destroyDependsOnStateOnly(t, newState)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testContext2Apply_destroyDependsOnStateOnly(t *testing.T, state *states.State) {
|
|
state = state.DeepCopy()
|
|
m := testModule(t, "empty")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
// Record the order we see Apply
|
|
var actual []string
|
|
var actualLock sync.Mutex
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
actualLock.Lock()
|
|
defer actualLock.Unlock()
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
actual = append(actual, id)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Parallelism: 1, // To check ordering
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"bar", "foo"}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
// Test that destroy ordering is correct with dependencies only
|
|
// in the state within a module (GH-11749)
|
|
func TestContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
|
|
newState := states.NewState()
|
|
child := newState.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
child.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
},
|
|
Module: child.Addr.Module(),
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
// It is possible for this to be racy, so we loop a number of times
|
|
// just to check.
|
|
for i := 0; i < 10; i++ {
|
|
t.Run("new", func(t *testing.T) {
|
|
testContext2Apply_destroyDependsOnStateOnlyModule(t, newState)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T, state *states.State) {
|
|
state = state.DeepCopy()
|
|
m := testModule(t, "empty")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// Record the order we see Apply
|
|
var actual []string
|
|
var actualLock sync.Mutex
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
actualLock.Lock()
|
|
defer actualLock.Unlock()
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
actual = append(actual, id)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Parallelism: 1, // To check ordering
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"bar", "foo"}
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("wrong order\ngot: %#v\nwant: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_dataBasic(t *testing.T) {
|
|
m := testModule(t, "apply-data-basic")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("yo"),
|
|
"foo": cty.NullVal(cty.String),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyDataBasicStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyData(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-data-resource")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: req.Config,
|
|
}
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("data.null_data_source.testing").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"-"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/null"]`),
|
|
)
|
|
|
|
hook := &testHook{}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
Hooks: []Hook{hook},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
newState, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if got := len(newState.Modules); got != 1 {
|
|
t.Fatalf("state has %d modules after destroy; want 1", got)
|
|
}
|
|
|
|
if got := len(newState.RootModule().Resources); got != 0 {
|
|
t.Fatalf("state has %d resources after destroy; want 0", got)
|
|
}
|
|
|
|
wantHookCalls := []*testHookCall{
|
|
{"PreDiff", "data.null_data_source.testing"},
|
|
{"PostDiff", "data.null_data_source.testing"},
|
|
{"PreDiff", "data.null_data_source.testing"},
|
|
{"PostDiff", "data.null_data_source.testing"},
|
|
{"PostStateUpdate", ""},
|
|
}
|
|
if !reflect.DeepEqual(hook.Calls, wantHookCalls) {
|
|
t.Errorf("wrong hook calls\ngot: %swant: %s", spew.Sdump(hook.Calls), spew.Sdump(wantHookCalls))
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/pull/5096
|
|
func TestContext2Apply_destroySkipsCBD(t *testing.T) {
|
|
// Config contains CBD resource depending on non-CBD resource, which triggers
|
|
// a cycle if they are both replaced, but should _not_ trigger a cycle when
|
|
// just doing a `terraform destroy`.
|
|
m := testModule(t, "apply-destroy-cbd")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-mod-var-provider-config")
|
|
p := func() (providers.Interface, error) {
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
return p, nil
|
|
}
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): p,
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyCrossProviders(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-cross-providers")
|
|
|
|
p_aws := testProvider("aws")
|
|
p_aws.ApplyResourceChangeFn = testApplyFn
|
|
p_aws.PlanResourceChangeFn = testDiffFn
|
|
p_aws.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"value": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p_aws),
|
|
}
|
|
|
|
ctx, m, state := getContextForApply_destroyCrossProviders(t, m, providers)
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
}
|
|
|
|
func getContextForApply_destroyCrossProviders(t *testing.T, m *configs.Config, providerFactories map[addrs.Provider]providers.Factory) (*Context, *configs.Config, *states.State) {
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.shared").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"test"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_vpc.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id": "vpc-aaabbb12", "value":"test"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: providerFactories,
|
|
})
|
|
|
|
return ctx, m, state
|
|
}
|
|
|
|
func TestContext2Apply_minimal(t *testing.T) {
|
|
m := testModule(t, "apply-minimal")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyMinimalStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_cancel(t *testing.T) {
|
|
stopped := false
|
|
|
|
m := testModule(t, "apply-cancel")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
if !stopped {
|
|
stopped = true
|
|
go ctx.Stop()
|
|
|
|
for {
|
|
if ctx.sh.Stopped() {
|
|
break
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
return testApplyFn(req)
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// Start the Apply in a goroutine
|
|
var applyDiags tfdiags.Diagnostics
|
|
stateCh := make(chan *states.State)
|
|
go func() {
|
|
state, diags := ctx.Apply(plan, m)
|
|
applyDiags = diags
|
|
|
|
stateCh <- state
|
|
}()
|
|
|
|
state := <-stateCh
|
|
// only expecting an early exit error
|
|
if !applyDiags.HasErrors() {
|
|
t.Fatal("expected early exit error")
|
|
}
|
|
|
|
for _, d := range applyDiags {
|
|
desc := d.Description()
|
|
if desc.Summary != "execution halted" {
|
|
t.Fatalf("unexpected error: %v", applyDiags.Err())
|
|
}
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCancelStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
if !p.StopCalled {
|
|
t.Fatal("stop should be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_cancelBlock(t *testing.T) {
|
|
m := testModule(t, "apply-cancel-block")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
applyCh := make(chan struct{})
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
close(applyCh)
|
|
|
|
for !ctx.sh.Stopped() {
|
|
// Wait for stop to be called. We call Gosched here so that
|
|
// the other goroutines can always be scheduled to set Stopped.
|
|
runtime.Gosched()
|
|
}
|
|
|
|
// Sleep
|
|
time.Sleep(100 * time.Millisecond)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// Start the Apply in a goroutine
|
|
var applyDiags tfdiags.Diagnostics
|
|
stateCh := make(chan *states.State)
|
|
go func() {
|
|
state, diags := ctx.Apply(plan, m)
|
|
applyDiags = diags
|
|
|
|
stateCh <- state
|
|
}()
|
|
|
|
stopDone := make(chan struct{})
|
|
go func() {
|
|
defer close(stopDone)
|
|
<-applyCh
|
|
ctx.Stop()
|
|
}()
|
|
|
|
// Make sure that stop blocks
|
|
select {
|
|
case <-stopDone:
|
|
t.Fatal("stop should block")
|
|
case <-time.After(10 * time.Millisecond):
|
|
}
|
|
|
|
// Wait for stop
|
|
select {
|
|
case <-stopDone:
|
|
case <-time.After(500 * time.Millisecond):
|
|
t.Fatal("stop should be done")
|
|
}
|
|
|
|
// Wait for apply to complete
|
|
state := <-stateCh
|
|
// only expecting an early exit error
|
|
if !applyDiags.HasErrors() {
|
|
t.Fatal("expected early exit error")
|
|
}
|
|
|
|
for _, d := range applyDiags {
|
|
desc := d.Description()
|
|
if desc.Summary != "execution halted" {
|
|
t.Fatalf("unexpected error: %v", applyDiags.Err())
|
|
}
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_cancelProvisioner(t *testing.T) {
|
|
m := testModule(t, "apply-cancel-provisioner")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
pr := testProvisioner()
|
|
pr.GetSchemaResponse = provisioners.GetSchemaResponse{
|
|
Provisioner: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
prStopped := make(chan struct{})
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
// Start the stop process
|
|
go ctx.Stop()
|
|
|
|
<-prStopped
|
|
return
|
|
}
|
|
pr.StopFn = func() error {
|
|
close(prStopped)
|
|
return nil
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// Start the Apply in a goroutine
|
|
var applyDiags tfdiags.Diagnostics
|
|
stateCh := make(chan *states.State)
|
|
go func() {
|
|
state, diags := ctx.Apply(plan, m)
|
|
applyDiags = diags
|
|
|
|
stateCh <- state
|
|
}()
|
|
|
|
// Wait for completion
|
|
state := <-stateCh
|
|
|
|
// we are expecting only an early exit error
|
|
if !applyDiags.HasErrors() {
|
|
t.Fatal("expected early exit error")
|
|
}
|
|
|
|
for _, d := range applyDiags {
|
|
desc := d.Description()
|
|
if desc.Summary != "execution halted" {
|
|
t.Fatalf("unexpected error: %v", applyDiags.Err())
|
|
}
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo: (tainted)
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
|
|
if !pr.StopCalled {
|
|
t.Fatal("stop should be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_compute(t *testing.T) {
|
|
m := testModule(t, "apply-compute")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"num": {
|
|
Type: cty.Number,
|
|
Optional: true,
|
|
},
|
|
"compute": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"compute_value": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"type": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"value": { // Populated from compute_value because compute = "value" in the config fixture
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
SetVariables: InputValues{
|
|
"value": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyComputeStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_countDecrease(t *testing.T) {
|
|
m := testModule(t, "apply-count-dec")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo": "foo","type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCountDecStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_countDecreaseToOneX(t *testing.T) {
|
|
m := testModule(t, "apply-count-dec-one")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCountDecToOneStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// https://github.com/PeoplePerHour/terraform/pull/11
|
|
//
|
|
// This tests a rare but possible situation where we have both a no-key and
|
|
// a zero-key instance of the same resource in the configuration when we
|
|
// disable count.
|
|
//
|
|
// The main way to get here is for a provider to fail to destroy the zero-key
|
|
// instance but succeed in creating the no-key instance, since those two
|
|
// can typically happen concurrently. There are various other ways to get here
|
|
// that might be considered user error, such as using "terraform state mv"
|
|
// to create a strange combination of different key types on the same resource.
|
|
//
|
|
// This test indirectly exercises an intentional interaction between
|
|
// refactoring.ImpliedMoveStatements and refactoring.ApplyMoves: we'll first
|
|
// generate an implied move statement from aws_instance.foo[0] to
|
|
// aws_instance.foo, but then refactoring.ApplyMoves should notice that and
|
|
// ignore the statement, in the same way as it would if an explicit move
|
|
// statement specified the same situation.
|
|
func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) {
|
|
m := testModule(t, "apply-count-dec-one")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "foo": "foo", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
{
|
|
got := strings.TrimSpace(legacyPlanComparisonString(state, plan.Changes))
|
|
want := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedPlanStr)
|
|
if got != want {
|
|
t.Fatalf("wrong plan result\ngot:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
{
|
|
change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo[0]"))
|
|
if change == nil {
|
|
t.Fatalf("no planned change for instance zero")
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for instance zero %s; want %s", got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
|
|
t.Errorf("wrong action reason for instance zero %s; want %s", got, want)
|
|
}
|
|
}
|
|
{
|
|
change := plan.Changes.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo"))
|
|
if change == nil {
|
|
t.Fatalf("no planned change for no-key instance")
|
|
}
|
|
if got, want := change.Action, plans.NoOp; got != want {
|
|
t.Errorf("wrong action for no-key instance %s; want %s", got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason for no-key instance %s; want %s", got, want)
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCountDecToOneCorruptedStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_countTainted(t *testing.T) {
|
|
m := testModule(t, "apply-count-tainted")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar", "type": "aws_instance", "foo": "foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
{
|
|
got := strings.TrimSpace(legacyDiffComparisonString(plan.Changes))
|
|
want := strings.TrimSpace(`
|
|
DESTROY/CREATE: aws_instance.foo[0]
|
|
foo: "foo" => "foo"
|
|
id: "bar" => "<computed>"
|
|
type: "aws_instance" => "<computed>"
|
|
CREATE: aws_instance.foo[1]
|
|
foo: "" => "foo"
|
|
id: "" => "<computed>"
|
|
type: "" => "<computed>"
|
|
`)
|
|
if got != want {
|
|
t.Fatalf("wrong plan\n\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
got := strings.TrimSpace(s.String())
|
|
want := strings.TrimSpace(`
|
|
aws_instance.foo.0:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
aws_instance.foo.1:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
`)
|
|
if got != want {
|
|
t.Fatalf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_countVariable(t *testing.T) {
|
|
m := testModule(t, "apply-count-variable")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCountVariableStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_countVariableRef(t *testing.T) {
|
|
m := testModule(t, "apply-count-variable-ref")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyCountVariableRefStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerInterpCount(t *testing.T) {
|
|
// This test ensures that a provisioner can interpolate a resource count
|
|
// even though the provisioner expression is evaluated during the plan
|
|
// walk. https://github.com/hashicorp/terraform/issues/16840
|
|
|
|
m, snap := testModuleWithSnapshot(t, "apply-provisioner-interp-count")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
pr := testProvisioner()
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
provisioners := map[string]provisioners.Factory{
|
|
"local-exec": testProvisionerFuncFixed(pr),
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
Provisioners: provisioners,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// We'll marshal and unmarshal the plan here, to ensure that we have
|
|
// a clean new context as would be created if we separately ran
|
|
// terraform plan -out=tfplan && terraform apply tfplan
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctxOpts.Providers = Providers
|
|
ctxOpts.Provisioners = provisioners
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("failed to create context for plan: %s", diags.Err())
|
|
}
|
|
|
|
// Applying the plan should now succeed
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply failed unexpectedly: %s", diags.Err())
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner was not called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_foreachVariable(t *testing.T) {
|
|
m := testModule(t, "plan-for-each-unknown-value")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("hello"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyForEachVariableStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleBasic(t *testing.T) {
|
|
m := testModule(t, "apply-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad, expected:\n%s\n\nactual:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleDestroyOrder(t *testing.T) {
|
|
m := testModule(t, "apply-module-destroy-order")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// Create a custom apply function to track the order they were destroyed
|
|
var order []string
|
|
var orderLock sync.Mutex
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
id := req.PriorState.GetAttr("id").AsString()
|
|
|
|
if id == "b" {
|
|
// Pause briefly to make any race conditions more visible, since
|
|
// missing edges here can cause undeterministic ordering.
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
orderLock.Lock()
|
|
defer orderLock.Unlock()
|
|
|
|
order = append(order, id)
|
|
resp.NewState = req.PlannedState
|
|
return resp
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Required: true},
|
|
"blah": {Type: cty.String, Optional: true},
|
|
"value": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.b").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.aws_instance.a")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"b", "a"}
|
|
if !reflect.DeepEqual(order, expected) {
|
|
t.Errorf("wrong order\ngot: %#v\nwant: %#v", order, expected)
|
|
}
|
|
|
|
{
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleDestroyOrderStr)
|
|
if actual != expected {
|
|
t.Errorf("wrong final state\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleInheritAlias(t *testing.T) {
|
|
m := testModule(t, "apply-module-provider-inherit-alias")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
return
|
|
}
|
|
|
|
root := req.Config.GetAttr("root")
|
|
if !root.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root"))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
module.child:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"].eu
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_orphanResource(t *testing.T) {
|
|
// This is a two-step test:
|
|
// 1. Apply a configuration with resources that have count set.
|
|
// This should place the empty resource object in the state to record
|
|
// that each exists, and record any instances.
|
|
// 2. Apply an empty configuration against the same state, which should
|
|
// then clean up both the instances and the containing resource objects.
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Step 1: create the resources and instances
|
|
m := testModule(t, "apply-orphan-resource")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// At this point both resources should be recorded in the state, along
|
|
// with the single instance associated with test_thing.one.
|
|
want := states.BuildState(func(s *states.SyncState) {
|
|
providerAddr := addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
}
|
|
oneAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_thing",
|
|
Name: "one",
|
|
}.Absolute(addrs.RootModuleInstance)
|
|
s.SetResourceProvider(oneAddr, providerAddr)
|
|
s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
}, providerAddr)
|
|
})
|
|
|
|
if state.String() != want.String() {
|
|
t.Fatalf("wrong state after step 1\n%s", cmp.Diff(want, state))
|
|
}
|
|
|
|
// Step 2: update with an empty config, to destroy everything
|
|
m = testModule(t, "empty")
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
{
|
|
addr := mustResourceInstanceAddr("test_thing.one[0]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// The state should now be _totally_ empty, with just an empty root module
|
|
// (since that always exists) and no resources at all.
|
|
want = states.NewState()
|
|
if !cmp.Equal(state, want) {
|
|
t.Fatalf("wrong state after step 2\ngot: %swant: %s", spew.Sdump(state), spew.Sdump(want))
|
|
}
|
|
|
|
}
|
|
|
|
func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) {
|
|
m := testModule(t, "apply-module-provider-inherit-alias-orphan")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
return
|
|
}
|
|
|
|
root := req.Config.GetAttr("root")
|
|
if !root.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("child should not get root"))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Create a state with an orphan module
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
{
|
|
addr := mustResourceInstanceAddr("module.child.aws_instance.bar")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
// This should ideally be ResourceInstanceDeleteBecauseNoModule, but
|
|
// the codepath deciding this doesn't currently have enough information
|
|
// to differentiate, and so this is a compromise.
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !p.ConfigureProviderCalled {
|
|
t.Fatal("must call configure")
|
|
}
|
|
|
|
checkStateString(t, state, "<no state>")
|
|
}
|
|
|
|
func TestContext2Apply_moduleOrphanProvider(t *testing.T) {
|
|
m := testModule(t, "apply-module-orphan-provider-inherit")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found"))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Create a state with an orphan module
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) {
|
|
m := testModule(t, "apply-module-orphan-provider-inherit")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found"))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Create a state with an orphan module that is nested (grandchild)
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleGrandchildProvider(t *testing.T) {
|
|
m := testModule(t, "apply-module-grandchild-provider-inherit")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var callLock sync.Mutex
|
|
called := false
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found"))
|
|
}
|
|
|
|
callLock.Lock()
|
|
called = true
|
|
callLock.Unlock()
|
|
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
callLock.Lock()
|
|
defer callLock.Unlock()
|
|
if called != true {
|
|
t.Fatalf("err: configure never called")
|
|
}
|
|
}
|
|
|
|
// This tests an issue where all the providers in a module but not
|
|
// in the root weren't being added to the root properly. In this test
|
|
// case: aws is explicitly added to root, but "test" should be added to.
|
|
// With the bug, it wasn't.
|
|
func TestContext2Apply_moduleOnlyProvider(t *testing.T) {
|
|
m := testModule(t, "apply-module-only-provider")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pTest := testProvider("test")
|
|
pTest.ApplyResourceChangeFn = testApplyFn
|
|
pTest.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleOnlyProviderStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleProviderAlias(t *testing.T) {
|
|
m := testModule(t, "apply-module-provider-alias")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleProviderAliasStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) {
|
|
m := testModule(t, "apply-module-provider-alias")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.ConfigResource{
|
|
Module: addrs.RootModule,
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "nonexistent",
|
|
Name: "thing",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
<no state>
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleProviderCloseNested(t *testing.T) {
|
|
m := testModule(t, "apply-module-provider-close-nested")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// Tests that variables used as module vars that reference data that
|
|
// already exists in the state and requires no diff works properly. This
|
|
// fixes an issue faced where module variables were pruned because they were
|
|
// accessing "non-existent" resources (they existed, just not in the graph
|
|
// cause they weren't in the diff).
|
|
func TestContext2Apply_moduleVarRefExisting(t *testing.T) {
|
|
m := testModule(t, "apply-ref-existing")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleVarRefExistingStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleVarResourceCount(t *testing.T) {
|
|
m := testModule(t, "apply-module-var-resource-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(2),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(5),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// GH-819
|
|
func TestContext2Apply_moduleBool(t *testing.T) {
|
|
m := testModule(t, "apply-module-bool")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyModuleBoolStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// Tests that a module can be targeted and everything is properly created.
|
|
// This adds to the plan test to also just verify that apply works.
|
|
func TestContext2Apply_moduleTarget(t *testing.T) {
|
|
m := testModule(t, "plan-targeted-cross-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("B", addrs.NoKey),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
module.A:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
type = aws_instance
|
|
|
|
Outputs:
|
|
|
|
value = foo
|
|
module.B:
|
|
aws_instance.bar:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
|
|
Dependencies:
|
|
module.A.aws_instance.foo
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_multiProvider(t *testing.T) {
|
|
m := testModule(t, "apply-multi-provider")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
pDO := testProvider("do")
|
|
pDO.ApplyResourceChangeFn = testApplyFn
|
|
pDO.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("do"): testProviderFuncFixed(pDO),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) < 2 {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyMultiProviderStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_multiProviderDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-multi-provider-destroy")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"addr": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
p2 := testProvider("vault")
|
|
p2.ApplyResourceChangeFn = testApplyFn
|
|
p2.PlanResourceChangeFn = testDiffFn
|
|
p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"vault_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
var state *states.State
|
|
|
|
// First, create the instances
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
state = s
|
|
}
|
|
|
|
// Destroy them
|
|
{
|
|
// Verify that aws_instance.bar is destroyed first
|
|
var checked bool
|
|
var called int32
|
|
var lock sync.Mutex
|
|
applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
if req.TypeName == "aws_instance" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called"))
|
|
return resp
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
// Set the apply functions
|
|
p.ApplyResourceChangeFn = applyFn
|
|
p2.ApplyResourceChangeFn = applyFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !checked {
|
|
t.Fatal("should be checked")
|
|
}
|
|
|
|
state = s
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
}
|
|
|
|
// This is like the multiProviderDestroy test except it tests that
|
|
// dependent resources within a child module that inherit provider
|
|
// configuration are still destroyed first.
|
|
func TestContext2Apply_multiProviderDestroyChild(t *testing.T) {
|
|
m := testModule(t, "apply-multi-provider-destroy-child")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
p2 := testProvider("vault")
|
|
p2.ApplyResourceChangeFn = testApplyFn
|
|
p2.PlanResourceChangeFn = testDiffFn
|
|
p2.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"vault_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
var state *states.State
|
|
|
|
// First, create the instances
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
state = s
|
|
}
|
|
|
|
// Destroy them
|
|
{
|
|
// Verify that aws_instance.bar is destroyed first
|
|
var checked bool
|
|
var called int32
|
|
var lock sync.Mutex
|
|
applyFn := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
if req.TypeName == "aws_instance" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 0 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called"))
|
|
return resp
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
// Set the apply functions
|
|
p.ApplyResourceChangeFn = applyFn
|
|
p2.ApplyResourceChangeFn = applyFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("vault"): testProviderFuncFixed(p2),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !checked {
|
|
t.Fatal("should be checked")
|
|
}
|
|
|
|
state = s
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_multiVar(t *testing.T) {
|
|
m := testModule(t, "apply-multi-var")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// First, apply with a count of 3
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(3),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := state.RootModule().OutputValues["output"]
|
|
expected := cty.StringVal("bar0,bar1,bar2")
|
|
if actual == nil || actual.Value != expected {
|
|
t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected)
|
|
}
|
|
|
|
t.Logf("Initial state: %s", state.String())
|
|
|
|
// Apply again, reduce the count to 1
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("End state: %s", state.String())
|
|
|
|
actual := state.RootModule().OutputValues["output"]
|
|
if actual == nil {
|
|
t.Fatal("missing output")
|
|
}
|
|
|
|
expected := cty.StringVal("bar0")
|
|
if actual.Value != expected {
|
|
t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is a holistic test of multi-var (aka "splat variable") handling
|
|
// across several different Terraform subsystems. This is here because
|
|
// historically there were quirky differences in handling across different
|
|
// parts of Terraform and so here we want to assert the expected behavior and
|
|
// ensure that it remains consistent in future.
|
|
func TestContext2Apply_multiVarComprehensive(t *testing.T) {
|
|
m := testModule(t, "apply-multi-var-comprehensive")
|
|
p := testProvider("test")
|
|
|
|
configs := map[string]cty.Value{}
|
|
var configsLock sync.Mutex
|
|
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
proposed := req.ProposedNewState
|
|
configsLock.Lock()
|
|
defer configsLock.Unlock()
|
|
key := proposed.GetAttr("key").AsString()
|
|
// This test was originally written using the legacy p.PlanResourceChangeFn interface,
|
|
// and so the assertions below expect an old-style ResourceConfig, which
|
|
// we'll construct via our shim for now to avoid rewriting all of the
|
|
// assertions.
|
|
configs[key] = req.ProposedNewState
|
|
|
|
retVals := make(map[string]cty.Value)
|
|
for it := proposed.ElementIterator(); it.Next(); {
|
|
idxVal, val := it.Element()
|
|
idx := idxVal.AsString()
|
|
|
|
switch idx {
|
|
case "id":
|
|
retVals[idx] = cty.UnknownVal(cty.String)
|
|
case "name":
|
|
retVals[idx] = cty.StringVal(key)
|
|
default:
|
|
retVals[idx] = val
|
|
}
|
|
}
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(retVals),
|
|
}
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"key": {Type: cty.String, Required: true},
|
|
|
|
"source_id": {Type: cty.String, Optional: true},
|
|
"source_name": {Type: cty.String, Optional: true},
|
|
"first_source_id": {Type: cty.String, Optional: true},
|
|
"first_source_name": {Type: cty.String, Optional: true},
|
|
"source_ids": {Type: cty.List(cty.String), Optional: true},
|
|
"source_names": {Type: cty.List(cty.String), Optional: true},
|
|
"source_ids_from_func": {Type: cty.List(cty.String), Optional: true},
|
|
"source_names_from_func": {Type: cty.List(cty.String), Optional: true},
|
|
"source_ids_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true},
|
|
"source_names_wrapped": {Type: cty.List(cty.List(cty.String)), Optional: true},
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
"name": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// First, apply with a count of 3
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(3),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
checkConfig := func(key string, want cty.Value) {
|
|
configsLock.Lock()
|
|
defer configsLock.Unlock()
|
|
|
|
got, ok := configs[key]
|
|
if !ok {
|
|
t.Errorf("no config recorded for %s; expected a configuration", key)
|
|
return
|
|
}
|
|
|
|
t.Run("config for "+key, func(t *testing.T) {
|
|
for _, problem := range deep.Equal(got, want) {
|
|
t.Errorf(problem)
|
|
}
|
|
})
|
|
}
|
|
|
|
checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{
|
|
"source_id": cty.UnknownVal(cty.String),
|
|
"source_name": cty.StringVal("source.0"),
|
|
}))
|
|
checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{
|
|
"source_id": cty.UnknownVal(cty.String),
|
|
"source_name": cty.StringVal("source.2"),
|
|
}))
|
|
checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{
|
|
"source_id": cty.UnknownVal(cty.String),
|
|
"source_name": cty.StringVal("source.0"),
|
|
}))
|
|
checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{
|
|
"source_id": cty.UnknownVal(cty.String),
|
|
"source_name": cty.StringVal("source.2"),
|
|
}))
|
|
checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{
|
|
"source_ids": cty.ListVal([]cty.Value{
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
}),
|
|
"source_names": cty.ListVal([]cty.Value{
|
|
cty.StringVal("source.0"),
|
|
cty.StringVal("source.1"),
|
|
cty.StringVal("source.2"),
|
|
}),
|
|
"source_ids_from_func": cty.UnknownVal(cty.String),
|
|
"source_names_from_func": cty.ListVal([]cty.Value{
|
|
cty.StringVal("source.0"),
|
|
cty.StringVal("source.1"),
|
|
cty.StringVal("source.2"),
|
|
}),
|
|
"source_ids_wrapped": cty.ListVal([]cty.Value{
|
|
cty.ListVal([]cty.Value{
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
}),
|
|
}),
|
|
"source_names_wrapped": cty.ListVal([]cty.Value{
|
|
cty.ListVal([]cty.Value{
|
|
cty.StringVal("source.0"),
|
|
cty.StringVal("source.1"),
|
|
cty.StringVal("source.2"),
|
|
}),
|
|
}),
|
|
"first_source_id": cty.UnknownVal(cty.String),
|
|
"first_source_name": cty.StringVal("source.0"),
|
|
}))
|
|
checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{
|
|
"source_ids": cty.ListVal([]cty.Value{
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
}),
|
|
"source_names": cty.ListVal([]cty.Value{
|
|
cty.StringVal("source.0"),
|
|
cty.StringVal("source.1"),
|
|
cty.StringVal("source.2"),
|
|
}),
|
|
"source_ids_wrapped": cty.ListVal([]cty.Value{
|
|
cty.ListVal([]cty.Value{
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
cty.UnknownVal(cty.String),
|
|
}),
|
|
}),
|
|
"source_names_wrapped": cty.ListVal([]cty.Value{
|
|
cty.ListVal([]cty.Value{
|
|
cty.StringVal("source.0"),
|
|
cty.StringVal("source.1"),
|
|
cty.StringVal("source.2"),
|
|
}),
|
|
}),
|
|
}))
|
|
|
|
t.Run("apply", func(t *testing.T) {
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("error during apply: %s", diags.Err())
|
|
}
|
|
|
|
want := map[string]interface{}{
|
|
"source_ids": []interface{}{"foo", "foo", "foo"},
|
|
"source_names": []interface{}{
|
|
"source.0",
|
|
"source.1",
|
|
"source.2",
|
|
},
|
|
}
|
|
got := map[string]interface{}{}
|
|
for k, s := range state.RootModule().OutputValues {
|
|
got[k] = hcl2shim.ConfigValueFromHCL2(s.Value)
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf(
|
|
"wrong outputs\ngot: %s\nwant: %s",
|
|
spew.Sdump(got), spew.Sdump(want),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test that multi-var (splat) access is ordered by count, not by
|
|
// value.
|
|
func TestContext2Apply_multiVarOrder(t *testing.T) {
|
|
m := testModule(t, "apply-multi-var-order")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// First, apply with a count of 3
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("State: %s", state.String())
|
|
|
|
actual := state.RootModule().OutputValues["should-be-11"]
|
|
expected := cty.StringVal("index-11")
|
|
if actual == nil || actual.Value != expected {
|
|
t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected)
|
|
}
|
|
}
|
|
|
|
// Test that multi-var (splat) access is ordered by count, not by
|
|
// value, through interpolations.
|
|
func TestContext2Apply_multiVarOrderInterp(t *testing.T) {
|
|
m := testModule(t, "apply-multi-var-order-interp")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// First, apply with a count of 3
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("State: %s", state.String())
|
|
|
|
actual := state.RootModule().OutputValues["should-be-11"]
|
|
expected := cty.StringVal("baz-index-11")
|
|
if actual == nil || actual.Value != expected {
|
|
t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected)
|
|
}
|
|
}
|
|
|
|
// Based on GH-10440 where a graph edge wasn't properly being created
|
|
// between a modified resource and a count instance being destroyed.
|
|
func TestContext2Apply_multiVarCountDec(t *testing.T) {
|
|
var s *states.State
|
|
|
|
// First create resources. Nothing sneaky here.
|
|
{
|
|
m := testModule(t, "apply-multi-var-count-dec")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
log.Print("\n========\nStep 1 Plan\n========")
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(2),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
log.Print("\n========\nStep 1 Apply\n========")
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("Step 1 state:\n%s", state)
|
|
|
|
s = state
|
|
}
|
|
|
|
// Decrease the count by 1 and verify that everything happens in the
|
|
// right order.
|
|
m := testModule(t, "apply-multi-var-count-dec")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// Verify that aws_instance.bar is modified first and nothing
|
|
// else happens at the same time.
|
|
{
|
|
var checked bool
|
|
var called int32
|
|
var lock sync.Mutex
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
if !req.PlannedState.IsNull() {
|
|
s := req.PlannedState.AsValueMap()
|
|
if ami, ok := s["ami"]; ok && !ami.IsNull() && ami.AsString() == "special" {
|
|
checked = true
|
|
|
|
// Sleep to allow parallel execution
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify that called is 0 (dep not called)
|
|
if atomic.LoadInt32(&called) != 1 {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("nothing else should be called"))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
atomic.AddInt32(&called, 1)
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
log.Print("\n========\nStep 2 Plan\n========")
|
|
plan, diags := ctx.Plan(m, s, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"num": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
t.Logf("Step 2 plan:\n%s", legacyDiffComparisonString(plan.Changes))
|
|
|
|
log.Print("\n========\nStep 2 Apply\n========")
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
if !checked {
|
|
t.Error("apply never called")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that we can resolve a multi-var (splat) for the first resource
|
|
// created in a non-root module, which happens when the module state doesn't
|
|
// exist yet.
|
|
// https://github.com/hashicorp/terraform/issues/14438
|
|
func TestContext2Apply_multiVarMissingState(t *testing.T) {
|
|
m := testModule(t, "apply-multi-var-missing-state")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"a_ids": {Type: cty.String, Optional: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// First, apply with a count of 3
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
// Before the relevant bug was fixed, Terraform would panic during apply.
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply failed: %s", diags.Err())
|
|
}
|
|
|
|
// If we get here with no errors or panics then our test was successful.
|
|
}
|
|
|
|
func TestContext2Apply_outputOrphan(t *testing.T) {
|
|
m := testModule(t, "apply-output-orphan")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetOutputValue("foo", cty.StringVal("bar"), false)
|
|
root.SetOutputValue("bar", cty.StringVal("baz"), false)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputOrphanStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputOrphanModule(t *testing.T) {
|
|
m := testModule(t, "apply-output-orphan-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputOrphanModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
|
|
// now apply with no module in the config, which should remove the
|
|
// remaining output
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
emptyConfig := configs.NewEmptyConfig()
|
|
|
|
// NOTE: While updating this test to pass the state in as a Plan argument,
|
|
// rather than into the testContext2 call above, it previously said
|
|
// State: state.DeepCopy(), which is a little weird since we just
|
|
// created "s" above as the result of the previous apply, but I've preserved
|
|
// it to avoid changing the flow of this test in case that's important
|
|
// for some reason.
|
|
plan, diags = ctx.Plan(emptyConfig, state.DeepCopy(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, emptyConfig)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if !state.Empty() {
|
|
t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(state))
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_providerComputedVar(t *testing.T) {
|
|
m := testModule(t, "apply-provider-computed")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
pTest := testProvider("test")
|
|
pTest.ApplyResourceChangeFn = testApplyFn
|
|
pTest.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest),
|
|
},
|
|
})
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found"))
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_providerConfigureDisabled(t *testing.T) {
|
|
m := testModule(t, "apply-provider-configure-disabled")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("value")
|
|
if val.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("value is not found"))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
if !p.ConfigureProviderCalled {
|
|
t.Fatal("configure never called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerModule(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-module")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
pr := testProvisioner()
|
|
pr.GetSchemaResponse = provisioners.GetSchemaResponse{
|
|
Provisioner: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_Provisioner_compute(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-compute")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
|
|
val := req.Config.GetAttr("command").AsString()
|
|
if val != "computed_value" {
|
|
t.Fatalf("bad value for foo: %q", val)
|
|
}
|
|
req.UIOutput.Output(fmt.Sprintf("Executing: %q", val))
|
|
|
|
return
|
|
}
|
|
h := new(MockHook)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"value": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
// Verify output was rendered
|
|
if !h.ProvisionOutputCalled {
|
|
t.Fatalf("ProvisionOutput hook not called")
|
|
}
|
|
if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want {
|
|
t.Errorf("expected output to be %q, but was %q", want, got)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerCreateFail(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-fail-create")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
resp := testApplyFn(req)
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
|
|
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(testTerraformApplyProvisionerFailCreateStr)
|
|
if got != want {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-fail-create")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateNoIdStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerFail(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-fail")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr := testProvisioner()
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION"))
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerFailStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-fail-create-before")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("EXPLOSION"))
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","require_new":"abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerFailCreateBeforeDestroyStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n:got\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_error_createBeforeDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-error-create-before")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "require_new": "abc","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("placeholder error from ApplyFn"))
|
|
return
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
if got, want := diags.Err().Error(), "placeholder error from ApplyFn"; got != want {
|
|
// We're looking for our artificial error from ApplyFn above, whose
|
|
// message is literally "placeholder error from ApplyFn".
|
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyErrorCreateBeforeDestroyStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-error-create-before")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar", "require_new": "abc"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
// Fail the destroy!
|
|
if req.PlannedState.IsNull() {
|
|
resp.NewState = req.PriorState
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
|
|
return
|
|
}
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyErrorDestroyCreateBeforeDestroyStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad: actual:\n%s\n\nexpected:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-multi-depose-create-before-destroy")
|
|
p := testProvider("aws")
|
|
ps := map[addrs.Provider]providers.Factory{addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p)}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"require_new": {Type: cty.String, Optional: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.web").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
createdInstanceId := "bar"
|
|
// Create works
|
|
createFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
s := req.PlannedState.AsValueMap()
|
|
s["id"] = cty.StringVal(createdInstanceId)
|
|
resp.NewState = cty.ObjectVal(s)
|
|
return
|
|
}
|
|
|
|
// Destroy starts broken
|
|
destroyFunc := func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
resp.NewState = req.PriorState
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy failed"))
|
|
return
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
if req.PlannedState.IsNull() {
|
|
return destroyFunc(req)
|
|
} else {
|
|
return createFunc(req)
|
|
}
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"require_new": &InputValue{
|
|
Value: cty.StringVal("yes"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
// Destroy is broken, so even though CBD successfully replaces the instance,
|
|
// we'll have to save the Deposed instance to destroy later
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.web: (1 deposed)
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
require_new = yes
|
|
Deposed ID 1 = foo
|
|
`)
|
|
|
|
createdInstanceId = "baz"
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"require_new": &InputValue{
|
|
Value: cty.StringVal("baz"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
// We're replacing the primary instance once again. Destroy is _still_
|
|
// broken, so the Deposed list gets longer
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
// For this one we can't rely on checkStateString because its result is
|
|
// not deterministic when multiple deposed objects are present. Instead,
|
|
// we will probe the state object directly.
|
|
{
|
|
is := state.RootModule().Resources["aws_instance.web"].Instances[addrs.NoKey]
|
|
if is.Current == nil {
|
|
t.Fatalf("no current object for aws_instance web; should have one")
|
|
}
|
|
if !bytes.Contains(is.Current.AttrsJSON, []byte("baz")) {
|
|
t.Fatalf("incorrect current object attrs %s; want id=baz", is.Current.AttrsJSON)
|
|
}
|
|
if got, want := len(is.Deposed), 2; got != want {
|
|
t.Fatalf("wrong number of deposed instances %d; want %d", got, want)
|
|
}
|
|
var foos, bars int
|
|
for _, obj := range is.Deposed {
|
|
if bytes.Contains(obj.AttrsJSON, []byte("foo")) {
|
|
foos++
|
|
}
|
|
if bytes.Contains(obj.AttrsJSON, []byte("bar")) {
|
|
bars++
|
|
}
|
|
}
|
|
if got, want := foos, 1; got != want {
|
|
t.Fatalf("wrong number of deposed instances with id=foo %d; want %d", got, want)
|
|
}
|
|
if got, want := bars, 1; got != want {
|
|
t.Fatalf("wrong number of deposed instances with id=bar %d; want %d", got, want)
|
|
}
|
|
}
|
|
|
|
// Destroy partially fixed!
|
|
destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
s := req.PriorState.AsValueMap()
|
|
id := s["id"].AsString()
|
|
if id == "foo" || id == "baz" {
|
|
resp.NewState = cty.NullVal(req.PriorState.Type())
|
|
} else {
|
|
resp.NewState = req.PriorState
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("destroy partially failed"))
|
|
}
|
|
return
|
|
}
|
|
|
|
createdInstanceId = "qux"
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"require_new": &InputValue{
|
|
Value: cty.StringVal("qux"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
// Expect error because 1/2 of Deposed destroys failed
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
// foo and baz are now gone, bar sticks around
|
|
checkStateString(t, state, `
|
|
aws_instance.web: (1 deposed)
|
|
ID = qux
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
require_new = qux
|
|
Deposed ID 1 = bar
|
|
`)
|
|
|
|
// Destroy working fully!
|
|
destroyFunc = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
resp.NewState = cty.NullVal(req.PriorState.Type())
|
|
return
|
|
}
|
|
|
|
createdInstanceId = "quux"
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: ps,
|
|
})
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"require_new": &InputValue{
|
|
Value: cty.StringVal("quux"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal("should not have error:", diags.Err())
|
|
}
|
|
|
|
// And finally the state is clean
|
|
checkStateString(t, state, `
|
|
aws_instance.web:
|
|
ID = quux
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
require_new = quux
|
|
`)
|
|
}
|
|
|
|
// Verify that a normal provisioner with on_failure "continue" set won't
|
|
// taint the resource and continues executing.
|
|
func TestContext2Apply_provisionerFailContinue(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-fail-continue")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error"))
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
type = aws_instance
|
|
`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
// Verify that a normal provisioner with on_failure "continue" records
|
|
// the error with the hook.
|
|
func TestContext2Apply_provisionerFailContinueHook(t *testing.T) {
|
|
h := new(MockHook)
|
|
m := testModule(t, "apply-provisioner-fail-continue")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error"))
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
if !h.PostProvisionInstanceStepCalled {
|
|
t.Fatal("PostProvisionInstanceStep not called")
|
|
}
|
|
if h.PostProvisionInstanceStepErrorArg == nil {
|
|
t.Fatal("should have error")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-destroy")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command").AsString()
|
|
if val != "destroy a bar" {
|
|
t.Fatalf("bad value for foo: %q", val)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.RootModule()
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
// Verify that on destroy provisioner failure, nothing happens to the instance
|
|
func TestContext2Apply_provisionerDestroyFail(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-destroy")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error"))
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.RootModule()
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo["a"]:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
// Verify that on destroy provisioner failure with "continue" that
|
|
// we continue to the next provisioner.
|
|
func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-destroy-continue")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var l sync.Mutex
|
|
var calls []string
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.IsNull() {
|
|
t.Fatalf("bad value for foo: %#v", val)
|
|
}
|
|
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
calls = append(calls, val.AsString())
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error"))
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.RootModule()
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
expected := []string{"one", "two"}
|
|
if !reflect.DeepEqual(calls, expected) {
|
|
t.Fatalf("wrong commands\ngot: %#v\nwant: %#v", calls, expected)
|
|
}
|
|
}
|
|
|
|
// Verify that on destroy provisioner failure with "continue" that
|
|
// we continue to the next provisioner. But if the next provisioner defines
|
|
// to fail, then we fail after running it.
|
|
func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-destroy-fail")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var l sync.Mutex
|
|
var calls []string
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.IsNull() {
|
|
t.Fatalf("bad value for foo: %#v", val)
|
|
}
|
|
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
calls = append(calls, val.AsString())
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provisioner error"))
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("apply succeeded; wanted error from second provisioner")
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
expected := []string{"one", "two"}
|
|
if !reflect.DeepEqual(calls, expected) {
|
|
t.Fatalf("bad: %#v", calls)
|
|
}
|
|
}
|
|
|
|
// Verify destroy provisioners are not run for tainted instances.
|
|
func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-destroy")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
destroyCalled := false
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
expected := "create a b"
|
|
val := req.Config.GetAttr("command")
|
|
if val.AsString() != expected {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.RootModule()
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr(`aws_instance.foo["a"]`).Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"input": &InputValue{
|
|
Value: cty.MapVal(map[string]cty.Value{
|
|
"a": cty.StringVal("b"),
|
|
}),
|
|
SourceType: ValueFromInput,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo["a"]:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = bar
|
|
type = aws_instance
|
|
`)
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
if destroyCalled {
|
|
t.Fatal("destroy should not be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-resource-ref")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
pr := testProvisioner()
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.AsString() != "2" {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerResourceRefStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerSelfRef(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-self-ref")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.AsString() != "bar" {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerSelfRefStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) {
|
|
var lock sync.Mutex
|
|
commands := make([]string, 0, 5)
|
|
|
|
m := testModule(t, "apply-provisioner-multi-self-ref")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
val := req.Config.GetAttr("command")
|
|
if val.IsNull() {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
commands = append(commands, val.AsString())
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
// Verify our result
|
|
sort.Strings(commands)
|
|
expectedCommands := []string{"number 0", "number 1", "number 2"}
|
|
if !reflect.DeepEqual(commands, expectedCommands) {
|
|
t.Fatalf("bad: %#v", commands)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) {
|
|
var lock sync.Mutex
|
|
order := make([]string, 0, 5)
|
|
|
|
m := testModule(t, "apply-provisioner-multi-self-ref-single")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
val := req.Config.GetAttr("order")
|
|
if val.IsNull() {
|
|
t.Fatalf("no val for order")
|
|
}
|
|
|
|
order = append(order, val.AsString())
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerMultiSelfRefSingleStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
|
|
// Verify our result
|
|
sort.Strings(order)
|
|
expectedOrder := []string{"0", "1", "2"}
|
|
if !reflect.DeepEqual(order, expectedOrder) {
|
|
t.Fatalf("bad: %#v", order)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-explicit-self-ref")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.IsNull() || val.AsString() != "bar" {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner not invoked")
|
|
}
|
|
}
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerForEachSelfRef(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-for-each-self")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
val := req.Config.GetAttr("command")
|
|
if val.IsNull() {
|
|
t.Fatalf("bad value for command: %#v", val)
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// Provisioner should NOT run on a diff, only create
|
|
func TestContext2Apply_Provisioner_Diff(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-diff")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerDiffStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner was not called on first apply")
|
|
}
|
|
pr.ProvisionResourceCalled = false
|
|
|
|
// Change the state to force a diff
|
|
mod := state.RootModule()
|
|
obj := mod.Resources["aws_instance.bar"].Instances[addrs.NoKey].Current
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(obj.AttrsJSON, &attrs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
attrs["foo"] = "baz"
|
|
obj.AttrsJSON, err = json.Marshal(attrs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Re-create context with state
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state2, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
|
|
actual = strings.TrimSpace(state2.String())
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was NOT invoked
|
|
if pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner was called on second apply; should not have been")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputDiffVars(t *testing.T) {
|
|
m := testModule(t, "apply-good")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.baz").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
//func(info *InstanceInfo, s *InstanceState, rc *ResourceConfig) (*InstanceDiff, error) {
|
|
// d := &InstanceDiff{
|
|
// Attributes: map[string]*ResourceAttrDiff{},
|
|
// }
|
|
// if new, ok := rc.Get("value"); ok {
|
|
// d.Attributes["value"] = &ResourceAttrDiff{
|
|
// New: new.(string),
|
|
// }
|
|
// }
|
|
// if new, ok := rc.Get("foo"); ok {
|
|
// d.Attributes["foo"] = &ResourceAttrDiff{
|
|
// New: new.(string),
|
|
// }
|
|
// } else if rc.IsComputed("foo") {
|
|
// d.Attributes["foo"] = &ResourceAttrDiff{
|
|
// NewComputed: true,
|
|
// Type: DiffAttrOutput, // This doesn't actually really do anything anymore, but this test originally set it.
|
|
// }
|
|
// }
|
|
// if new, ok := rc.Get("num"); ok {
|
|
// d.Attributes["num"] = &ResourceAttrDiff{
|
|
// New: fmt.Sprintf("%#v", new),
|
|
// }
|
|
// }
|
|
// return d, nil
|
|
//}
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
}
|
|
|
|
func TestContext2Apply_destroyX(t *testing.T) {
|
|
m := testModule(t, "apply-destroy")
|
|
h := new(HookRecordApplyOrder)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Next, plan and apply a destroy operation
|
|
h.Active = true
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Test that things were destroyed
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Test that things were destroyed _in the right order_
|
|
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
|
|
actual2 := h.IDs
|
|
if !reflect.DeepEqual(actual2, expected2) {
|
|
t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyOrder(t *testing.T) {
|
|
m := testModule(t, "apply-destroy")
|
|
h := new(HookRecordApplyOrder)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("State 1: %s", state)
|
|
|
|
// Next, plan and apply a destroy
|
|
h.Active = true
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Test that things were destroyed
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Test that things were destroyed _in the right order_
|
|
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
|
|
actual2 := h.IDs
|
|
if !reflect.DeepEqual(actual2, expected2) {
|
|
t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2)
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/2767
|
|
func TestContext2Apply_destroyModulePrefix(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-module-resource-prefix")
|
|
h := new(MockHook)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Verify that we got the apply info correct
|
|
if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" {
|
|
t.Fatalf("bad: %s", v)
|
|
}
|
|
|
|
// Next, plan and apply a destroy operation and reset the hook
|
|
h = new(MockHook)
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Test that things were destroyed
|
|
if v := h.PreApplyAddr.String(); v != "module.child.aws_instance.foo" {
|
|
t.Fatalf("bad: %s", v)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyNestedModule(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-nested-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Test that things were destroyed
|
|
actual := strings.TrimSpace(s.String())
|
|
if actual != "<no state>" {
|
|
t.Fatalf("expected no state, got: %s", actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-deeply-nested-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Test that things were destroyed
|
|
if !s.Empty() {
|
|
t.Fatalf("wrong final state %s\nwant empty state", spew.Sdump(s))
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/5440
|
|
func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-destroy-module-with-attrs")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf("Step 1 plan: %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errs: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("Step 1 state: %s", state)
|
|
}
|
|
|
|
h := new(HookRecordApplyOrder)
|
|
h.Active = true
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy plan err: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("Step 2 plan: %s", legacyDiffComparisonString(plan.Changes))
|
|
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply err: %s", diags.Err())
|
|
}
|
|
|
|
t.Logf("Step 2 state: %s", state)
|
|
}
|
|
|
|
//Test that things were destroyed
|
|
if state.HasManagedResourceInstanceObjects() {
|
|
t.Fatal("expected empty state, got:", state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
h := new(HookRecordApplyOrder)
|
|
h.Active = true
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy plan err: %s", diags.Err())
|
|
}
|
|
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers =
|
|
map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
//Test that things were destroyed
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
<no state>`)
|
|
if actual != expected {
|
|
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-mod-var-and-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan err: %s", diags)
|
|
}
|
|
if len(diags) != 1 {
|
|
// Should have one warning that -target is in effect.
|
|
t.Fatalf("got %d diagnostics in plan; want 1", len(diags))
|
|
}
|
|
if got, want := diags[0].Severity(), tfdiags.Warning; got != want {
|
|
t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
|
|
}
|
|
if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want {
|
|
t.Errorf("wrong diagnostic summary %#v; want %#v", got, want)
|
|
}
|
|
|
|
// Destroy, targeting the module explicitly
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply err: %s", diags)
|
|
}
|
|
if len(diags) != 1 {
|
|
t.Fatalf("got %d diagnostics; want 1", len(diags))
|
|
}
|
|
if got, want := diags[0].Severity(), tfdiags.Warning; got != want {
|
|
t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
|
|
}
|
|
if got, want := diags[0].Description().Summary, "Applied changes may be incomplete"; got != want {
|
|
t.Errorf("wrong diagnostic summary %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
//Test that things were destroyed
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`<no state>`)
|
|
if actual != expected {
|
|
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-destroy-mod-var-and-count-nested")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
h := new(HookRecordApplyOrder)
|
|
h.Active = true
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy plan err: %s", diags.Err())
|
|
}
|
|
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers =
|
|
map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
//Test that things were destroyed
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
<no state>`)
|
|
if actual != expected {
|
|
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyOutputs(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-outputs")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
// add the required id
|
|
m := req.Config.AsValueMap()
|
|
m["id"] = cty.StringVal("foo")
|
|
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(m),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// Next, plan and apply a destroy operation
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) > 0 {
|
|
t.Fatalf("expected no resources, got: %#v", mod)
|
|
}
|
|
|
|
// destroying again should produce no errors
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyOrphan(t *testing.T) {
|
|
m := testModule(t, "apply-error")
|
|
p := testProvider("aws")
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.baz").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
if _, ok := mod.Resources["aws_instance.baz"]; ok {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-provisioner")
|
|
p := testProvider("aws")
|
|
pr := testProvisioner()
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if pr.ProvisionResourceCalled {
|
|
t.Fatal("provisioner should not be called")
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace("<no state>")
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_error(t *testing.T) {
|
|
errored := false
|
|
|
|
m := testModule(t, "apply-error")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
if errored {
|
|
resp.NewState = req.PlannedState
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
|
|
return
|
|
}
|
|
errored = true
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyErrorStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorDestroy(t *testing.T) {
|
|
m := testModule(t, "empty")
|
|
p := testProvider("test")
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
// Should actually be called for this test, because Terraform Core
|
|
// constructs the plan for a destroy operation itself.
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
// The apply (in this case, a destroy) always fails, so we can verify
|
|
// that the object stays in the state after a destroy fails even though
|
|
// we aren't returning a new state object here.
|
|
return providers.ApplyResourceChangeResponse{
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("failed")),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state := states.BuildState(func(ss *states.SyncState) {
|
|
ss.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_thing",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
test_thing.foo:
|
|
ID = baz
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`) // test_thing.foo is still here, even though provider returned no new state along with its error
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorCreateInvalidNew(t *testing.T) {
|
|
m := testModule(t, "apply-error")
|
|
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
// We're intentionally returning an inconsistent new state here
|
|
// because we want to test that Terraform ignores the inconsistency
|
|
// when accompanied by another error.
|
|
return providers.ApplyResourceChangeResponse{
|
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
|
"value": cty.StringVal("wrong wrong wrong wrong"),
|
|
"foo": cty.StringVal("absolutely brimming over with wrongability"),
|
|
}),
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should have error")
|
|
}
|
|
if got, want := len(diags), 1; got != want {
|
|
// There should be no additional diagnostics generated by Terraform's own eval logic,
|
|
// because the provider's own error supersedes them.
|
|
t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err())
|
|
}
|
|
if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) {
|
|
t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err())
|
|
}
|
|
if got, want := len(state.RootModule().Resources), 2; got != want {
|
|
t.Errorf("%d resources in state before prune; should have %d\n%s", got, want, spew.Sdump(state))
|
|
}
|
|
state.PruneResourceHusks() // aws_instance.bar with no instances gets left behind when we bail out, but that's okay
|
|
if got, want := len(state.RootModule().Resources), 1; got != want {
|
|
t.Errorf("%d resources in state after prune; should have only one (aws_instance.foo, tainted)\n%s", got, spew.Sdump(state))
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorUpdateNullNew(t *testing.T) {
|
|
m := testModule(t, "apply-error")
|
|
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
// We're intentionally returning no NewState here because we want to
|
|
// test that Terraform retains the prior state, rather than treating
|
|
// the returned null as "no state" (object deleted).
|
|
return providers.ApplyResourceChangeResponse{
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("forced error")),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state := states.BuildState(func(ss *states.SyncState) {
|
|
ss.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"value":"old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("should have error")
|
|
}
|
|
if got, want := len(diags), 1; got != want {
|
|
// There should be no additional diagnostics generated by Terraform's own eval logic,
|
|
// because the provider's own error supersedes them.
|
|
t.Errorf("wrong number of diagnostics %d; want %d\n%s", got, want, diags.Err())
|
|
}
|
|
if got, want := diags.Err().Error(), "forced error"; !strings.Contains(got, want) {
|
|
t.Errorf("returned error does not contain %q, but it should\n%s", want, diags.Err())
|
|
}
|
|
state.PruneResourceHusks()
|
|
if got, want := len(state.RootModule().Resources), 1; got != want {
|
|
t.Fatalf("%d resources in state; should have only one (aws_instance.foo, unmodified)\n%s", got, spew.Sdump(state))
|
|
}
|
|
|
|
is := state.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
if is == nil {
|
|
t.Fatalf("aws_instance.foo is not in the state after apply")
|
|
}
|
|
if got, want := is.Current.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) {
|
|
t.Fatalf("incorrect attributes for aws_instance.foo\ngot: %s\nwant: JSON containing %s\n\n%s", got, want, spew.Sdump(is))
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorPartial(t *testing.T) {
|
|
errored := false
|
|
|
|
m := testModule(t, "apply-error")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
if errored {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
|
|
return
|
|
}
|
|
errored = true
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags == nil {
|
|
t.Fatal("should have error")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
if len(mod.Resources) != 2 {
|
|
t.Fatalf("bad: %#v", mod.Resources)
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyErrorPartialStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_hook(t *testing.T) {
|
|
m := testModule(t, "apply-good")
|
|
h := new(MockHook)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
if !h.PreApplyCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostApplyCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostStateUpdateCalled {
|
|
t.Fatalf("should call post state update")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_hookOrphan(t *testing.T) {
|
|
m := testModule(t, "apply-blank")
|
|
h := new(MockHook)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
if !h.PreApplyCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostApplyCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostStateUpdateCalled {
|
|
t.Fatalf("should call post state update")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_idAttr(t *testing.T) {
|
|
m := testModule(t, "apply-idattr")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
rs, ok := mod.Resources["aws_instance.foo"]
|
|
if !ok {
|
|
t.Fatal("not in state")
|
|
}
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(rs.Instances[addrs.NoKey].Current.AttrsJSON, &attrs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := attrs["id"], "foo"; got != want {
|
|
t.Fatalf("wrong id\ngot: %#v\nwant: %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputBasic(t *testing.T) {
|
|
m := testModule(t, "apply-output")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputAdd(t *testing.T) {
|
|
m1 := testModule(t, "apply-output-add-before")
|
|
p1 := testProvider("aws")
|
|
p1.ApplyResourceChangeFn = testApplyFn
|
|
p1.PlanResourceChangeFn = testDiffFn
|
|
ctx1 := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p1),
|
|
},
|
|
})
|
|
|
|
plan1, diags := ctx1.Plan(m1, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state1, diags := ctx1.Apply(plan1, m1)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
m2 := testModule(t, "apply-output-add-after")
|
|
p2 := testProvider("aws")
|
|
p2.ApplyResourceChangeFn = testApplyFn
|
|
p2.PlanResourceChangeFn = testDiffFn
|
|
ctx2 := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p2),
|
|
},
|
|
})
|
|
|
|
plan2, diags := ctx1.Plan(m2, state1, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state2, diags := ctx2.Apply(plan2, m2)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state2.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputAddStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputList(t *testing.T) {
|
|
m := testModule(t, "apply-output-list")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputListStr)
|
|
if actual != expected {
|
|
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputMulti(t *testing.T) {
|
|
m := testModule(t, "apply-output-multi")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputMultiStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_outputMultiIndex(t *testing.T) {
|
|
m := testModule(t, "apply-output-multi-index")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyOutputMultiIndexStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_taintX(t *testing.T) {
|
|
m := testModule(t, "apply-taint")
|
|
p := testProvider("aws")
|
|
// destroyCount tests against regression of
|
|
// https://github.com/hashicorp/terraform/issues/1056
|
|
var destroyCount = int32(0)
|
|
var once sync.Once
|
|
simulateProviderDelay := func() {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
once.Do(simulateProviderDelay)
|
|
if req.PlannedState.IsNull() {
|
|
atomic.AddInt32(&destroyCount, 1)
|
|
}
|
|
return testApplyFn(req)
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyTaintStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n%s", actual)
|
|
}
|
|
|
|
if destroyCount != 1 {
|
|
t.Fatalf("Expected 1 destroy, got %d", destroyCount)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_taintDep(t *testing.T) {
|
|
m := testModule(t, "apply-taint-dep")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyTaintDepStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_taintDepRequiresNew(t *testing.T) {
|
|
m := testModule(t, "apply-taint-dep-requires-new")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"baz","num": "2", "type": "aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","num": "2", "type": "aws_instance", "foo": "baz"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf("plan: %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformApplyTaintDepRequireNewStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_targeted(t *testing.T) {
|
|
m := testModule(t, "apply-targeted")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("expected 1 resource, got: %#v", mod.Resources)
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedCount(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo.0:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
aws_instance.foo.1:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
aws_instance.foo.2:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedCountIndex(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1),
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo.1:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedDestroy(t *testing.T) {
|
|
m := testModule(t, "destroy-targeted")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetOutputValue("out", cty.StringVal("bar"), false)
|
|
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.b").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if diags := ctx.Validate(m); diags.HasErrors() {
|
|
t.Fatalf("validate errors: %s", diags.Err())
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "a",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) != 0 {
|
|
t.Fatalf("expected 0 resources, got: %#v", mod.Resources)
|
|
}
|
|
|
|
// the root output should not get removed; only the targeted resource.
|
|
//
|
|
// Note: earlier versions of this test expected 0 outputs, but it turns out
|
|
// that was because Validate - not apply or destroy - removed the output
|
|
// (which depends on the targeted resource) from state. That version of this
|
|
// test did not match actual terraform behavior: the output remains in
|
|
// state.
|
|
//
|
|
// TODO: Future refactoring may enable us to remove the output from state in
|
|
// this case, and that would be Just Fine - this test can be modified to
|
|
// expect 0 outputs.
|
|
if len(mod.OutputValues) != 1 {
|
|
t.Fatalf("expected 1 outputs, got: %#v", mod.OutputValues)
|
|
}
|
|
|
|
// the module instance should remain
|
|
mod = state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("expected 1 resources, got: %#v", mod.Resources)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-targeted-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `<no state>`)
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/4462
|
|
func TestContext2Apply_targetedDestroyModule(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar:
|
|
ID = i-abc123
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
aws_instance.foo:
|
|
ID = i-bcd345
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
|
|
module.child:
|
|
aws_instance.bar:
|
|
ID = i-abc123
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-count")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
foo := &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
|
}
|
|
bar := &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
foo,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
foo,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
foo,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
bar,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
bar,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar[2]").Resource,
|
|
bar,
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(2),
|
|
),
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
addrs.ManagedResourceMode, "aws_instance", "bar", addrs.IntKey(1),
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.bar.0:
|
|
ID = i-abc123
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
aws_instance.bar.2:
|
|
ID = i-abc123
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
aws_instance.foo.0:
|
|
ID = i-bcd345
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
aws_instance.foo.1:
|
|
ID = i-bcd345
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedModule(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
if mod == nil {
|
|
t.Fatalf("no child module found in the state!\n\n%#v", state)
|
|
}
|
|
if len(mod.Resources) != 2 {
|
|
t.Fatalf("expected 2 resources, got: %#v", mod.Resources)
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
module.child:
|
|
aws_instance.bar:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
// GH-1858
|
|
func TestContext2Apply_targetedModuleDep(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module-dep")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf("Diff: %s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
|
|
Dependencies:
|
|
module.child.aws_instance.mod
|
|
|
|
module.child:
|
|
aws_instance.mod:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
|
|
Outputs:
|
|
|
|
output = foo
|
|
`)
|
|
}
|
|
|
|
// GH-10911 untargeted outputs should not be in the graph, and therefore
|
|
// not execute.
|
|
func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module-unrelated-outputs")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
state := states.NewState()
|
|
_ = state.EnsureModule(addrs.RootModuleInstance.Child("child2", addrs.NoKey))
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// - module.child1's instance_id output is dropped because we don't preserve
|
|
// non-root module outputs between runs (they can be recalculated from config)
|
|
// - module.child2's instance_id is updated because its dependency is updated
|
|
// - child2_id is updated because if its transitive dependency via module.child2
|
|
checkStateString(t, s, `
|
|
<no state>
|
|
Outputs:
|
|
|
|
child2_id = foo
|
|
|
|
module.child2:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
|
|
Outputs:
|
|
|
|
instance_id = foo
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedModuleResource(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module-resource")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
if mod == nil || len(mod.Resources) != 1 {
|
|
t.Fatalf("expected 1 resource, got: %#v", mod)
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
module.child:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-resource-orphan-module")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"type":"aws_instance"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_unknownAttribute(t *testing.T) {
|
|
m := testModule(t, "apply-unknown")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp = testDiffFn(req)
|
|
planned := resp.PlannedState.AsValueMap()
|
|
planned["unknown"] = cty.UnknownVal(cty.String)
|
|
resp.PlannedState = cty.ObjectVal(planned)
|
|
return resp
|
|
}
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"num": {Type: cty.Number, Optional: true},
|
|
"unknown": {Type: cty.String, Computed: true},
|
|
"type": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Error("should error, because attribute 'unknown' is still unknown after apply")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_unknownAttributeInterpolate(t *testing.T) {
|
|
m := testModule(t, "apply-unknown-interpolate")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts); diags == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_vars(t *testing.T) {
|
|
fixture := contextFixtureApplyVars(t)
|
|
opts := fixture.ContextOpts()
|
|
ctx := testContext2(t, opts)
|
|
m := fixture.Config
|
|
|
|
diags := ctx.Validate(m)
|
|
if len(diags) != 0 {
|
|
t.Fatalf("bad: %s", diags.ErrWithWarnings())
|
|
}
|
|
|
|
variables := InputValues{
|
|
"foo": &InputValue{
|
|
Value: cty.StringVal("us-east-1"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
"test_list": &InputValue{
|
|
Value: cty.ListVal([]cty.Value{
|
|
cty.StringVal("Hello"),
|
|
cty.StringVal("World"),
|
|
}),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
"test_map": &InputValue{
|
|
Value: cty.MapVal(map[string]cty.Value{
|
|
"Hello": cty.StringVal("World"),
|
|
"Foo": cty.StringVal("Bar"),
|
|
"Baz": cty.StringVal("Foo"),
|
|
}),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
"amis": &InputValue{
|
|
Value: cty.MapVal(map[string]cty.Value{
|
|
"us-east-1": cty.StringVal("override"),
|
|
}),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: variables,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(testTerraformApplyVarsStr)
|
|
if got != want {
|
|
t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_varsEnv(t *testing.T) {
|
|
fixture := contextFixtureApplyVarsEnv(t)
|
|
opts := fixture.ContextOpts()
|
|
ctx := testContext2(t, opts)
|
|
m := fixture.Config
|
|
|
|
diags := ctx.Validate(m)
|
|
if len(diags) != 0 {
|
|
t.Fatalf("bad: %s", diags.ErrWithWarnings())
|
|
}
|
|
|
|
variables := InputValues{
|
|
"string": &InputValue{
|
|
Value: cty.StringVal("baz"),
|
|
SourceType: ValueFromEnvVar,
|
|
},
|
|
"list": &InputValue{
|
|
Value: cty.ListVal([]cty.Value{
|
|
cty.StringVal("Hello"),
|
|
cty.StringVal("World"),
|
|
}),
|
|
SourceType: ValueFromEnvVar,
|
|
},
|
|
"map": &InputValue{
|
|
Value: cty.MapVal(map[string]cty.Value{
|
|
"Hello": cty.StringVal("World"),
|
|
"Foo": cty.StringVal("Bar"),
|
|
"Baz": cty.StringVal("Foo"),
|
|
}),
|
|
SourceType: ValueFromEnvVar,
|
|
},
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: variables,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyVarsEnvStr)
|
|
if actual != expected {
|
|
t.Errorf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_createBefore_depends(t *testing.T) {
|
|
m := testModule(t, "apply-depends-create-before")
|
|
h := new(HookRecordApplyOrder)
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "web",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "lb",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "web",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("plan failed")
|
|
} else {
|
|
t.Logf("plan:\n%s", legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
h.Active = true
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) < 2 {
|
|
t.Logf("state after apply:\n%s", state.String())
|
|
t.Fatalf("only %d resources in root module; want at least 2", len(mod.Resources))
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(testTerraformApplyDependsCreateBeforeStr)
|
|
if got != want {
|
|
t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
|
|
// Test that things were managed _in the right order_
|
|
order := h.States
|
|
|
|
diffs := h.Diffs
|
|
if !order[0].IsNull() || diffs[0].Action == plans.Delete {
|
|
t.Fatalf("should create new instance first: %#v", order)
|
|
}
|
|
|
|
if order[1].GetAttr("id").AsString() != "baz" {
|
|
t.Fatalf("update must happen after create: %#v", order[1])
|
|
}
|
|
|
|
if order[2].GetAttr("id").AsString() != "bar" || diffs[2].Action != plans.Delete {
|
|
t.Fatalf("destroy must happen after update: %#v", order[2])
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_singleDestroy(t *testing.T) {
|
|
m := testModule(t, "apply-depends-create-before")
|
|
h := new(HookRecordApplyOrder)
|
|
p := testProvider("aws")
|
|
invokeCount := 0
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
invokeCount++
|
|
switch invokeCount {
|
|
case 1:
|
|
if req.PlannedState.IsNull() {
|
|
t.Fatalf("should not destroy")
|
|
}
|
|
if id := req.PlannedState.GetAttr("id"); id.IsKnown() {
|
|
t.Fatalf("should not have ID")
|
|
}
|
|
case 2:
|
|
if req.PlannedState.IsNull() {
|
|
t.Fatalf("should not destroy")
|
|
}
|
|
if id := req.PlannedState.GetAttr("id"); id.AsString() != "baz" {
|
|
t.Fatalf("should have id")
|
|
}
|
|
case 3:
|
|
if !req.PlannedState.IsNull() {
|
|
t.Fatalf("should destroy")
|
|
}
|
|
default:
|
|
t.Fatalf("bad invoke count %d", invokeCount)
|
|
}
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "web",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar","require_new":"ami-old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "lb",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"baz","instance":"bar"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "web",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
h.Active = true
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
if invokeCount != 3 {
|
|
t.Fatalf("bad: %d", invokeCount)
|
|
}
|
|
}
|
|
|
|
// GH-7824
|
|
func TestContext2Apply_issue7824(t *testing.T) {
|
|
p := testProvider("template")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"template_file": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"template": {Type: cty.String, Optional: true},
|
|
"__template_requires_new": {Type: cty.Bool, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m, snap := testModuleWithSnapshot(t, "issue-7824")
|
|
|
|
// Apply cleanly step 0
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("template"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
// Write / Read plan to simulate running it through a Plan file
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers =
|
|
map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("template"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// This deals with the situation where a splat expression is used referring
|
|
// to another resource whose count is non-constant.
|
|
func TestContext2Apply_issue5254(t *testing.T) {
|
|
// Create a provider. We use "template" here just to match the repro
|
|
// we got from the issue itself.
|
|
p := testProvider("template")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"template_file": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"template": {Type: cty.String, Optional: true},
|
|
"__template_requires_new": {Type: cty.Bool, Optional: true},
|
|
"id": {Type: cty.String, Computed: true},
|
|
"type": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Apply cleanly step 0
|
|
m := testModule(t, "issue-5254/step-0")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("template"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
m, snap := testModuleWithSnapshot(t, "issue-5254/step-1")
|
|
|
|
// Application success. Now make the modification and store a plan
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("template"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
// Write / Read plan to simulate running it through a Plan file
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("template"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(`
|
|
template_file.child:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/template"]
|
|
__template_requires_new = true
|
|
template = Hi
|
|
type = template_file
|
|
|
|
Dependencies:
|
|
template_file.parent
|
|
template_file.parent.0:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/template"]
|
|
template = Hi
|
|
type = template_file
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("wrong final state\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_targetedWithTaintedInState(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
m, snap := testModuleWithSnapshot(t, "apply-tainted-targets")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.ifailedprovisioners").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"ifailedprovisioners"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "iambeingadded",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
// Write / Read plan to simulate running it through a Plan file
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(`
|
|
aws_instance.iambeingadded:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
type = aws_instance
|
|
aws_instance.ifailedprovisioners: (tainted)
|
|
ID = ifailedprovisioners
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected state: \n%s\ngot: \n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Higher level test exposing the bug this covers in
|
|
// TestResource_ignoreChangesRequired
|
|
func TestContext2Apply_ignoreChangesCreate(t *testing.T) {
|
|
m := testModule(t, "apply-ignore-changes-create")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
instanceSchema.Attributes["required_field"] = &configschema.Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("bad: %s", state)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
// Expect no changes from original state
|
|
expected := strings.TrimSpace(`
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
required_field = set
|
|
type = aws_instance
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ignoreChangesWithDep(t *testing.T) {
|
|
m := testModule(t, "apply-ignore-changes-dep")
|
|
p := testProvider("aws")
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
switch req.TypeName {
|
|
case "aws_instance":
|
|
resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: "ami"}})
|
|
case "aws_eip":
|
|
return testDiffFn(req)
|
|
default:
|
|
t.Fatalf("Unexpected type: %s", req.TypeName)
|
|
}
|
|
return
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123","ami":"ami-abcd1234"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd234","ami":"i-bcd234"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_eip.foo[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"eip-abc123","instance":"i-abc123"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_eip.foo[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"eip-bcd234","instance":"i-bcd234"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "foo",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state.DeepCopy(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(state.String())
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ignoreChangesAll(t *testing.T) {
|
|
m := testModule(t, "apply-ignore-changes-all")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
instanceSchema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
instanceSchema.Attributes["required_field"] = &configschema.Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("plan failed")
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
mod := state.RootModule()
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("bad: %s", state)
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
// Expect no changes from original state
|
|
expected := strings.TrimSpace(`
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
required_field = set
|
|
type = aws_instance
|
|
`)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/terraform/issues/7378
|
|
func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-destroy-nested-module-with-attrs")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
var state *states.State
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// First plan and apply a create operation
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
{
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy plan err: %s", diags.Err())
|
|
}
|
|
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply err: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
if !state.Empty() {
|
|
t.Fatalf("state after apply: %s\nwant empty state", spew.Sdump(state))
|
|
}
|
|
}
|
|
|
|
// If a data source explicitly depends on another resource, it's because we need
|
|
// that resource to be applied first.
|
|
func TestContext2Apply_dataDependsOn(t *testing.T) {
|
|
p := testProvider("null")
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "null_instance" "write" {
|
|
foo = "attribute"
|
|
}
|
|
|
|
data "null_data_source" "read" {
|
|
count = 1
|
|
depends_on = ["null_instance.write"]
|
|
}
|
|
|
|
resource "null_instance" "depends" {
|
|
foo = data.null_data_source.read[0].foo
|
|
}
|
|
`})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// the "provisioner" here writes to this variable, because the intent is to
|
|
// create a dependency which can't be viewed through the graph, and depends
|
|
// solely on the configuration providing "depends_on"
|
|
provisionerOutput := ""
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
// the side effect of the resource being applied
|
|
provisionerOutput = "APPLIED"
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("boop"),
|
|
"foo": cty.StringVal(provisionerOutput),
|
|
}),
|
|
}
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
root := state.Module(addrs.RootModuleInstance)
|
|
is := root.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "null_data_source",
|
|
Name: "read",
|
|
}.Instance(addrs.IntKey(0)))
|
|
if is == nil {
|
|
t.Fatal("data resource instance is not present in state; should be")
|
|
}
|
|
var attrs map[string]interface{}
|
|
err := json.Unmarshal(is.Current.AttrsJSON, &attrs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual := attrs["foo"]
|
|
expected := "APPLIED"
|
|
if actual != expected {
|
|
t.Fatalf("bad:\n%s", strings.TrimSpace(state.String()))
|
|
}
|
|
|
|
// run another plan to make sure the data source doesn't show as a change
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != plans.NoOp {
|
|
t.Fatalf("unexpected change for %s", c.Addr)
|
|
}
|
|
}
|
|
|
|
// now we cause a change in the first resource, which should trigger a plan
|
|
// in the data source, and the resource that depends on the data source
|
|
// must plan a change as well.
|
|
m = testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "null_instance" "write" {
|
|
foo = "new"
|
|
}
|
|
|
|
data "null_data_source" "read" {
|
|
depends_on = ["null_instance.write"]
|
|
}
|
|
|
|
resource "null_instance" "depends" {
|
|
foo = data.null_data_source.read.foo
|
|
}
|
|
`})
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
// the side effect of the resource being applied
|
|
provisionerOutput = "APPLIED_AGAIN"
|
|
return testApplyFn(req)
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
expectedChanges := map[string]plans.Action{
|
|
"null_instance.write": plans.Update,
|
|
"data.null_data_source.read": plans.Read,
|
|
"null_instance.depends": plans.Update,
|
|
}
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
if c.Action != expectedChanges[c.Addr.String()] {
|
|
t.Errorf("unexpected %s for %s", c.Action, c.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_terraformWorkspace(t *testing.T) {
|
|
m := testModule(t, "apply-terraform-workspace")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Meta: &ContextMeta{Env: "foo"},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
actual := state.RootModule().OutputValues["output"]
|
|
expected := cty.StringVal("foo")
|
|
if actual == nil || actual.Value != expected {
|
|
t.Fatalf("wrong value\ngot: %#v\nwant: %#v", actual.Value, expected)
|
|
}
|
|
}
|
|
|
|
// verify that multiple config references only create a single depends_on entry
|
|
func TestContext2Apply_multiRef(t *testing.T) {
|
|
m := testModule(t, "apply-multi-ref")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
deps := state.Modules[""].Resources["aws_instance.other"].Instances[addrs.NoKey].Current.Dependencies
|
|
if len(deps) != 1 || deps[0].String() != "aws_instance.create" {
|
|
t.Fatalf("expected 1 depends_on entry for aws_instance.create, got %q", deps)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_targetedModuleRecursive(t *testing.T) {
|
|
m := testModule(t, "apply-targeted-module-recursive")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey),
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
mod := state.Module(
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("subchild", addrs.NoKey),
|
|
)
|
|
if mod == nil {
|
|
t.Fatalf("no subchild module found in the state!\n\n%#v", state)
|
|
}
|
|
if len(mod.Resources) != 1 {
|
|
t.Fatalf("expected 1 resources, got: %#v", mod.Resources)
|
|
}
|
|
|
|
checkStateString(t, state, `
|
|
<no state>
|
|
module.child.subchild:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
num = 2
|
|
type = aws_instance
|
|
`)
|
|
}
|
|
|
|
func TestContext2Apply_localVal(t *testing.T) {
|
|
m := testModule(t, "apply-local-val")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("error during apply: %s", diags.Err())
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
want := strings.TrimSpace(`
|
|
<no state>
|
|
Outputs:
|
|
|
|
result_1 = hello
|
|
result_3 = hello world
|
|
`)
|
|
if got != want {
|
|
t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithLocals(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-with-locals")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetOutputValue("name", cty.StringVal("test-bar"), false)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
s, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("error during apply: %s", diags.Err())
|
|
}
|
|
|
|
got := strings.TrimSpace(s.String())
|
|
want := strings.TrimSpace(`<no state>`)
|
|
if got != want {
|
|
t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_providerWithLocals(t *testing.T) {
|
|
m := testModule(t, "provider-with-locals")
|
|
p := testProvider("aws")
|
|
|
|
providerRegion := ""
|
|
// this should not be overridden during destroy
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
val := req.Config.GetAttr("region")
|
|
if !val.IsNull() {
|
|
providerRegion = val.AsString()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
if state.HasManagedResourceInstanceObjects() {
|
|
t.Fatal("expected no state, got:", state)
|
|
}
|
|
|
|
if providerRegion != "bar" {
|
|
t.Fatalf("expected region %q, got: %q", "bar", providerRegion)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyWithProviders(t *testing.T) {
|
|
m := testModule(t, "destroy-module-with-provider")
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
removed := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey).Child("removed", addrs.NoKey))
|
|
removed.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.child").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].baz`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// test that we can't destroy if the provider is missing
|
|
if _, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.DestroyMode}); diags == nil {
|
|
t.Fatal("expected plan error, provider.aws.baz doesn't exist")
|
|
}
|
|
|
|
// correct the state
|
|
state.Modules["module.mod.module.removed"].Resources["aws_instance.child"].ProviderConfig = mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`)
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("error during apply: %s", diags.Err())
|
|
}
|
|
|
|
got := strings.TrimSpace(state.String())
|
|
|
|
want := strings.TrimSpace("<no state>")
|
|
if got != want {
|
|
t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_providersFromState(t *testing.T) {
|
|
m := configs.NewEmptyConfig()
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
implicitProviderState := states.NewState()
|
|
impRoot := implicitProviderState.EnsureModule(addrs.RootModuleInstance)
|
|
impRoot.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
aliasedProviderState := states.NewState()
|
|
aliasRoot := aliasedProviderState.EnsureModule(addrs.RootModuleInstance)
|
|
aliasRoot.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"].bar`),
|
|
)
|
|
|
|
moduleProviderState := states.NewState()
|
|
moduleProviderRoot := moduleProviderState.EnsureModule(addrs.RootModuleInstance)
|
|
moduleProviderRoot.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
mustProviderConfig(`module.child.provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
state *states.State
|
|
output string
|
|
err bool
|
|
}{
|
|
{
|
|
name: "add implicit provider",
|
|
state: implicitProviderState,
|
|
err: false,
|
|
output: "<no state>",
|
|
},
|
|
|
|
// an aliased provider must be in the config to remove a resource
|
|
{
|
|
name: "add aliased provider",
|
|
state: aliasedProviderState,
|
|
err: true,
|
|
},
|
|
|
|
// a provider in a module implies some sort of config, so this isn't
|
|
// allowed even without an alias
|
|
{
|
|
name: "add unaliased module provider",
|
|
state: moduleProviderState,
|
|
err: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, tc.state, DefaultPlanOpts)
|
|
if tc.err {
|
|
if diags == nil {
|
|
t.Fatal("expected error")
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
if !tc.err && diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, state, "<no state>")
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_plannedInterpolatedCount(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-interpolated-count")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.test").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan failed: %s", diags.Err())
|
|
}
|
|
|
|
// We'll marshal and unmarshal the plan here, to ensure that we have
|
|
// a clean new context as would be created if we separately ran
|
|
// terraform plan -out=tfplan && terraform apply tfplan
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = Providers
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
// Applying the plan should now succeed
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply failed: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "plan-destroy-interpolated-count")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.a[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetOutputValue("out", cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foo")}), false)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: providers,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan failed: %s", diags.Err())
|
|
}
|
|
|
|
// We'll marshal and unmarshal the plan here, to ensure that we have
|
|
// a clean new context as would be created if we separately ran
|
|
// terraform plan -out=tfplan && terraform apply tfplan
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatalf("failed to round-trip through planfile: %s", err)
|
|
}
|
|
|
|
ctxOpts.Providers = providers
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("err: %s", diags.Err())
|
|
}
|
|
|
|
// Applying the plan should now succeed
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply failed: %s", diags.Err())
|
|
}
|
|
if !state.Empty() {
|
|
t.Fatalf("state not empty: %s\n", state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_scaleInMultivarRef(t *testing.T) {
|
|
m := testModule(t, "apply-resource-scale-in")
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.one").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.two").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"instance_count": {
|
|
Value: cty.NumberIntVal(0),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
{
|
|
addr := mustResourceInstanceAddr("aws_instance.one[0]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
// This test was originally written with Terraform v0.11 and earlier
|
|
// in mind, so it declares a no-key instance of aws_instance.one,
|
|
// but its configuration sets count (to zero) and so we end up first
|
|
// moving the no-key instance to the zero key and then planning to
|
|
// destroy the zero key.
|
|
if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.one"); !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
{
|
|
addr := mustResourceInstanceAddr("aws_instance.two")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.PrevRunAddr, mustResourceInstanceAddr("aws_instance.two"); !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Update; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
|
|
// Applying the plan should now succeed
|
|
_, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
}
|
|
|
|
func TestContext2Apply_inconsistentWithPlan(t *testing.T) {
|
|
m := testModule(t, "apply-inconsistent-with-plan")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("before"),
|
|
}),
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
return providers.ApplyResourceChangeResponse{
|
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
|
// This is intentionally incorrect: because id was fixed at "before"
|
|
// during plan, it must not change during apply.
|
|
"id": cty.StringVal("after"),
|
|
}),
|
|
}
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("apply succeeded; want error")
|
|
}
|
|
if got, want := diags.Err().Error(), "Provider produced inconsistent result after apply"; !strings.Contains(got, want) {
|
|
t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want)
|
|
}
|
|
}
|
|
|
|
// Issue 19908 was about retaining an existing object in the state when an
|
|
// update to it fails and the provider does not return a partially-updated
|
|
// value for it. Previously we were incorrectly removing it from the state
|
|
// in that case, but instead it should be retained so the update can be
|
|
// retried.
|
|
func TestContext2Apply_issue19908(t *testing.T) {
|
|
m := testModule(t, "apply-issue19908")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {Type: cty.String, Required: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
var diags tfdiags.Diagnostics
|
|
diags = diags.Append(fmt.Errorf("update failed"))
|
|
return providers.ApplyResourceChangeResponse{
|
|
Diagnostics: diags,
|
|
}
|
|
}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"baz":"old"}`),
|
|
Status: states.ObjectReady,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("apply succeeded; want error")
|
|
}
|
|
if got, want := diags.Err().Error(), "update failed"; !strings.Contains(got, want) {
|
|
t.Fatalf("wrong error\ngot: %s\nshould contain: %s", got, want)
|
|
}
|
|
|
|
mod := state.RootModule()
|
|
rs := mod.Resources["test.foo"]
|
|
if rs == nil {
|
|
t.Fatalf("test.foo not in state after apply, but should be")
|
|
}
|
|
is := rs.Instances[addrs.NoKey]
|
|
if is == nil {
|
|
t.Fatalf("test.foo not in state after apply, but should be")
|
|
}
|
|
obj := is.Current
|
|
if obj == nil {
|
|
t.Fatalf("test.foo has no current object in state after apply, but should do")
|
|
}
|
|
|
|
if got, want := obj.Status, states.ObjectReady; got != want {
|
|
t.Errorf("test.foo has wrong status %s after apply; want %s", got, want)
|
|
}
|
|
if got, want := obj.AttrsJSON, []byte(`"old"`); !bytes.Contains(got, want) {
|
|
t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_invalidIndexRef(t *testing.T) {
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {Type: cty.String, Optional: true, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
m := testModule(t, "apply-invalid-index")
|
|
c := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
diags := c.Validate(m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
}
|
|
|
|
wantErr := `The given key does not identify an element in this collection value`
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan succeeded; want error")
|
|
}
|
|
gotErr := diags.Err().Error()
|
|
|
|
if !strings.Contains(gotErr, wantErr) {
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleReplaceCycle(t *testing.T) {
|
|
for _, mode := range []string{"normal", "cbd"} {
|
|
var m *configs.Config
|
|
|
|
switch mode {
|
|
case "normal":
|
|
m = testModule(t, "apply-module-replace-cycle")
|
|
case "cbd":
|
|
m = testModule(t, "apply-module-replace-cycle-cbd")
|
|
}
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
instanceSchema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"require_new": {Type: cty.String, Optional: true},
|
|
},
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": instanceSchema,
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
modA := state.EnsureModule(addrs.RootModuleInstance.Child("a", addrs.NoKey))
|
|
modA.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","require_new":"old"}`),
|
|
CreateBeforeDestroy: mode == "cbd",
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
modB := state.EnsureModule(addrs.RootModuleInstance.Child("b", addrs.NoKey))
|
|
modB.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.IntKey(0)),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b","require_new":"old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
aBefore, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("a"),
|
|
"require_new": cty.StringVal("old"),
|
|
}), instanceSchema.ImpliedType())
|
|
aAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"require_new": cty.StringVal("new"),
|
|
}), instanceSchema.ImpliedType())
|
|
bBefore, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("b"),
|
|
"require_new": cty.StringVal("old"),
|
|
}), instanceSchema.ImpliedType())
|
|
bAfter, _ := plans.NewDynamicValue(
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.UnknownVal(cty.String),
|
|
"require_new": cty.UnknownVal(cty.String),
|
|
}), instanceSchema.ImpliedType())
|
|
|
|
var aAction plans.Action
|
|
switch mode {
|
|
case "normal":
|
|
aAction = plans.DeleteThenCreate
|
|
case "cbd":
|
|
aAction = plans.CreateThenDelete
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
changes := &plans.Changes{
|
|
Resources: []*plans.ResourceInstanceChangeSrc{
|
|
{
|
|
Addr: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("a", addrs.NoKey)),
|
|
ProviderAddr: addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: aAction,
|
|
Before: aBefore,
|
|
After: aAfter,
|
|
},
|
|
},
|
|
{
|
|
Addr: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("b", addrs.NoKey)),
|
|
ProviderAddr: addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.DeleteThenCreate,
|
|
Before: bBefore,
|
|
After: bAfter,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
plan := &plans.Plan{
|
|
UIMode: plans.NormalMode,
|
|
Changes: changes,
|
|
PriorState: state.DeepCopy(),
|
|
PrevRunState: state.DeepCopy(),
|
|
}
|
|
|
|
t.Run(mode, func(t *testing.T) {
|
|
_, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_destroyDataCycle(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-destroy-data-cycle")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("new"),
|
|
"foo": cty.NullVal(cty.String),
|
|
}),
|
|
}
|
|
}
|
|
|
|
tp := testProvider("test")
|
|
tp.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "null_resource",
|
|
Name: "a",
|
|
}.Instance(addrs.IntKey(0)),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("null"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "a",
|
|
}.Instance(addrs.IntKey(0)),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "null_data_source",
|
|
Name: "d",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "null_data_source",
|
|
Name: "d",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("null"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(tp),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
diags.HasErrors()
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// We'll marshal and unmarshal the plan here, to ensure that we have
|
|
// a clean new context as would be created if we separately ran
|
|
// terraform plan -out=tfplan && terraform apply tfplan
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctxOpts.Providers = Providers
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("failed to create context for plan: %s", diags.Err())
|
|
}
|
|
|
|
tp.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
foo := req.Config.GetAttr("foo")
|
|
if !foo.IsKnown() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown config value foo"))
|
|
return resp
|
|
}
|
|
|
|
if foo.AsString() != "new" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong config value: %q", foo.AsString()))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_taintedDestroyFailure(t *testing.T) {
|
|
m := testModule(t, "apply-destroy-tainted")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
// All destroys fail.
|
|
if req.PlannedState.IsNull() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure"))
|
|
return
|
|
}
|
|
|
|
// c will also fail to create, meaning the existing tainted instance
|
|
// becomes deposed, ans is then promoted back to current.
|
|
// only C has a foo attribute
|
|
planned := req.PlannedState.AsValueMap()
|
|
foo, ok := planned["foo"]
|
|
if ok && !foo.IsNull() && foo.AsString() == "c" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("failure"))
|
|
return
|
|
}
|
|
|
|
return testApplyFn(req)
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"a","foo":"a"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"b","foo":"b"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "c",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"id":"c","foo":"old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
Hooks: []Hook{&testHook{}},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
diags.HasErrors()
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("expected error")
|
|
}
|
|
|
|
root = state.Module(addrs.RootModuleInstance)
|
|
|
|
// the instance that failed to destroy should remain tainted
|
|
a := root.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey))
|
|
|
|
if a.Current.Status != states.ObjectTainted {
|
|
t.Fatal("test_instance.a should be tainted")
|
|
}
|
|
|
|
// b is create_before_destroy, and the destroy failed, so there should be 1
|
|
// deposed instance.
|
|
b := root.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey))
|
|
|
|
if b.Current.Status != states.ObjectReady {
|
|
t.Fatal("test_instance.b should be Ready")
|
|
}
|
|
|
|
if len(b.Deposed) != 1 {
|
|
t.Fatal("test_instance.b failed to keep deposed instance")
|
|
}
|
|
|
|
// the desposed c instance should be promoted back to Current, and remain
|
|
// tainted
|
|
c := root.ResourceInstance(addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "c",
|
|
}.Instance(addrs.NoKey))
|
|
|
|
if c.Current == nil {
|
|
t.Fatal("test_instance.c has no current instance, but it should")
|
|
}
|
|
|
|
if c.Current.Status != states.ObjectTainted {
|
|
t.Fatal("test_instance.c should be tainted")
|
|
}
|
|
|
|
if len(c.Deposed) != 0 {
|
|
t.Fatal("test_instance.c should have no deposed instances")
|
|
}
|
|
|
|
if string(c.Current.AttrsJSON) != `{"foo":"old","id":"c"}` {
|
|
t.Fatalf("unexpected attrs for c: %q\n", c.Current.AttrsJSON)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_plannedConnectionRefs(t *testing.T) {
|
|
m := testModule(t, "apply-plan-connection-refs")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
s := req.PlannedState.AsValueMap()
|
|
// delay "a" slightly, so if the reference edge is missing the "b"
|
|
// provisioner will see an unknown value.
|
|
if s["foo"].AsString() == "a" {
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
s["id"] = cty.StringVal("ID")
|
|
if ty, ok := s["type"]; ok && !ty.IsKnown() {
|
|
s["type"] = cty.StringVal(req.TypeName)
|
|
}
|
|
resp.NewState = cty.ObjectVal(s)
|
|
return resp
|
|
}
|
|
|
|
provisionerFactory := func() (provisioners.Interface, error) {
|
|
pr := testProvisioner()
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
host := req.Connection.GetAttr("host")
|
|
if host.IsNull() || !host.IsKnown() {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid host value: %#v", host))
|
|
}
|
|
|
|
return resp
|
|
}
|
|
return pr, nil
|
|
}
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
provisioners := map[string]provisioners.Factory{
|
|
"shell": provisionerFactory,
|
|
}
|
|
|
|
hook := &testHook{}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
Provisioners: provisioners,
|
|
Hooks: []Hook{hook},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
diags.HasErrors()
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_cbdCycle(t *testing.T) {
|
|
m, snap := testModuleWithSnapshot(t, "apply-cbd-cycle")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "a",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a","require_new":"old","foo":"b"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "b",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "c",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "b",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b","require_new":"old","foo":"c"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "c",
|
|
},
|
|
Module: addrs.RootModule,
|
|
},
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "c",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"c","require_new":"old"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
Providers := map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
}
|
|
|
|
hook := &testHook{}
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: Providers,
|
|
Hooks: []Hook{hook},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
diags.HasErrors()
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
// We'll marshal and unmarshal the plan here, to ensure that we have
|
|
// a clean new context as would be created if we separately ran
|
|
// terraform plan -out=tfplan && terraform apply tfplan
|
|
ctxOpts, m, plan, err := contextOptsForPlanViaFile(snap, plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctxOpts.Providers = Providers
|
|
ctx, diags = NewContext(ctxOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("failed to create context for plan: %s", diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_apply_set(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
var pmMu sync.Mutex
|
|
arcPMs := map[string]cty.Value{}
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
pmMu.Lock()
|
|
defer pmMu.Unlock()
|
|
arcPMs[req.TypeName] = req.ProviderMeta
|
|
|
|
s := req.PlannedState.AsValueMap()
|
|
s["id"] = cty.StringVal("ID")
|
|
if ty, ok := s["type"]; ok && !ty.IsKnown() {
|
|
s["type"] = cty.StringVal(req.TypeName)
|
|
}
|
|
return providers.ApplyResourceChangeResponse{
|
|
NewState: cty.ObjectVal(s),
|
|
}
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ApplyResourceChangeCalled {
|
|
t.Fatalf("ApplyResourceChange not called")
|
|
}
|
|
|
|
expectations := map[string]cty.Value{}
|
|
|
|
if pm, ok := arcPMs["test_resource"]; !ok {
|
|
t.Fatalf("sub-module ApplyResourceChange not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in sub-module ApplyResourceChange")
|
|
} else {
|
|
expectations["quux-submodule"] = pm
|
|
}
|
|
|
|
if pm, ok := arcPMs["test_instance"]; !ok {
|
|
t.Fatalf("root module ApplyResourceChange not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in root module ApplyResourceChange")
|
|
} else {
|
|
expectations["quux"] = pm
|
|
}
|
|
|
|
type metaStruct struct {
|
|
Baz string `cty:"baz"`
|
|
}
|
|
|
|
for expected, v := range expectations {
|
|
var meta metaStruct
|
|
err := gocty.FromCtyValue(v, &meta)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing cty value: %s", err)
|
|
}
|
|
if meta.Baz != expected {
|
|
t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_apply_unset(t *testing.T) {
|
|
m := testModule(t, "provider-meta-unset")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
var pmMu sync.Mutex
|
|
arcPMs := map[string]cty.Value{}
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
pmMu.Lock()
|
|
defer pmMu.Unlock()
|
|
arcPMs[req.TypeName] = req.ProviderMeta
|
|
|
|
s := req.PlannedState.AsValueMap()
|
|
s["id"] = cty.StringVal("ID")
|
|
if ty, ok := s["type"]; ok && !ty.IsKnown() {
|
|
s["type"] = cty.StringVal(req.TypeName)
|
|
}
|
|
return providers.ApplyResourceChangeResponse{
|
|
NewState: cty.ObjectVal(s),
|
|
}
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ApplyResourceChangeCalled {
|
|
t.Fatalf("ApplyResourceChange not called")
|
|
}
|
|
|
|
if pm, ok := arcPMs["test_resource"]; !ok {
|
|
t.Fatalf("sub-module ApplyResourceChange not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in sub-module ApplyResourceChange: %+v", pm)
|
|
}
|
|
|
|
if pm, ok := arcPMs["test_instance"]; !ok {
|
|
t.Fatalf("root module ApplyResourceChange not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in root module ApplyResourceChange: %+v", pm)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_plan_set(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
prcPMs := map[string]cty.Value{}
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
prcPMs[req.TypeName] = req.ProviderMeta
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.PlanResourceChangeCalled {
|
|
t.Fatalf("PlanResourceChange not called")
|
|
}
|
|
|
|
expectations := map[string]cty.Value{}
|
|
|
|
if pm, ok := prcPMs["test_resource"]; !ok {
|
|
t.Fatalf("sub-module PlanResourceChange not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in sub-module PlanResourceChange")
|
|
} else {
|
|
expectations["quux-submodule"] = pm
|
|
}
|
|
|
|
if pm, ok := prcPMs["test_instance"]; !ok {
|
|
t.Fatalf("root module PlanResourceChange not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in root module PlanResourceChange")
|
|
} else {
|
|
expectations["quux"] = pm
|
|
}
|
|
|
|
type metaStruct struct {
|
|
Baz string `cty:"baz"`
|
|
}
|
|
|
|
for expected, v := range expectations {
|
|
var meta metaStruct
|
|
err := gocty.FromCtyValue(v, &meta)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing cty value: %s", err)
|
|
}
|
|
if meta.Baz != expected {
|
|
t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_plan_unset(t *testing.T) {
|
|
m := testModule(t, "provider-meta-unset")
|
|
p := testProvider("test")
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
prcPMs := map[string]cty.Value{}
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
prcPMs[req.TypeName] = req.ProviderMeta
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.PlanResourceChangeCalled {
|
|
t.Fatalf("PlanResourceChange not called")
|
|
}
|
|
|
|
if pm, ok := prcPMs["test_resource"]; !ok {
|
|
t.Fatalf("sub-module PlanResourceChange not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in sub-module PlanResourceChange: %+v", pm)
|
|
}
|
|
|
|
if pm, ok := prcPMs["test_instance"]; !ok {
|
|
t.Fatalf("root module PlanResourceChange not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in root module PlanResourceChange: %+v", pm)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_plan_setNoSchema(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan supposed to error, has no errors")
|
|
}
|
|
|
|
var rootErr, subErr bool
|
|
errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks"
|
|
for _, diag := range diags {
|
|
if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" {
|
|
t.Errorf("Unexpected error: %+v", diag.Description())
|
|
}
|
|
switch diag.Description().Detail {
|
|
case fmt.Sprintf(errorSummary, "instance"):
|
|
rootErr = true
|
|
case fmt.Sprintf(errorSummary, "resource"):
|
|
subErr = true
|
|
default:
|
|
t.Errorf("Unexpected error: %s", diag.Description())
|
|
}
|
|
}
|
|
if !rootErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for root module, none received")
|
|
}
|
|
if !subErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for sub-module, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_plan_setInvalid(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"quux": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("plan supposed to error, has no errors")
|
|
}
|
|
|
|
var reqErr, invalidErr bool
|
|
for _, diag := range diags {
|
|
switch diag.Description().Summary {
|
|
case "Missing required argument":
|
|
if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` {
|
|
reqErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
case "Unsupported argument":
|
|
if diag.Description().Detail == `An argument named "baz" is not expected here.` {
|
|
invalidErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
default:
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
}
|
|
if !reqErr {
|
|
t.Errorf("Expected missing required argument error, none received")
|
|
}
|
|
if !invalidErr {
|
|
t.Errorf("Expected unsupported argument error, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refresh_set(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
rrcPMs := map[string]cty.Value{}
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
|
|
rrcPMs[req.TypeName] = req.ProviderMeta
|
|
newState, err := p.GetProviderSchemaResponse.ResourceTypes[req.TypeName].Block.CoerceValue(req.PriorState)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
resp.NewState = newState
|
|
return resp
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Refresh(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ReadResourceCalled {
|
|
t.Fatalf("ReadResource not called")
|
|
}
|
|
|
|
expectations := map[string]cty.Value{}
|
|
|
|
if pm, ok := rrcPMs["test_resource"]; !ok {
|
|
t.Fatalf("sub-module ReadResource not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in sub-module ReadResource")
|
|
} else {
|
|
expectations["quux-submodule"] = pm
|
|
}
|
|
|
|
if pm, ok := rrcPMs["test_instance"]; !ok {
|
|
t.Fatalf("root module ReadResource not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in root module ReadResource")
|
|
} else {
|
|
expectations["quux"] = pm
|
|
}
|
|
|
|
type metaStruct struct {
|
|
Baz string `cty:"baz"`
|
|
}
|
|
|
|
for expected, v := range expectations {
|
|
var meta metaStruct
|
|
err := gocty.FromCtyValue(v, &meta)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing cty value: %s", err)
|
|
}
|
|
if meta.Baz != expected {
|
|
t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refresh_setNoSchema(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// we need a schema for plan/apply so they don't error
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// drop the schema before refresh, to test that it errors
|
|
schema.ProviderMeta = nil
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags = ctx.Refresh(m, state, DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("refresh supposed to error, has no errors")
|
|
}
|
|
|
|
var rootErr, subErr bool
|
|
errorSummary := "The resource test_%s.bar belongs to a provider that doesn't support provider_meta blocks"
|
|
for _, diag := range diags {
|
|
if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" {
|
|
t.Errorf("Unexpected error: %+v", diag.Description())
|
|
}
|
|
switch diag.Description().Detail {
|
|
case fmt.Sprintf(errorSummary, "instance"):
|
|
rootErr = true
|
|
case fmt.Sprintf(errorSummary, "resource"):
|
|
subErr = true
|
|
default:
|
|
t.Errorf("Unexpected error: %s", diag.Description())
|
|
}
|
|
}
|
|
if !rootErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for root module, none received")
|
|
}
|
|
if !subErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for sub-module, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refresh_setInvalid(t *testing.T) {
|
|
m := testModule(t, "provider-meta-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
// we need a matching schema for plan/apply so they don't error
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
// change the schema before refresh, to test that it errors
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"quux": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags = ctx.Refresh(m, state, DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("refresh supposed to error, has no errors")
|
|
}
|
|
|
|
var reqErr, invalidErr bool
|
|
for _, diag := range diags {
|
|
switch diag.Description().Summary {
|
|
case "Missing required argument":
|
|
if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` {
|
|
reqErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
case "Unsupported argument":
|
|
if diag.Description().Detail == `An argument named "baz" is not expected here.` {
|
|
invalidErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
default:
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
}
|
|
if !reqErr {
|
|
t.Errorf("Expected missing required argument error, none received")
|
|
}
|
|
if !invalidErr {
|
|
t.Errorf("Expected unsupported argument error, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refreshdata_set(t *testing.T) {
|
|
m := testModule(t, "provider-meta-data-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
rdsPMs := map[string]cty.Value{}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
rdsPMs[req.TypeName] = req.ProviderMeta
|
|
switch req.TypeName {
|
|
case "test_data_source":
|
|
log.Printf("[TRACE] test_data_source RDSR returning")
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("yo"),
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
}
|
|
case "test_file":
|
|
log.Printf("[TRACE] test_file RDSR returning")
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"rendered": cty.StringVal("baz"),
|
|
"template": cty.StringVal(""),
|
|
}),
|
|
}
|
|
default:
|
|
// config drift, oops
|
|
log.Printf("[TRACE] unknown request TypeName: %q", req.TypeName)
|
|
return providers.ReadDataSourceResponse{}
|
|
}
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Refresh(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ReadDataSourceCalled {
|
|
t.Fatalf("ReadDataSource not called")
|
|
}
|
|
|
|
expectations := map[string]cty.Value{}
|
|
|
|
if pm, ok := rdsPMs["test_file"]; !ok {
|
|
t.Fatalf("sub-module ReadDataSource not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in sub-module ReadDataSource")
|
|
} else {
|
|
expectations["quux-submodule"] = pm
|
|
}
|
|
|
|
if pm, ok := rdsPMs["test_data_source"]; !ok {
|
|
t.Fatalf("root module ReadDataSource not called")
|
|
} else if pm.IsNull() {
|
|
t.Fatalf("null ProviderMeta in root module ReadDataSource")
|
|
} else {
|
|
expectations["quux"] = pm
|
|
}
|
|
|
|
type metaStruct struct {
|
|
Baz string `cty:"baz"`
|
|
}
|
|
|
|
for expected, v := range expectations {
|
|
var meta metaStruct
|
|
err := gocty.FromCtyValue(v, &meta)
|
|
if err != nil {
|
|
t.Fatalf("Error parsing cty value: %s", err)
|
|
}
|
|
if meta.Baz != expected {
|
|
t.Fatalf("Expected meta.Baz to be %q, got %q", expected, meta.Baz)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refreshdata_unset(t *testing.T) {
|
|
m := testModule(t, "provider-meta-data-unset")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"baz": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
rdsPMs := map[string]cty.Value{}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
rdsPMs[req.TypeName] = req.ProviderMeta
|
|
switch req.TypeName {
|
|
case "test_data_source":
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("yo"),
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
}
|
|
case "test_file":
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("bar"),
|
|
"rendered": cty.StringVal("baz"),
|
|
"template": cty.StringVal(""),
|
|
}),
|
|
}
|
|
default:
|
|
// config drift, oops
|
|
return providers.ReadDataSourceResponse{}
|
|
}
|
|
}
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
if !p.ReadDataSourceCalled {
|
|
t.Fatalf("ReadDataSource not called")
|
|
}
|
|
|
|
if pm, ok := rdsPMs["test_file"]; !ok {
|
|
t.Fatalf("sub-module ReadDataSource not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in sub-module ReadDataSource")
|
|
}
|
|
|
|
if pm, ok := rdsPMs["test_data_source"]; !ok {
|
|
t.Fatalf("root module ReadDataSource not called")
|
|
} else if !pm.IsNull() {
|
|
t.Fatalf("non-null ProviderMeta in root module ReadDataSource")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refreshdata_setNoSchema(t *testing.T) {
|
|
m := testModule(t, "provider-meta-data-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("yo"),
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("refresh supposed to error, has no errors")
|
|
}
|
|
|
|
var rootErr, subErr bool
|
|
errorSummary := "The resource data.test_%s.foo belongs to a provider that doesn't support provider_meta blocks"
|
|
for _, diag := range diags {
|
|
if diag.Description().Summary != "Provider registry.terraform.io/hashicorp/test doesn't support provider_meta" {
|
|
t.Errorf("Unexpected error: %+v", diag.Description())
|
|
}
|
|
switch diag.Description().Detail {
|
|
case fmt.Sprintf(errorSummary, "data_source"):
|
|
rootErr = true
|
|
case fmt.Sprintf(errorSummary, "file"):
|
|
subErr = true
|
|
default:
|
|
t.Errorf("Unexpected error: %s", diag.Description())
|
|
}
|
|
}
|
|
if !rootErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for root module, none received")
|
|
}
|
|
if !subErr {
|
|
t.Errorf("Expected unsupported provider_meta block error for sub-module, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_ProviderMeta_refreshdata_setInvalid(t *testing.T) {
|
|
m := testModule(t, "provider-meta-data-set")
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
schema := p.ProviderSchema()
|
|
schema.ProviderMeta = &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"quux": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("yo"),
|
|
"foo": cty.StringVal("bar"),
|
|
}),
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, states.NewState(), DefaultPlanOpts)
|
|
if !diags.HasErrors() {
|
|
t.Fatalf("refresh supposed to error, has no errors")
|
|
}
|
|
|
|
var reqErr, invalidErr bool
|
|
for _, diag := range diags {
|
|
switch diag.Description().Summary {
|
|
case "Missing required argument":
|
|
if diag.Description().Detail == `The argument "quux" is required, but no definition was found.` {
|
|
reqErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
case "Unsupported argument":
|
|
if diag.Description().Detail == `An argument named "baz" is not expected here.` {
|
|
invalidErr = true
|
|
} else {
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
default:
|
|
t.Errorf("Unexpected error %+v", diag.Description())
|
|
}
|
|
}
|
|
if !reqErr {
|
|
t.Errorf("Expected missing required argument error, none received")
|
|
}
|
|
if !invalidErr {
|
|
t.Errorf("Expected unsupported argument error, none received")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_expandModuleVariables(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "mod1" {
|
|
for_each = toset(["a"])
|
|
source = "./mod"
|
|
}
|
|
|
|
module "mod2" {
|
|
source = "./mod"
|
|
in = module.mod1["a"].out
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
foo = var.in
|
|
}
|
|
|
|
variable "in" {
|
|
type = string
|
|
default = "default"
|
|
}
|
|
|
|
output "out" {
|
|
value = aws_instance.foo.id
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
expected := `<no state>
|
|
module.mod1["a"]:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = default
|
|
type = aws_instance
|
|
|
|
Outputs:
|
|
|
|
out = foo
|
|
module.mod2:
|
|
aws_instance.foo:
|
|
ID = foo
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
foo = foo
|
|
type = aws_instance
|
|
|
|
Dependencies:
|
|
module.mod1.aws_instance.foo`
|
|
|
|
if state.String() != expected {
|
|
t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_inheritAndStoreCBD(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "aws_instance" "foo" {
|
|
}
|
|
|
|
resource "aws_instance" "cbd" {
|
|
foo = aws_instance.foo.id
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
foo := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo"))
|
|
if !foo.Current.CreateBeforeDestroy {
|
|
t.Fatal("aws_instance.foo should also be create_before_destroy")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleDependsOn(t *testing.T) {
|
|
m := testModule(t, "apply-module-depends-on")
|
|
|
|
p := testProvider("test")
|
|
|
|
// each instance being applied should happen in sequential order
|
|
applied := int64(0)
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
cfg := req.Config.AsValueMap()
|
|
foo := cfg["foo"].AsString()
|
|
ord := atomic.LoadInt64(&applied)
|
|
|
|
resp := providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data"),
|
|
"foo": cfg["foo"],
|
|
}),
|
|
}
|
|
|
|
if foo == "a" && ord < 4 {
|
|
// due to data source "a"'s module depending on instance 4, this
|
|
// should not be less than 4
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source a read too early"))
|
|
}
|
|
if foo == "b" && ord < 1 {
|
|
// due to data source "b"'s module depending on instance 1, this
|
|
// should not be less than 1
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source b read too early"))
|
|
}
|
|
return resp
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
state := req.PlannedState.AsValueMap()
|
|
num, _ := state["num"].AsBigFloat().Float64()
|
|
ord := int64(num)
|
|
if !atomic.CompareAndSwapInt64(&applied, ord-1, ord) {
|
|
actual := atomic.LoadInt64(&applied)
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("instance %d was applied after %d", ord, actual))
|
|
}
|
|
|
|
state["id"] = cty.StringVal(fmt.Sprintf("test_%d", ord))
|
|
state["type"] = cty.StringVal("test_instance")
|
|
resp.NewState = cty.ObjectVal(state)
|
|
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
if res.Action != plans.NoOp {
|
|
t.Fatalf("expected NoOp, got %s for %s", res.Action, res.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleSelfReference(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "test" {
|
|
source = "./test"
|
|
|
|
a = module.test.b
|
|
}
|
|
|
|
output "c" {
|
|
value = module.test.c
|
|
}
|
|
`,
|
|
"test/main.tf": `
|
|
variable "a" {}
|
|
|
|
resource "test_instance" "test" {
|
|
}
|
|
|
|
output "b" {
|
|
value = test_instance.test.id
|
|
}
|
|
|
|
output "c" {
|
|
value = var.a
|
|
}`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
if !state.Empty() {
|
|
t.Fatal("expected empty state, got:", state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleExpandDependsOn(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
module "child" {
|
|
count = 1
|
|
source = "./child"
|
|
|
|
depends_on = [test_instance.a, test_instance.b]
|
|
}
|
|
|
|
resource "test_instance" "a" {
|
|
}
|
|
|
|
|
|
resource "test_instance" "b" {
|
|
}
|
|
`,
|
|
"child/main.tf": `
|
|
resource "test_instance" "foo" {
|
|
}
|
|
|
|
output "myoutput" {
|
|
value = "literal string"
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
if !state.Empty() {
|
|
t.Fatal("expected empty state, got:", state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_scaleInCBD(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "ct" {
|
|
type = number
|
|
}
|
|
|
|
resource "test_instance" "a" {
|
|
count = var.ct
|
|
}
|
|
|
|
resource "test_instance" "b" {
|
|
require_new = local.removable
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "test_instance" "c" {
|
|
require_new = test_instance.b.id
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
|
|
output "out" {
|
|
value = join(".", test_instance.a[*].id)
|
|
}
|
|
|
|
locals {
|
|
removable = join(".", test_instance.a[*].id)
|
|
}
|
|
`})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a[0]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a0"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.a[1]").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"a1"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.b").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"b", "require_new":"old.old"}`),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")},
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("test_instance.c").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"c", "require_new":"b"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
mustConfigResourceAddr("test_instance.a"),
|
|
mustConfigResourceAddr("test_instance.b"),
|
|
},
|
|
CreateBeforeDestroy: true,
|
|
},
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
)
|
|
|
|
p := testProvider("test")
|
|
|
|
p.PlanResourceChangeFn = func(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
n := r.ProposedNewState.AsValueMap()
|
|
|
|
if r.PriorState.IsNull() {
|
|
n["id"] = cty.UnknownVal(cty.String)
|
|
resp.PlannedState = cty.ObjectVal(n)
|
|
return resp
|
|
}
|
|
|
|
p := r.PriorState.AsValueMap()
|
|
|
|
priorRN := p["require_new"]
|
|
newRN := n["require_new"]
|
|
|
|
if eq := priorRN.Equals(newRN); !eq.IsKnown() || eq.False() {
|
|
resp.RequiresReplace = []cty.Path{{cty.GetAttrStep{Name: "require_new"}}}
|
|
n["id"] = cty.UnknownVal(cty.String)
|
|
}
|
|
|
|
resp.PlannedState = cty.ObjectVal(n)
|
|
return resp
|
|
}
|
|
|
|
// reduce the count to 1
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"ct": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
{
|
|
addr := mustResourceInstanceAddr("test_instance.a[0]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.NoOp; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
{
|
|
addr := mustResourceInstanceAddr("test_instance.a[1]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[1]"); !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
log.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
// check the output, as those can't cause an error planning the value
|
|
out := state.RootModule().OutputValues["out"].Value.AsString()
|
|
if out != "a0" {
|
|
t.Fatalf(`expected output "a0", got: %q`, out)
|
|
}
|
|
|
|
// reduce the count to 0
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"ct": &InputValue{
|
|
Value: cty.NumberIntVal(0),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
{
|
|
addr := mustResourceInstanceAddr("test_instance.a[0]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change == nil {
|
|
t.Fatalf("no planned change for %s", addr)
|
|
}
|
|
if got, want := change.PrevRunAddr, mustResourceInstanceAddr("test_instance.a[0]"); !want.Equal(got) {
|
|
t.Errorf("wrong previous run address for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
t.Errorf("wrong action for %s %s; want %s", addr, got, want)
|
|
}
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseCountIndex; got != want {
|
|
t.Errorf("wrong action reason for %s %s; want %s", addr, got, want)
|
|
}
|
|
}
|
|
{
|
|
addr := mustResourceInstanceAddr("test_instance.a[1]")
|
|
change := plan.Changes.ResourceInstance(addr)
|
|
if change != nil {
|
|
// It was already removed in the previous plan/apply
|
|
t.Errorf("unexpected planned change for %s", addr)
|
|
}
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
// check the output, as those can't cause an error planning the value
|
|
out = state.RootModule().OutputValues["out"].Value.AsString()
|
|
if out != "" {
|
|
t.Fatalf(`expected output "", got: %q`, out)
|
|
}
|
|
}
|
|
|
|
// Ensure that we can destroy when a provider references a resource that will
|
|
// also be destroyed
|
|
func TestContext2Apply_destroyProviderReference(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
provider "null" {
|
|
value = ""
|
|
}
|
|
|
|
module "mod" {
|
|
source = "./mod"
|
|
}
|
|
|
|
provider "test" {
|
|
value = module.mod.output
|
|
}
|
|
|
|
resource "test_instance" "bar" {
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
data "null_data_source" "foo" {
|
|
count = 1
|
|
}
|
|
|
|
|
|
output "output" {
|
|
value = data.null_data_source.foo[0].output
|
|
}
|
|
`})
|
|
|
|
schemaFn := func(name string) *ProviderSchema {
|
|
return &ProviderSchema{
|
|
Provider: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"value": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
name + "_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
name + "_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"output": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
testP := new(MockProvider)
|
|
testP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{NewState: req.PriorState}
|
|
}
|
|
testP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("test"))
|
|
|
|
providerConfig := ""
|
|
testP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
|
value := req.Config.GetAttr("value")
|
|
if value.IsKnown() && !value.IsNull() {
|
|
providerConfig = value.AsString()
|
|
} else {
|
|
providerConfig = ""
|
|
}
|
|
return resp
|
|
}
|
|
testP.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
if providerConfig != "valid" {
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider config is %q", providerConfig))
|
|
return
|
|
}
|
|
return testApplyFn(req)
|
|
}
|
|
testP.PlanResourceChangeFn = testDiffFn
|
|
|
|
nullP := new(MockProvider)
|
|
nullP.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{NewState: req.PriorState}
|
|
}
|
|
nullP.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schemaFn("null"))
|
|
|
|
nullP.ApplyResourceChangeFn = testApplyFn
|
|
nullP.PlanResourceChangeFn = testDiffFn
|
|
|
|
nullP.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("ID"),
|
|
"output": cty.StringVal("valid"),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP),
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(testP),
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(nullP),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
|
t.Fatalf("destroy apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// Destroying properly requires pruning out all unneeded config nodes to
|
|
// prevent incorrect expansion evaluation.
|
|
func TestContext2Apply_destroyInterModuleExpansion(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
data "test_data_source" "a" {
|
|
for_each = {
|
|
one = "thing"
|
|
}
|
|
}
|
|
|
|
locals {
|
|
module_input = {
|
|
for k, v in data.test_data_source.a : k => v.id
|
|
}
|
|
}
|
|
|
|
module "mod1" {
|
|
source = "./mod"
|
|
input = local.module_input
|
|
}
|
|
|
|
module "mod2" {
|
|
source = "./mod"
|
|
input = module.mod1.outputs
|
|
}
|
|
|
|
resource "test_instance" "bar" {
|
|
for_each = module.mod2.outputs
|
|
}
|
|
|
|
output "module_output" {
|
|
value = module.mod2.outputs
|
|
}
|
|
output "test_instances" {
|
|
value = test_instance.bar
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
variable "input" {
|
|
}
|
|
|
|
data "test_data_source" "foo" {
|
|
for_each = var.input
|
|
}
|
|
|
|
output "outputs" {
|
|
value = data.test_data_source.foo
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("data_source"),
|
|
"foo": cty.StringVal("output"),
|
|
}),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
destroy := func() {
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.DestroyMode,
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("destroy apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
destroy()
|
|
// Destroying again from the empty state should not cause any errors either
|
|
destroy()
|
|
}
|
|
|
|
func TestContext2Apply_createBeforeDestroyWithModule(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "v" {}
|
|
|
|
module "mod" {
|
|
source = "./mod"
|
|
in = var.v
|
|
}
|
|
|
|
resource "test_resource" "a" {
|
|
value = var.v
|
|
depends_on = [module.mod]
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
`,
|
|
"mod/main.tf": `
|
|
variable "in" {}
|
|
|
|
resource "test_resource" "a" {
|
|
value = var.in
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
proposed := req.ProposedNewState.AsValueMap()
|
|
proposed["id"] = cty.UnknownVal(cty.String)
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(proposed),
|
|
RequiresReplace: []cty.Path{{cty.GetAttrStep{Name: "value"}}},
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"v": &InputValue{
|
|
Value: cty.StringVal("A"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"v": &InputValue{
|
|
Value: cty.StringVal("B"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_forcedCBD(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "v" {}
|
|
|
|
resource "test_instance" "a" {
|
|
require_new = var.v
|
|
}
|
|
|
|
resource "test_instance" "b" {
|
|
depends_on = [test_instance.a]
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"v": &InputValue{
|
|
Value: cty.StringVal("A"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"v": &InputValue{
|
|
Value: cty.StringVal("B"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_removeReferencedResource(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "ct" {
|
|
}
|
|
|
|
resource "test_resource" "to_remove" {
|
|
count = var.ct
|
|
}
|
|
|
|
resource "test_resource" "c" {
|
|
value = join("", test_resource.to_remove[*].id)
|
|
}
|
|
`})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"ct": &InputValue{
|
|
Value: cty.NumberIntVal(1),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"ct": &InputValue{
|
|
Value: cty.NumberIntVal(0),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_variableSensitivity(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "sensitive_var" {
|
|
default = "foo"
|
|
sensitive = true
|
|
}
|
|
|
|
variable "sensitive_id" {
|
|
default = "secret id"
|
|
sensitive = true
|
|
}
|
|
|
|
resource "test_resource" "foo" {
|
|
value = var.sensitive_var
|
|
|
|
network_interface {
|
|
network_interface_id = var.sensitive_id
|
|
}
|
|
}`,
|
|
})
|
|
|
|
p := new(MockProvider)
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{NewState: req.PriorState}
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_resource": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"value": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"network_interface": {
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"network_interface_id": {Type: cty.String, Optional: true},
|
|
"device_index": {Type: cty.Number, Optional: true},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingSet,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
// Run a second apply with no changes
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
// Now change the variable value for sensitive_var
|
|
ctx = testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags = ctx.Plan(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"sensitive_var": &InputValue{
|
|
Value: cty.StringVal("bar"),
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_variableSensitivityPropagation(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "sensitive_map" {
|
|
type = map(string)
|
|
default = {
|
|
"x" = "foo"
|
|
}
|
|
sensitive = true
|
|
}
|
|
|
|
resource "test_resource" "foo" {
|
|
value = var.sensitive_map.x
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan errors: %s", diags.Err())
|
|
}
|
|
|
|
verifySensitiveValue := func(pvms []cty.PathValueMarks) {
|
|
if len(pvms) != 1 {
|
|
t.Fatalf("expected 1 sensitive path, got %d", len(pvms))
|
|
}
|
|
pvm := pvms[0]
|
|
if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) {
|
|
t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath)
|
|
}
|
|
if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) {
|
|
t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks)
|
|
}
|
|
}
|
|
|
|
addr := mustResourceInstanceAddr("test_resource.foo")
|
|
fooChangeSrc := plan.Changes.ResourceInstance(addr)
|
|
verifySensitiveValue(fooChangeSrc.AfterValMarks)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
fooState := state.ResourceInstance(addr)
|
|
verifySensitiveValue(fooState.Current.AttrSensitivePaths)
|
|
}
|
|
|
|
func TestContext2Apply_variableSensitivityProviders(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_resource" "foo" {
|
|
sensitive_value = "should get marked"
|
|
}
|
|
|
|
resource "test_resource" "bar" {
|
|
value = test_resource.foo.sensitive_value
|
|
random = test_resource.foo.id # not sensitive
|
|
|
|
nesting_single {
|
|
value = "abc"
|
|
sensitive_value = "xyz"
|
|
}
|
|
}
|
|
|
|
resource "test_resource" "baz" {
|
|
value = test_resource.bar.nesting_single.sensitive_value
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan errors: %s", diags.Err())
|
|
}
|
|
|
|
verifySensitiveValue := func(pvms []cty.PathValueMarks) {
|
|
if len(pvms) != 1 {
|
|
t.Fatalf("expected 1 sensitive path, got %d", len(pvms))
|
|
}
|
|
pvm := pvms[0]
|
|
if gotPath, wantPath := pvm.Path, cty.GetAttrPath("value"); !gotPath.Equals(wantPath) {
|
|
t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath)
|
|
}
|
|
if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) {
|
|
t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks)
|
|
}
|
|
}
|
|
|
|
// Sensitive attributes (defined by the provider) are marked
|
|
// as sensitive when referenced from another resource
|
|
// "bar" references sensitive resources in "foo"
|
|
barAddr := mustResourceInstanceAddr("test_resource.bar")
|
|
barChangeSrc := plan.Changes.ResourceInstance(barAddr)
|
|
verifySensitiveValue(barChangeSrc.AfterValMarks)
|
|
|
|
bazAddr := mustResourceInstanceAddr("test_resource.baz")
|
|
bazChangeSrc := plan.Changes.ResourceInstance(bazAddr)
|
|
verifySensitiveValue(bazChangeSrc.AfterValMarks)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("apply errors: %s", diags.Err())
|
|
}
|
|
|
|
barState := state.ResourceInstance(barAddr)
|
|
verifySensitiveValue(barState.Current.AttrSensitivePaths)
|
|
|
|
bazState := state.ResourceInstance(bazAddr)
|
|
verifySensitiveValue(bazState.Current.AttrSensitivePaths)
|
|
}
|
|
|
|
func TestContext2Apply_variableSensitivityChange(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "sensitive_var" {
|
|
default = "hello"
|
|
sensitive = true
|
|
}
|
|
|
|
resource "test_resource" "foo" {
|
|
value = var.sensitive_var
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo", "value":"hello"}`),
|
|
// No AttrSensitivePaths present
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
addr := mustResourceInstanceAddr("test_resource.foo")
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
fooState := state.ResourceInstance(addr)
|
|
|
|
if len(fooState.Current.AttrSensitivePaths) != 1 {
|
|
t.Fatalf("wrong number of sensitive paths, expected 1, got, %v", len(fooState.Current.AttrSensitivePaths))
|
|
}
|
|
got := fooState.Current.AttrSensitivePaths[0]
|
|
want := cty.PathValueMarks{
|
|
Path: cty.GetAttrPath("value"),
|
|
Marks: cty.NewValueMarks(marks.Sensitive),
|
|
}
|
|
|
|
if !got.Equal(want) {
|
|
t.Fatalf("wrong value marks; got:\n%#v\n\nwant:\n%#v\n", got, want)
|
|
}
|
|
|
|
m2 := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
variable "sensitive_var" {
|
|
default = "hello"
|
|
sensitive = false
|
|
}
|
|
|
|
resource "test_resource" "foo" {
|
|
value = var.sensitive_var
|
|
}`,
|
|
})
|
|
|
|
ctx2 := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// NOTE: Prior to our refactoring to make the state an explicit argument
|
|
// of Plan, as opposed to hidden state inside Context, this test was
|
|
// calling ctx.Apply instead of ctx2.Apply and thus using the previous
|
|
// plan instead of this new plan. "Fixing" it to use the new plan seems
|
|
// to break the test, so we've preserved that oddity here by saving the
|
|
// old plan as oldPlan and essentially discarding the new plan entirely,
|
|
// but this seems rather suspicious and we should ideally figure out what
|
|
// this test was originally intending to do and make it do that.
|
|
oldPlan := plan
|
|
_, diags = ctx2.Plan(m2, state, DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
stateWithoutSensitive, diags := ctx.Apply(oldPlan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
fooState2 := stateWithoutSensitive.ResourceInstance(addr)
|
|
if len(fooState2.Current.AttrSensitivePaths) > 0 {
|
|
t.Fatalf(
|
|
"wrong number of sensitive paths, expected 0, got, %v\n%s",
|
|
len(fooState2.Current.AttrSensitivePaths),
|
|
spew.Sdump(fooState2.Current.AttrSensitivePaths),
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_moduleVariableOptionalAttributes(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
terraform {
|
|
experiments = [module_variable_optional_attrs]
|
|
}
|
|
|
|
variable "in" {
|
|
type = object({
|
|
required = string
|
|
optional = optional(string)
|
|
})
|
|
}
|
|
|
|
output "out" {
|
|
value = var.in
|
|
}
|
|
`})
|
|
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"in": &InputValue{
|
|
Value: cty.MapVal(map[string]cty.Value{
|
|
"required": cty.StringVal("boop"),
|
|
}),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
}
|
|
|
|
got := state.RootModule().OutputValues["out"].Value
|
|
want := cty.ObjectVal(map[string]cty.Value{
|
|
"required": cty.StringVal("boop"),
|
|
|
|
// Because "optional" was marked as optional, it got silently filled
|
|
// in as a null value of string type rather than returning an error.
|
|
"optional": cty.NullVal(cty.String),
|
|
})
|
|
if !want.RawEquals(got) {
|
|
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_provisionerSensitive(t *testing.T) {
|
|
m := testModule(t, "apply-provisioner-sensitive")
|
|
p := testProvider("aws")
|
|
|
|
pr := testProvisioner()
|
|
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
|
if req.Config.ContainsMarked() {
|
|
t.Fatalf("unexpectedly marked config value: %#v", req.Config)
|
|
}
|
|
command := req.Config.GetAttr("command")
|
|
if command.IsMarked() {
|
|
t.Fatalf("unexpectedly marked command argument: %#v", command.Marks())
|
|
}
|
|
req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString()))
|
|
return
|
|
}
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = testApplyFn
|
|
|
|
h := new(MockHook)
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
Provisioners: map[string]provisioners.Factory{
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
SetVariables: InputValues{
|
|
"password": &InputValue{
|
|
Value: cty.StringVal("secret"),
|
|
SourceType: ValueFromCaller,
|
|
},
|
|
},
|
|
})
|
|
assertNoErrors(t, diags)
|
|
|
|
// "restart" provisioner
|
|
pr.CloseCalled = false
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
logDiagnostics(t, diags)
|
|
t.Fatal("apply failed")
|
|
}
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
|
|
// Verify apply was invoked
|
|
if !pr.ProvisionResourceCalled {
|
|
t.Fatalf("provisioner was not called on apply")
|
|
}
|
|
|
|
// Verify output was suppressed
|
|
if !h.ProvisionOutputCalled {
|
|
t.Fatalf("ProvisionOutput hook not called")
|
|
}
|
|
if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) {
|
|
t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got)
|
|
}
|
|
if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) {
|
|
t.Errorf("expected hook to be called with %q, but was:\n%s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_warnings(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_resource" "foo" {
|
|
}`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
|
resp := testApplyFn(req)
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warning"))
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
assertNoErrors(t, diags)
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
}
|
|
|
|
inst := state.ResourceInstance(mustResourceInstanceAddr("test_resource.foo"))
|
|
if inst == nil {
|
|
t.Fatal("missing 'test_resource.foo' in state:", state)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_rpcDiagnostics(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_instance" "a" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
resp = testApplyFn(req)
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble"))
|
|
return resp
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if len(diags) == 0 {
|
|
t.Fatal("expected warnings")
|
|
}
|
|
|
|
for _, d := range diags {
|
|
des := d.Description().Summary
|
|
if !strings.Contains(des, "frobble") {
|
|
t.Fatalf(`expected frobble, got %q`, des)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_dataSensitive(t *testing.T) {
|
|
m := testModule(t, "apply-data-sensitive")
|
|
p := testProvider("null")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
// add the required id
|
|
m := req.Config.AsValueMap()
|
|
m["id"] = cty.StringVal("foo")
|
|
|
|
return providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(m),
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("diags: %s", diags.Err())
|
|
} else {
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
}
|
|
|
|
state, diags := ctx.Apply(plan, m)
|
|
assertNoErrors(t, diags)
|
|
|
|
addr := mustResourceInstanceAddr("data.null_data_source.testing")
|
|
|
|
dataSourceState := state.ResourceInstance(addr)
|
|
pvms := dataSourceState.Current.AttrSensitivePaths
|
|
if len(pvms) != 1 {
|
|
t.Fatalf("expected 1 sensitive path, got %d", len(pvms))
|
|
}
|
|
pvm := pvms[0]
|
|
if gotPath, wantPath := pvm.Path, cty.GetAttrPath("foo"); !gotPath.Equals(wantPath) {
|
|
t.Errorf("wrong path\n got: %#v\nwant: %#v", gotPath, wantPath)
|
|
}
|
|
if gotMarks, wantMarks := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !gotMarks.Equal(wantMarks) {
|
|
t.Errorf("wrong marks\n got: %#v\nwant: %#v", gotMarks, wantMarks)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorRestorePrivateData(t *testing.T) {
|
|
// empty config to remove our resource
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": "",
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{
|
|
// we error during apply, which will trigger core to preserve the last
|
|
// known state, including private data
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(errors.New("oops")),
|
|
}
|
|
|
|
addr := mustResourceInstanceAddr("test_object.a")
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Private: []byte("private"),
|
|
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
state, _ = ctx.Apply(plan, m)
|
|
if string(state.ResourceInstance(addr).Current.Private) != "private" {
|
|
t.Fatal("missing private data in state")
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_errorRestoreStatus(t *testing.T) {
|
|
// empty config to remove our resource
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": "",
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
|
// We error during apply, but return the current object state.
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("oops"))
|
|
// return a warning too to make sure it isn't dropped
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("warned"))
|
|
resp.NewState = req.PriorState
|
|
resp.Private = req.PlannedPrivate
|
|
return resp
|
|
}
|
|
|
|
addr := mustResourceInstanceAddr("test_object.a")
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectTainted,
|
|
AttrsJSON: []byte(`{"test_string":"foo"}`),
|
|
Private: []byte("private"),
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.b")},
|
|
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
state, diags = ctx.Apply(plan, m)
|
|
|
|
errString := diags.ErrWithWarnings().Error()
|
|
if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") {
|
|
t.Fatalf("error missing expected info: %q", errString)
|
|
}
|
|
|
|
if len(diags) != 2 {
|
|
t.Fatalf("expected 1 error and 1 warning, got: %q", errString)
|
|
}
|
|
|
|
res := state.ResourceInstance(addr)
|
|
if res == nil {
|
|
t.Fatal("resource was removed from state")
|
|
}
|
|
|
|
if res.Current.Status != states.ObjectTainted {
|
|
t.Fatal("resource should still be tainted in the state")
|
|
}
|
|
|
|
if len(res.Current.Dependencies) != 1 || !res.Current.Dependencies[0].Equal(mustConfigResourceAddr("test_object.b")) {
|
|
t.Fatalf("incorrect dependencies, got %q", res.Current.Dependencies)
|
|
}
|
|
|
|
if string(res.Current.Private) != "private" {
|
|
t.Fatalf("incorrect private data, got %q", res.Current.Private)
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_nonConformingResponse(t *testing.T) {
|
|
// empty config to remove our resource
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_object" "a" {
|
|
test_string = "x"
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
respDiags := tfdiags.Diagnostics(nil).Append(tfdiags.SimpleWarning("warned"))
|
|
respDiags = respDiags.Append(errors.New("oops"))
|
|
p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{
|
|
// Don't lose these diagnostics
|
|
Diagnostics: respDiags,
|
|
// This state is missing required attributes, and should produce an error
|
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
|
"test_string": cty.StringVal("x"),
|
|
}),
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
errString := diags.ErrWithWarnings().Error()
|
|
if !strings.Contains(errString, "oops") || !strings.Contains(errString, "warned") {
|
|
t.Fatalf("error missing expected info: %q", errString)
|
|
}
|
|
|
|
// we should have more than the ones returned from the provider, and they
|
|
// should not be coalesced into a single value
|
|
if len(diags) < 3 {
|
|
t.Fatalf("incorrect diagnostics, got %d values with %s", len(diags), diags.ErrWithWarnings())
|
|
}
|
|
}
|
|
|
|
func TestContext2Apply_nilResponse(t *testing.T) {
|
|
// empty config to remove our resource
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_object" "a" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := simpleMockProvider()
|
|
p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
_, diags = ctx.Apply(plan, m)
|
|
if !diags.HasErrors() {
|
|
t.Fatal("expected and error")
|
|
}
|
|
|
|
errString := diags.ErrWithWarnings().Error()
|
|
if !strings.Contains(errString, "invalid nil value") {
|
|
t.Fatalf("error missing expected info: %q", errString)
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// NOTE: Due to the size of this file, new tests should be added to
|
|
// context_apply2_test.go.
|
|
////////////////////////////////////////////////////////////////////////////////
|