mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
e0ecd2ebb3
Signed-off-by: RLRabinowitz <rlrabinowitz2@gmail.com>
1689 lines
48 KiB
Go
1689 lines
48 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
)
|
|
|
|
func TestContext2Refresh(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.web").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`),
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: readState,
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
t.Fatal("ReadResource should be called")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
newState, err := schema.CoerceValue(fromState.Value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(readState, newState, valueComparer) {
|
|
t.Fatal(cmp.Diff(readState, newState, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dynamicAttr(t *testing.T) {
|
|
m := testModule(t, "refresh-dynamic")
|
|
|
|
startingState := states.BuildState(func(ss *states.SyncState) {
|
|
ss.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"dynamic":{"type":"string","value":"hello"}}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
readStateVal := cty.ObjectVal(map[string]cty.Value{
|
|
"dynamic": cty.EmptyTupleVal,
|
|
})
|
|
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"dynamic": {Type: cty.DynamicPseudoType, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{
|
|
NewState: readStateVal,
|
|
}
|
|
}
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return resp
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
s, diags := ctx.Refresh(m, startingState, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
t.Fatal("ReadResource should be called")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
newState, err := mod.Resources["test_instance.foo"].Instances[addrs.NoKey].Current.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(readStateVal, newState.Value, valueComparer) {
|
|
t.Error(cmp.Diff(newState.Value, readStateVal, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataComputedModuleVar(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-data-module-var")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
obj := req.ProposedNewState.AsValueMap()
|
|
obj["id"] = cty.UnknownVal(cty.String)
|
|
resp.PlannedState = cty.ObjectVal(obj)
|
|
return resp
|
|
}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
resp.State = req.Config
|
|
return resp
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"aws_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"output": {
|
|
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{Mode: plans.RefreshOnlyMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, plan.PriorState, `
|
|
<no state>
|
|
`)
|
|
}
|
|
|
|
func TestContext2Refresh_targeted(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_elb": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"instances": {
|
|
Type: cty.Set(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"vpc_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
m := testModule(t, "refresh-targeted")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
refreshedResources := make([]string, 0, 2)
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "me",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"vpc-abc123", "i-abc123"}
|
|
if !reflect.DeepEqual(refreshedResources, expected) {
|
|
t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_targetedCount(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_elb": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"instances": {
|
|
Type: cty.Set(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"vpc_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
m := testModule(t, "refresh-targeted-count")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
refreshedResources := make([]string, 0, 2)
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.Resource(
|
|
addrs.ManagedResourceMode, "aws_instance", "me",
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
// Target didn't specify index, so we should get all our instances
|
|
expected := []string{
|
|
"vpc-abc123",
|
|
"i-abc123",
|
|
"i-cde567",
|
|
"i-cde789",
|
|
}
|
|
sort.Strings(expected)
|
|
sort.Strings(refreshedResources)
|
|
if !reflect.DeepEqual(refreshedResources, expected) {
|
|
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_targetedCountIndex(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_elb": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"instances": {
|
|
Type: cty.Set(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"vpc_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
"aws_vpc": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
m := testModule(t, "refresh-targeted-count")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
refreshedResources := make([]string, 0, 2)
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
|
Mode: plans.NormalMode,
|
|
Targets: []addrs.Targetable{
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
addrs.ManagedResourceMode, "aws_instance", "me", addrs.IntKey(0),
|
|
),
|
|
},
|
|
})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
expected := []string{"vpc-abc123", "i-abc123"}
|
|
if !reflect.DeepEqual(refreshedResources, expected) {
|
|
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_moduleComputedVar(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"value": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "refresh-module-computed-var")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
// This was failing (see GH-2188) at some point, so this test just
|
|
// verifies that the failure goes away.
|
|
if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() {
|
|
t.Fatalf("refresh errs: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_delete(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block.ImpliedType()),
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
if len(mod.Resources) > 0 {
|
|
t.Fatal("resources should be empty")
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_ignoreUncreated(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
_, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
if p.ReadResourceCalled {
|
|
t.Fatal("refresh should not be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_hook(t *testing.T) {
|
|
h := new(MockHook)
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Hooks: []Hook{h},
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if _, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() {
|
|
t.Fatalf("refresh errs: %s", diags.Err())
|
|
}
|
|
if !h.PreRefreshCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
if !h.PostRefreshCalled {
|
|
t.Fatal("should be called")
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_modules(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-modules")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceTainted(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
testSetResourceInstanceCurrent(child, "aws_instance.web", `{"id":"baz"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
if !req.PriorState.GetAttr("id").RawEquals(cty.StringVal("baz")) {
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
new, _ := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
|
|
if len(path) == 1 && path[0].(cty.GetAttrStep).Name == "id" {
|
|
return cty.StringVal("new"), nil
|
|
}
|
|
return v, nil
|
|
})
|
|
return providers.ReadResourceResponse{
|
|
NewState: new,
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testContextRefreshModuleStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_moduleInputComputedOutput(t *testing.T) {
|
|
m := testModule(t, "refresh-module-input-computed-output")
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
"compute": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() {
|
|
t.Fatalf("refresh errs: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_moduleVarModule(t *testing.T) {
|
|
m := testModule(t, "refresh-module-var-module")
|
|
p := testProvider("aws")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() {
|
|
t.Fatalf("refresh errs: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// GH-70
|
|
func TestContext2Refresh_noState(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-no-state")
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
if _, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode}); diags.HasErrors() {
|
|
t.Fatalf("refresh errs: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_output(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "refresh-output")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo","foo":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
root.SetOutputValue("foo", cty.StringVal("foo"), false)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testContextRefreshOutputStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%q\n\nwant:\n%q", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_outputPartial(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-output-partial")
|
|
|
|
// Refresh creates a partial plan for any instances that don't have
|
|
// remote objects yet, to get stub values for interpolation. Therefore
|
|
// we need to make DiffFn available to let that complete.
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block.ImpliedType()),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.foo", `{}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testContextRefreshOutputPartialStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_stateBasic(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
|
ty := schema.ImpliedType()
|
|
|
|
readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: readStateVal,
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
t.Fatal("read resource should be called")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataCount(t *testing.T) {
|
|
p := testProvider("test")
|
|
m := testModule(t, "refresh-data-count")
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
m := req.ProposedNewState.AsValueMap()
|
|
m["things"] = cty.ListVal([]cty.Value{cty.StringVal("foo")})
|
|
resp.PlannedState = cty.ObjectVal(m)
|
|
return resp
|
|
}
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"things": {Type: cty.List(cty.String), Computed: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"test": {},
|
|
},
|
|
})
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{
|
|
State: req.Config,
|
|
}
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
s, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode})
|
|
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
checkStateString(t, s, `<no state>`)
|
|
}
|
|
|
|
func TestContext2Refresh_dataState(t *testing.T) {
|
|
m := testModule(t, "refresh-data-resource-basic")
|
|
state := states.NewState()
|
|
schema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"inputs": {
|
|
Type: cty.Map(cty.String),
|
|
Optional: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
p := testProvider("null")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
DataSources: map[string]*configschema.Block{
|
|
"null_data_source": schema,
|
|
},
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
var readStateVal cty.Value
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
m := req.Config.AsValueMap()
|
|
readStateVal = cty.ObjectVal(m)
|
|
|
|
return providers.ReadDataSourceResponse{
|
|
State: readStateVal,
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
if !p.ReadDataSourceCalled {
|
|
t.Fatal("ReadDataSource should have been called")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
|
|
newState, err := mod.Resources["data.null_data_source.testing"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataStateRefData(t *testing.T) {
|
|
p := testProvider("null")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
DataSources: map[string]*configschema.Block{
|
|
"null_data_source": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"bar": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "refresh-data-ref-data")
|
|
state := states.NewState()
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
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),
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testTerraformRefreshDataRefDataStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_tainted(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-basic")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceTainted(root, "aws_instance.web", `{"id":"bar"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
// add the required id
|
|
m := req.PriorState.AsValueMap()
|
|
m["id"] = cty.StringVal("foo")
|
|
|
|
return providers.ReadResourceResponse{
|
|
NewState: cty.ObjectVal(m),
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
if !p.ReadResourceCalled {
|
|
t.Fatal("ReadResource was not called; should have been")
|
|
}
|
|
|
|
actual := strings.TrimSpace(s.String())
|
|
expected := strings.TrimSpace(testContextRefreshTaintedStr)
|
|
if actual != expected {
|
|
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
}
|
|
}
|
|
|
|
// Doing a Refresh (or any operation really, but Refresh usually
|
|
// happens first) with a config with an unknown provider should result in
|
|
// an error. The key bug this found was that this wasn't happening if
|
|
// Providers was _empty_.
|
|
func TestContext2Refresh_unknownProvider(t *testing.T) {
|
|
m := testModule(t, "refresh-unknown-provider")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
c, diags := NewContext(&ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{},
|
|
})
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
_, diags = c.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode})
|
|
if !diags.HasErrors() {
|
|
t.Fatal("successfully refreshed; want error")
|
|
}
|
|
|
|
if got, want := diags.Err().Error(), "Missing required provider"; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_vars(t *testing.T) {
|
|
p := testProvider("aws")
|
|
|
|
schema := &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"ami": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{"aws_instance": schema},
|
|
})
|
|
|
|
m := testModule(t, "refresh-vars")
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.web", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
|
|
"id": cty.StringVal("foo"),
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
|
NewState: readStateVal,
|
|
}
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: req.ProposedNewState,
|
|
}
|
|
}
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
t.Fatal("read resource should be called")
|
|
}
|
|
|
|
mod := s.RootModule()
|
|
|
|
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(readStateVal, newState.Value, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(readStateVal, newState.Value, valueComparer, equateEmpty))
|
|
}
|
|
|
|
for _, r := range mod.Resources {
|
|
if r.Addr.Resource.Type == "" {
|
|
t.Fatalf("no type: %#v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_orphanModule(t *testing.T) {
|
|
p := testProvider("aws")
|
|
m := testModule(t, "refresh-module-orphan")
|
|
|
|
// Create a custom refresh function to track the order they were visited
|
|
var order []string
|
|
var orderLock sync.Mutex
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
orderLock.Lock()
|
|
defer orderLock.Unlock()
|
|
|
|
order = append(order, req.PriorState.GetAttr("id").AsString())
|
|
return providers.ReadResourceResponse{
|
|
NewState: req.PriorState,
|
|
}
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
Dependencies: []addrs.ConfigResource{
|
|
{Module: addrs.Module{"module.child"}},
|
|
{Module: addrs.Module{"module.child"}},
|
|
},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
child.SetResourceInstanceCurrent(
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"i-bcd23"}`),
|
|
Dependencies: []addrs.ConfigResource{{Module: addrs.Module{"module.grandchild"}}},
|
|
},
|
|
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
|
)
|
|
grandchild := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey).Child("grandchild", addrs.NoKey))
|
|
testSetResourceInstanceCurrent(grandchild, "aws_instance.baz", `{"id":"i-cde345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
testCheckDeadlock(t, func() {
|
|
_, err := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err.Err())
|
|
}
|
|
|
|
// TODO: handle order properly for orphaned modules / resources
|
|
// expected := []string{"i-abc123", "i-bcd234", "i-cde345"}
|
|
// if !reflect.DeepEqual(order, expected) {
|
|
// t.Fatalf("expected: %#v, got: %#v", expected, order)
|
|
// }
|
|
})
|
|
}
|
|
|
|
func TestContext2Validate(t *testing.T) {
|
|
p := testProvider("aws")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
Provider: &configschema.Block{},
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"aws_instance": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"foo": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"num": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
m := testModule(t, "validate-good")
|
|
c := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
diags := c.Validate(m)
|
|
if len(diags) != 0 {
|
|
t.Fatalf("unexpected error: %#v", diags.ErrWithWarnings())
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_updateProviderInState(t *testing.T) {
|
|
m := testModule(t, "update-resource-provider")
|
|
p := testProvider("aws")
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "aws_instance.bar", `{"id":"foo"}`, `provider["registry.opentofu.org/hashicorp/aws"].baz`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
expected := strings.TrimSpace(`
|
|
aws_instance.bar:
|
|
ID = foo
|
|
provider = provider["registry.opentofu.org/hashicorp/aws"].foo`)
|
|
|
|
s, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
actual := s.String()
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_schemaUpgradeFlatmap(t *testing.T) {
|
|
m := testModule(t, "refresh-schema-upgrade")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"name": { // imagining we renamed this from "id"
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResourceTypeSchemaVersions: map[string]uint64{
|
|
"test_thing": 5,
|
|
},
|
|
})
|
|
p.UpgradeResourceStateResponse = &providers.UpgradeResourceStateResponse{
|
|
UpgradedState: cty.ObjectVal(map[string]cty.Value{
|
|
"name": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
s := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_thing",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
SchemaVersion: 3,
|
|
AttrsFlat: map[string]string{
|
|
"id": "foo",
|
|
},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state, diags := ctx.Refresh(m, s, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
{
|
|
got := p.UpgradeResourceStateRequest
|
|
want := providers.UpgradeResourceStateRequest{
|
|
TypeName: "test_thing",
|
|
Version: 3,
|
|
RawStateFlatmap: map[string]string{
|
|
"id": "foo",
|
|
},
|
|
}
|
|
if !cmp.Equal(got, want) {
|
|
t.Errorf("wrong upgrade request\n%s", cmp.Diff(want, got))
|
|
}
|
|
}
|
|
|
|
{
|
|
got := state.String()
|
|
want := strings.TrimSpace(`
|
|
test_thing.bar:
|
|
ID =
|
|
provider = provider["registry.opentofu.org/hashicorp/test"]
|
|
name = foo
|
|
`)
|
|
if got != want {
|
|
t.Fatalf("wrong result state\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_schemaUpgradeJSON(t *testing.T) {
|
|
m := testModule(t, "refresh-schema-upgrade")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_thing": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"name": { // imagining we renamed this from "id"
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResourceTypeSchemaVersions: map[string]uint64{
|
|
"test_thing": 5,
|
|
},
|
|
})
|
|
p.UpgradeResourceStateResponse = &providers.UpgradeResourceStateResponse{
|
|
UpgradedState: cty.ObjectVal(map[string]cty.Value{
|
|
"name": cty.StringVal("foo"),
|
|
}),
|
|
}
|
|
|
|
s := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_thing",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
SchemaVersion: 3,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state, diags := ctx.Refresh(m, s, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
{
|
|
got := p.UpgradeResourceStateRequest
|
|
want := providers.UpgradeResourceStateRequest{
|
|
TypeName: "test_thing",
|
|
Version: 3,
|
|
RawStateJSON: []byte(`{"id":"foo"}`),
|
|
}
|
|
if !cmp.Equal(got, want) {
|
|
t.Errorf("wrong upgrade request\n%s", cmp.Diff(want, got))
|
|
}
|
|
}
|
|
|
|
{
|
|
got := state.String()
|
|
want := strings.TrimSpace(`
|
|
test_thing.bar:
|
|
ID =
|
|
provider = provider["registry.opentofu.org/hashicorp/test"]
|
|
name = foo
|
|
`)
|
|
if got != want {
|
|
t.Fatalf("wrong result state\ngot:\n%s\n\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataValidation(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
data "aws_data_source" "foo" {
|
|
foo = "bar"
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
resp.PlannedState = req.ProposedNewState
|
|
return
|
|
}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
resp.State = req.Config
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Refresh(m, states.NewState(), &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
// Should get this error:
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if !p.ValidateDataResourceConfigCalled {
|
|
t.Fatal("ValidateDataSourceConfig not called during plan")
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataResourceDependsOn(t *testing.T) {
|
|
m := testModule(t, "plan-data-depends-on")
|
|
p := testProvider("test")
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
"test_resource": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
DataSources: map[string]*configschema.Block{
|
|
"test_data": {
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"compute": {Type: cty.String, Computed: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"compute": cty.StringVal("value"),
|
|
}),
|
|
}
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
testSetResourceInstanceCurrent(root, "test_resource.a", `{"id":"a"}`, `provider["registry.opentofu.org/hashicorp/test"]`)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
}
|
|
}
|
|
|
|
// verify that create_before_destroy is updated in the state during refresh
|
|
func TestRefresh_updateLifecycle(t *testing.T) {
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "aws_instance",
|
|
Name: "bar",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "aws_instance" "bar" {
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
state, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("plan errors: %s", diags.Err())
|
|
}
|
|
|
|
r := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.bar"))
|
|
if !r.Current.CreateBeforeDestroy {
|
|
t.Fatal("create_before_destroy not updated in instance state")
|
|
}
|
|
}
|
|
|
|
func TestContext2Refresh_dataSourceOrphan(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": ``,
|
|
})
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.DataResourceMode,
|
|
Type: "test_data_source",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey),
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
p := testProvider("test")
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
resp.State = cty.NullVal(req.Config.Type())
|
|
return
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
_, diags := ctx.Refresh(m, state, &PlanOpts{Mode: plans.NormalMode})
|
|
if diags.HasErrors() {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
|
|
if p.ReadResourceCalled {
|
|
t.Fatal("there are no managed resources to read")
|
|
}
|
|
|
|
if p.ReadDataSourceCalled {
|
|
t.Fatal("orphaned data source instance should not be read")
|
|
}
|
|
}
|
|
|
|
// Legacy providers may return invalid null values for blocks, causing noise in
|
|
// the diff output and unexpected behavior with ignore_changes. Make sure
|
|
// refresh fixes these up before storing the state.
|
|
func TestContext2Refresh_reifyNullBlock(t *testing.T) {
|
|
m := testModuleInline(t, map[string]string{
|
|
"main.tf": `
|
|
resource "test_resource" "foo" {
|
|
}
|
|
`,
|
|
})
|
|
|
|
p := new(MockProvider)
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
// incorrectly return a null _set_block value
|
|
v := req.PriorState.AsValueMap()
|
|
v["set_block"] = cty.NullVal(v["set_block"].Type())
|
|
return providers.ReadResourceResponse{NewState: cty.ObjectVal(v)}
|
|
}
|
|
|
|
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,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
"set_block": {
|
|
Block: configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"a": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
Nesting: configschema.NestingSet,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
fooAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey)
|
|
|
|
state := states.NewState()
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
root.SetResourceInstanceCurrent(
|
|
fooAddr,
|
|
&states.ResourceInstanceObjectSrc{
|
|
Status: states.ObjectReady,
|
|
AttrsJSON: []byte(`{"id":"foo", "network_interface":[]}`),
|
|
Dependencies: []addrs.ConfigResource{},
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
},
|
|
})
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode})
|
|
if diags.HasErrors() {
|
|
t.Fatalf("refresh errors: %s", diags.Err())
|
|
}
|
|
|
|
jsonState := plan.PriorState.ResourceInstance(fooAddr.Absolute(addrs.RootModuleInstance)).Current.AttrsJSON
|
|
|
|
// the set_block should still be an empty container, and not null
|
|
expected := `{"id":"foo","set_block":[]}`
|
|
if string(jsonState) != expected {
|
|
t.Fatalf("invalid state\nexpected: %s\ngot: %s\n", expected, jsonState)
|
|
}
|
|
}
|