mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
Fix checksum issue with remote state If we read a state file with "null" objects in a module and they become initialized to an empty map the state file may be written out with empty objects rather than "null", changing the checksum. If we can detect this, increment the serial number to prevent a conflict in atlas. Our fakeAtlas test server now needs to decode the state directly rather than using the ReadState function, so as to be able to read the state unaltered. The terraform.State data structures have initialization spread out throughout the package. More thoroughly initialize State during ReadState, and add a call to init() during WriteState as another normalization safeguard. Expose State.init through an exported Init() method, so that a new State can be completely realized outside of the terraform package. Additionally, the internal init now completely walks all internal state structures ensuring that all maps and slices are initialized. While it was mentioned before that the `init()` methods are problematic with too many call sites, expanding this out better exposes the entry points that will need to be refactored later for improved concurrency handling. The State structures had a mix of `omitempty` fields. Remove omitempty for all maps and slices as part of this normalization process. Make Lineage mandatory, which is now explicitly set in some tests.
289 lines
6.1 KiB
Go
289 lines
6.1 KiB
Go
package command
|
|
|
|
import (
|
|
"flag"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-getter"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/hashicorp/terraform/helper/logging"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
// This is the directory where our test fixtures are.
|
|
var fixtureDir = "./test-fixtures"
|
|
|
|
func init() {
|
|
test = true
|
|
|
|
// Expand the fixture dir on init because we change the working
|
|
// directory in some tests.
|
|
var err error
|
|
fixtureDir, err = filepath.Abs(fixtureDir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
flag.Parse()
|
|
if testing.Verbose() {
|
|
// if we're verbose, use the logging requested by TF_LOG
|
|
logging.SetOutput()
|
|
} else {
|
|
// otherwise silence all logs
|
|
log.SetOutput(ioutil.Discard)
|
|
}
|
|
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func tempDir(t *testing.T) string {
|
|
dir, err := ioutil.TempDir("", "tf")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return dir
|
|
}
|
|
|
|
func testFixturePath(name string) string {
|
|
return filepath.Join(fixtureDir, name)
|
|
}
|
|
|
|
func testCtxConfig(p terraform.ResourceProvider) *terraform.ContextOpts {
|
|
return &terraform.ContextOpts{
|
|
Providers: map[string]terraform.ResourceProviderFactory{
|
|
"test": func() (terraform.ResourceProvider, error) {
|
|
return p, nil
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func testCtxConfigWithShell(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *terraform.ContextOpts {
|
|
return &terraform.ContextOpts{
|
|
Providers: map[string]terraform.ResourceProviderFactory{
|
|
"test": func() (terraform.ResourceProvider, error) {
|
|
return p, nil
|
|
},
|
|
},
|
|
Provisioners: map[string]terraform.ResourceProvisionerFactory{
|
|
"shell": func() (terraform.ResourceProvisioner, error) {
|
|
return pr, nil
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func testModule(t *testing.T, name string) *module.Tree {
|
|
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
s := &getter.FolderStorage{StorageDir: tempDir(t)}
|
|
if err := mod.Load(s, module.GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return mod
|
|
}
|
|
|
|
func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
|
path := testTempFile(t)
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := terraform.WritePlan(plan, f); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
p, err := terraform.ReadPlan(f)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// testState returns a test State structure that we use for a lot of tests.
|
|
func testState() *terraform.State {
|
|
state := &terraform.State{
|
|
Version: 2,
|
|
Modules: []*terraform.ModuleState{
|
|
&terraform.ModuleState{
|
|
Path: []string{"root"},
|
|
Resources: map[string]*terraform.ResourceState{
|
|
"test_instance.foo": &terraform.ResourceState{
|
|
Type: "test_instance",
|
|
Primary: &terraform.InstanceState{
|
|
ID: "bar",
|
|
},
|
|
},
|
|
},
|
|
Outputs: map[string]*terraform.OutputState{},
|
|
},
|
|
},
|
|
}
|
|
state.Init()
|
|
return state
|
|
}
|
|
|
|
func testStateFile(t *testing.T, s *terraform.State) string {
|
|
path := testTempFile(t)
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := terraform.WriteState(s, f); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
// testStateFileDefault writes the state out to the default statefile
|
|
// in the cwd. Use `testCwd` to change into a temp cwd.
|
|
func testStateFileDefault(t *testing.T, s *terraform.State) string {
|
|
f, err := os.Create(DefaultStateFilename)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := terraform.WriteState(s, f); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return DefaultStateFilename
|
|
}
|
|
|
|
// testStateFileRemote writes the state out to the remote statefile
|
|
// in the cwd. Use `testCwd` to change into a temp cwd.
|
|
func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
|
path := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := terraform.WriteState(s, f); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
// testStateOutput tests that the state at the given path contains
|
|
// the expected state string.
|
|
func testStateOutput(t *testing.T, path string, expected string) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
newState, err := terraform.ReadState(f)
|
|
f.Close()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(newState.String())
|
|
expected = strings.TrimSpace(expected)
|
|
if actual != expected {
|
|
t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual)
|
|
}
|
|
}
|
|
|
|
func testProvider() *terraform.MockResourceProvider {
|
|
p := new(terraform.MockResourceProvider)
|
|
p.DiffReturn = &terraform.InstanceDiff{}
|
|
p.RefreshFn = func(
|
|
info *terraform.InstanceInfo,
|
|
s *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
return s, nil
|
|
}
|
|
p.ResourcesReturn = []terraform.ResourceType{
|
|
terraform.ResourceType{
|
|
Name: "test_instance",
|
|
},
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func testTempFile(t *testing.T) string {
|
|
return filepath.Join(testTempDir(t), "state.tfstate")
|
|
}
|
|
|
|
func testTempDir(t *testing.T) string {
|
|
d, err := ioutil.TempDir("", "tf")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return d
|
|
}
|
|
|
|
// testCwd is used to change the current working directory
|
|
// into a test directory that should be remoted after
|
|
func testCwd(t *testing.T) (string, string) {
|
|
tmp, err := ioutil.TempDir("", "tf")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if err := os.Chdir(tmp); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
return tmp, cwd
|
|
}
|
|
|
|
// testFixCwd is used to as a defer to testDir
|
|
func testFixCwd(t *testing.T, tmp, cwd string) {
|
|
if err := os.Chdir(cwd); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if err := os.RemoveAll(tmp); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|