mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Our existing state model is designed with the assumption that anyone who is accessing a ResourceInstanceObjectSrc will be doing so from an object representing the entire state at once, and so they can separately fetch information about the resource as a whole and the provider instance it belongs to if needed. In our new language runtime we want to be able to just pass around pointers to individual resource instance object states rather than the entire state data structure, and so these two new "full" variants extend the object representation with a provider instance address and the name of the resource type within that provider. This also adopts a slightly different representation of the value itself, making use of some newer Go language features, so that we don't need quite so much duplication between the "source" and decoded versions of this model. In practice it's only the Value field that needs to vary between the two, because that's the part where we require provider schema information to decode. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
240 lines
7.3 KiB
Go
240 lines
7.3 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package states
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/lang/marks"
|
|
"github.com/zclconf/go-cty-debug/ctydebug"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// The tests in here are currently pretty minimal as a compromise while we're
|
|
// still trying to learn if this new representation is even a viable approach.
|
|
// The focus is mainly on whether we're correcting translating from and to the
|
|
// traditional old state representation, since we'll still be using the old
|
|
// models as our primary representation for now so we can continue to use
|
|
// unmodified state manager implementations, etc.
|
|
//
|
|
// TODO: If these new codepaths survive beyond the early exploratory work
|
|
// for the new language runtime then we should consider what fuller testing
|
|
// might be helpful here, and implement it.
|
|
|
|
func TestSyncStateResourceInstanceObjectFull(t *testing.T) {
|
|
instAddrRel := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "baz",
|
|
}.Instance(addrs.NoKey)
|
|
instAddrAbs := instAddrRel.Absolute(addrs.RootModuleInstance)
|
|
providerInstAddr := addrs.AbsProviderInstanceCorrect{
|
|
Config: addrs.AbsProviderConfigCorrect{
|
|
Module: addrs.RootModuleInstance.Child("mod", addrs.NoKey),
|
|
Config: addrs.ProviderConfigCorrect{
|
|
Provider: addrs.NewBuiltInProvider("foo"),
|
|
},
|
|
},
|
|
}
|
|
depAddr := addrs.ConfigResource{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "dependency",
|
|
},
|
|
}
|
|
deposedKey := DeposedKey("...")
|
|
legacyProviderConfigAddr := addrs.AbsProviderConfig{
|
|
Module: providerInstAddr.Config.Module.Module(),
|
|
Provider: providerInstAddr.Config.Config.Provider,
|
|
}
|
|
objTy := cty.Object(map[string]cty.Type{
|
|
"a": cty.Number,
|
|
"b": cty.Number,
|
|
})
|
|
|
|
s := NewState()
|
|
s.RootModule().SetResourceInstanceCurrent(instAddrRel, &ResourceInstanceObjectSrc{
|
|
Status: ObjectReady,
|
|
AttrsJSON: []byte(`{"a":1,"b":2}`),
|
|
AttrSensitivePaths: []cty.PathValueMarks{
|
|
{Path: cty.GetAttrPath("b"), Marks: cty.NewValueMarks(marks.Sensitive)},
|
|
},
|
|
Private: []byte(`private`),
|
|
CreateBeforeDestroy: true,
|
|
SchemaVersion: 5,
|
|
Dependencies: []addrs.ConfigResource{
|
|
depAddr,
|
|
},
|
|
}, legacyProviderConfigAddr, addrs.NoKey)
|
|
s.RootModule().SetResourceInstanceDeposed(instAddrRel, deposedKey, &ResourceInstanceObjectSrc{
|
|
Status: ObjectReady,
|
|
AttrsJSON: []byte(`{"a":0,"b":1}`),
|
|
}, legacyProviderConfigAddr, addrs.NoKey)
|
|
|
|
ss := s.SyncWrapper()
|
|
|
|
t.Run("current object", func(t *testing.T) {
|
|
gotObjSrc := ss.ResourceInstanceObjectFull(instAddrAbs, NotDeposed)
|
|
gotObj, err := DecodeResourceInstanceObjectFull(gotObjSrc, objTy)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
wantObj := &ResourceInstanceObjectFull{
|
|
Status: ObjectReady,
|
|
Value: cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.NumberIntVal(1),
|
|
"b": cty.NumberIntVal(2).Mark(marks.Sensitive),
|
|
}),
|
|
Private: []byte(`private`),
|
|
CreateBeforeDestroy: true,
|
|
SchemaVersion: 5,
|
|
ProviderInstanceAddr: providerInstAddr,
|
|
ResourceType: "foo",
|
|
Dependencies: []addrs.ConfigResource{
|
|
depAddr,
|
|
},
|
|
}
|
|
if diff := cmp.Diff(wantObj, gotObj, ctydebug.CmpOptions); diff != "" {
|
|
t.Error("wrong result\n" + diff)
|
|
}
|
|
})
|
|
t.Run("deposed object", func(t *testing.T) {
|
|
gotObjSrc := ss.ResourceInstanceObjectFull(instAddrAbs, deposedKey)
|
|
gotObj, err := DecodeResourceInstanceObjectFull(gotObjSrc, objTy)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
wantObj := &ResourceInstanceObjectFull{
|
|
Status: ObjectReady,
|
|
Value: cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.NumberIntVal(0),
|
|
"b": cty.NumberIntVal(1),
|
|
}),
|
|
ProviderInstanceAddr: providerInstAddr,
|
|
ResourceType: "foo",
|
|
}
|
|
if diff := cmp.Diff(wantObj, gotObj, ctydebug.CmpOptions); diff != "" {
|
|
t.Error("wrong result\n" + diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSyncStateSetResourceInstanceObjectFull(t *testing.T) {
|
|
instAddrRel := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "baz",
|
|
}.Instance(addrs.NoKey)
|
|
instAddrAbs := instAddrRel.Absolute(addrs.RootModuleInstance)
|
|
providerInstAddr := addrs.AbsProviderInstanceCorrect{
|
|
Config: addrs.AbsProviderConfigCorrect{
|
|
Module: addrs.RootModuleInstance.Child("mod", addrs.NoKey),
|
|
Config: addrs.ProviderConfigCorrect{
|
|
Provider: addrs.NewBuiltInProvider("foo"),
|
|
},
|
|
},
|
|
}
|
|
depAddr := addrs.ConfigResource{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "foo",
|
|
Name: "dependency",
|
|
},
|
|
}
|
|
deposedKey := DeposedKey("...")
|
|
legacyProviderConfigAddr := addrs.AbsProviderConfig{
|
|
Module: providerInstAddr.Config.Module.Module(),
|
|
Provider: providerInstAddr.Config.Config.Provider,
|
|
}
|
|
objTy := cty.Object(map[string]cty.Type{
|
|
"a": cty.Number,
|
|
"b": cty.Number,
|
|
})
|
|
|
|
currentObj := &ResourceInstanceObjectFull{
|
|
Status: ObjectReady,
|
|
Value: cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.NumberIntVal(1),
|
|
"b": cty.NumberIntVal(2).Mark(marks.Sensitive),
|
|
}),
|
|
Private: []byte(`private`),
|
|
CreateBeforeDestroy: true,
|
|
SchemaVersion: 5,
|
|
ProviderInstanceAddr: providerInstAddr,
|
|
ResourceType: "foo",
|
|
Dependencies: []addrs.ConfigResource{
|
|
depAddr,
|
|
},
|
|
}
|
|
deposedObj := &ResourceInstanceObjectFull{
|
|
Status: ObjectReady,
|
|
Value: cty.ObjectVal(map[string]cty.Value{
|
|
"a": cty.NumberIntVal(0),
|
|
"b": cty.NumberIntVal(1),
|
|
}),
|
|
ProviderInstanceAddr: providerInstAddr,
|
|
ResourceType: "foo",
|
|
}
|
|
currentObjSrc, err := EncodeResourceInstanceObjectFull(currentObj, objTy)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
deposedObjSrc, err := EncodeResourceInstanceObjectFull(deposedObj, objTy)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
gotState := BuildState(func(ss *SyncState) {
|
|
ss.SetResourceInstanceObjectFull(instAddrAbs, deposedKey, deposedObjSrc)
|
|
ss.SetResourceInstanceObjectFull(instAddrAbs, NotDeposed, currentObjSrc)
|
|
})
|
|
wantState := &State{
|
|
Modules: map[string]*Module{
|
|
"": {
|
|
Addr: addrs.RootModuleInstance,
|
|
Resources: map[string]*Resource{
|
|
"foo.baz": {
|
|
Addr: instAddrAbs.ContainingResource(),
|
|
ProviderConfig: legacyProviderConfigAddr,
|
|
Instances: map[addrs.InstanceKey]*ResourceInstance{
|
|
addrs.NoKey: {
|
|
Current: &ResourceInstanceObjectSrc{
|
|
Status: ObjectReady,
|
|
AttrsJSON: []byte(`{"a":1,"b":2}`),
|
|
AttrSensitivePaths: []cty.PathValueMarks{
|
|
{Path: cty.GetAttrPath("b"), Marks: cty.NewValueMarks(marks.Sensitive)},
|
|
},
|
|
Private: []byte(`private`),
|
|
CreateBeforeDestroy: true,
|
|
SchemaVersion: 5,
|
|
Dependencies: []addrs.ConfigResource{
|
|
depAddr,
|
|
},
|
|
},
|
|
Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{
|
|
deposedKey: {
|
|
Status: ObjectReady,
|
|
AttrsJSON: []byte(`{"a":0,"b":1}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
LocalValues: make(map[string]cty.Value),
|
|
OutputValues: make(map[string]*OutputValue),
|
|
},
|
|
},
|
|
}
|
|
if diff := cmp.Diff(wantState, gotState); diff != "" {
|
|
t.Error("wrong result\n" + diff)
|
|
}
|
|
|
|
}
|