mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-24 16:10:46 -06:00
2f5dcd5c0a
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
236 lines
7.3 KiB
Go
236 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 local
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/backend"
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/encryption"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/states/statemgr"
|
|
"github.com/opentofu/opentofu/internal/tofu"
|
|
)
|
|
|
|
// TestLocal returns a configured Local struct with temporary paths and
|
|
// in-memory ContextOpts.
|
|
//
|
|
// No operations will be called on the returned value, so you can still set
|
|
// public fields without any locks.
|
|
func TestLocal(t *testing.T) *Local {
|
|
t.Helper()
|
|
tempDir, err := filepath.EvalSymlinks(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
local := New(encryption.StateEncryptionDisabled())
|
|
local.StatePath = filepath.Join(tempDir, "state.tfstate")
|
|
local.StateOutPath = filepath.Join(tempDir, "state.tfstate")
|
|
local.StateBackupPath = filepath.Join(tempDir, "state.tfstate.bak")
|
|
local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
|
|
local.ContextOpts = &tofu.ContextOpts{}
|
|
|
|
return local
|
|
}
|
|
|
|
// TestLocalProvider modifies the ContextOpts of the *Local parameter to
|
|
// have a provider with the given name.
|
|
func TestLocalProvider(t *testing.T, b *Local, name string, schema providers.ProviderSchema) *tofu.MockProvider {
|
|
// Build a mock resource provider for in-memory operations
|
|
p := new(tofu.MockProvider)
|
|
|
|
p.GetProviderSchemaResponse = &schema
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
// this is a destroy plan,
|
|
if req.ProposedNewState.IsNull() {
|
|
resp.PlannedState = req.ProposedNewState
|
|
resp.PlannedPrivate = req.PriorPrivate
|
|
return resp
|
|
}
|
|
|
|
rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
|
|
if rSchema == nil {
|
|
rSchema = &configschema.Block{} // default schema is empty
|
|
}
|
|
plannedVals := map[string]cty.Value{}
|
|
for name, attrS := range rSchema.Attributes {
|
|
val := req.ProposedNewState.GetAttr(name)
|
|
if attrS.Computed && val.IsNull() {
|
|
val = cty.UnknownVal(attrS.Type)
|
|
}
|
|
plannedVals[name] = val
|
|
}
|
|
for name := range rSchema.BlockTypes {
|
|
// For simplicity's sake we just copy the block attributes over
|
|
// verbatim, since this package's mock providers are all relatively
|
|
// simple -- we're testing the backend, not esoteric provider features.
|
|
plannedVals[name] = req.ProposedNewState.GetAttr(name)
|
|
}
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
PlannedState: cty.ObjectVal(plannedVals),
|
|
PlannedPrivate: req.PriorPrivate,
|
|
}
|
|
}
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
return providers.ReadResourceResponse{NewState: req.PriorState}
|
|
}
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
|
return providers.ReadDataSourceResponse{State: req.Config}
|
|
}
|
|
|
|
// Initialize the opts
|
|
if b.ContextOpts == nil {
|
|
b.ContextOpts = &tofu.ContextOpts{}
|
|
}
|
|
|
|
// Set up our provider
|
|
b.ContextOpts.Providers = map[addrs.Provider]providers.Factory{
|
|
addrs.NewDefaultProvider(name): providers.FactoryFixed(p),
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
// TestLocalSingleState is a backend implementation that wraps Local
|
|
// and modifies it to only support single states (returns
|
|
// ErrWorkspacesNotSupported for multi-state operations).
|
|
//
|
|
// This isn't an actual use case, this is exported just to provide a
|
|
// easy way to test that behavior.
|
|
type TestLocalSingleState struct {
|
|
*Local
|
|
}
|
|
|
|
// TestNewLocalSingle is a factory for creating a TestLocalSingleState.
|
|
// This function matches the signature required for backend/init.
|
|
func TestNewLocalSingle(enc encryption.StateEncryption) backend.Backend {
|
|
return &TestLocalSingleState{Local: New(encryption.StateEncryptionDisabled())}
|
|
}
|
|
|
|
func (b *TestLocalSingleState) Workspaces() ([]string, error) {
|
|
return nil, backend.ErrWorkspacesNotSupported
|
|
}
|
|
|
|
func (b *TestLocalSingleState) DeleteWorkspace(string, bool) error {
|
|
return backend.ErrWorkspacesNotSupported
|
|
}
|
|
|
|
func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
|
|
if name != backend.DefaultStateName {
|
|
return nil, backend.ErrWorkspacesNotSupported
|
|
}
|
|
|
|
return b.Local.StateMgr(name)
|
|
}
|
|
|
|
// TestLocalNoDefaultState is a backend implementation that wraps
|
|
// Local and modifies it to support named states, but not the
|
|
// default state. It returns ErrDefaultWorkspaceNotSupported when
|
|
// the DefaultStateName is used.
|
|
type TestLocalNoDefaultState struct {
|
|
*Local
|
|
}
|
|
|
|
// TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
|
|
// This function matches the signature required for backend/init.
|
|
func TestNewLocalNoDefault(enc encryption.StateEncryption) backend.Backend {
|
|
return &TestLocalNoDefaultState{Local: New(encryption.StateEncryptionDisabled())}
|
|
}
|
|
|
|
func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
|
|
workspaces, err := b.Local.Workspaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filtered := workspaces[:0]
|
|
for _, name := range workspaces {
|
|
if name != backend.DefaultStateName {
|
|
filtered = append(filtered, name)
|
|
}
|
|
}
|
|
|
|
return filtered, nil
|
|
}
|
|
|
|
func (b *TestLocalNoDefaultState) DeleteWorkspace(name string, force bool) error {
|
|
if name == backend.DefaultStateName {
|
|
return backend.ErrDefaultWorkspaceNotSupported
|
|
}
|
|
return b.Local.DeleteWorkspace(name, force)
|
|
}
|
|
|
|
func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
|
|
if name == backend.DefaultStateName {
|
|
return nil, backend.ErrDefaultWorkspaceNotSupported
|
|
}
|
|
return b.Local.StateMgr(name)
|
|
}
|
|
|
|
func testStateFile(t *testing.T, path string, s *states.State) {
|
|
t.Helper()
|
|
|
|
if err := statemgr.WriteAndPersist(statemgr.NewFilesystem(path, encryption.StateEncryptionDisabled()), s, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func mustProviderConfig(s string) addrs.AbsProviderConfig {
|
|
p, diags := addrs.ParseAbsProviderConfigStr(s)
|
|
if diags.HasErrors() {
|
|
panic(diags.Err())
|
|
}
|
|
return p
|
|
}
|
|
|
|
func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance {
|
|
addr, diags := addrs.ParseAbsResourceInstanceStr(s)
|
|
if diags.HasErrors() {
|
|
panic(diags.Err())
|
|
}
|
|
return addr
|
|
}
|
|
|
|
// assertBackendStateUnlocked attempts to lock the backend state for a test.
|
|
// Failure indicates that the state was locked and false is returned.
|
|
// True is returned if a lock was obtained.
|
|
func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
|
|
t.Helper()
|
|
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
|
|
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
|
t.Errorf("state is already locked: %s", err.Error())
|
|
// lock was obtained
|
|
return false
|
|
}
|
|
// lock was not obtained
|
|
return true
|
|
}
|
|
|
|
// assertBackendStateLocked attempts to lock the backend state for a test.
|
|
// Failure indicates that the state was not locked and false is returned.
|
|
// True is returned if a lock was not obtained.
|
|
func assertBackendStateLocked(t *testing.T, b *Local) bool {
|
|
t.Helper()
|
|
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
|
|
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
|
// lock was not obtained
|
|
return true
|
|
}
|
|
t.Error("unexpected success locking state")
|
|
// lock was obtained
|
|
return false
|
|
}
|