github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/states/statemgr/filesystem_test.go (about)

     1  package statemgr
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  
    12  	"github.com/go-test/deep"
    13  	version "github.com/hashicorp/go-version"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/hashicorp/terraform/addrs"
    17  	"github.com/hashicorp/terraform/states"
    18  	"github.com/hashicorp/terraform/states/statefile"
    19  	tfversion "github.com/hashicorp/terraform/version"
    20  )
    21  
    22  func TestFilesystem(t *testing.T) {
    23  	defer testOverrideVersion(t, "1.2.3")()
    24  	ls := testFilesystem(t)
    25  	defer os.Remove(ls.readPath)
    26  	TestFull(t, ls)
    27  }
    28  
    29  func TestFilesystemRace(t *testing.T) {
    30  	defer testOverrideVersion(t, "1.2.3")()
    31  	ls := testFilesystem(t)
    32  	defer os.Remove(ls.readPath)
    33  
    34  	current := TestFullInitialState()
    35  
    36  	var wg sync.WaitGroup
    37  	for i := 0; i < 100; i++ {
    38  		wg.Add(1)
    39  		go func() {
    40  			defer wg.Done()
    41  			ls.WriteState(current)
    42  		}()
    43  	}
    44  	wg.Wait()
    45  }
    46  
    47  func TestFilesystemLocks(t *testing.T) {
    48  	defer testOverrideVersion(t, "1.2.3")()
    49  	s := testFilesystem(t)
    50  	defer os.Remove(s.readPath)
    51  
    52  	// lock first
    53  	info := NewLockInfo()
    54  	info.Operation = "test"
    55  	lockID, err := s.Lock(info)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	out, err := exec.Command("go", "run", "-mod=vendor", "testdata/lockstate.go", s.path).CombinedOutput()
    61  	if err != nil {
    62  		t.Fatal("unexpected lock failure", err, string(out))
    63  	}
    64  
    65  	if !strings.Contains(string(out), "lock failed") {
    66  		t.Fatal("expected 'locked failed', got", string(out))
    67  	}
    68  
    69  	// check our lock info
    70  	lockInfo, err := s.lockInfo()
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  
    75  	if lockInfo.Operation != "test" {
    76  		t.Fatalf("invalid lock info %#v\n", lockInfo)
    77  	}
    78  
    79  	// a noop, since we unlock on exit
    80  	if err := s.Unlock(lockID); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  
    84  	// local locks can re-lock
    85  	lockID, err = s.Lock(info)
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  
    90  	if err := s.Unlock(lockID); err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	// we should not be able to unlock the same lock twice
    95  	if err := s.Unlock(lockID); err == nil {
    96  		t.Fatal("unlocking an unlocked state should fail")
    97  	}
    98  
    99  	// make sure lock info is gone
   100  	lockInfoPath := s.lockInfoPath()
   101  	if _, err := os.Stat(lockInfoPath); !os.IsNotExist(err) {
   102  		t.Fatal("lock info not removed")
   103  	}
   104  }
   105  
   106  // Verify that we can write to the state file, as Windows' mandatory locking
   107  // will prevent writing to a handle different than the one that hold the lock.
   108  func TestFilesystem_writeWhileLocked(t *testing.T) {
   109  	defer testOverrideVersion(t, "1.2.3")()
   110  	s := testFilesystem(t)
   111  	defer os.Remove(s.readPath)
   112  
   113  	// lock first
   114  	info := NewLockInfo()
   115  	info.Operation = "test"
   116  	lockID, err := s.Lock(info)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	defer func() {
   121  		if err := s.Unlock(lockID); err != nil {
   122  			t.Fatal(err)
   123  		}
   124  	}()
   125  
   126  	if err := s.WriteState(TestFullInitialState()); err != nil {
   127  		t.Fatal(err)
   128  	}
   129  }
   130  
   131  func TestFilesystem_pathOut(t *testing.T) {
   132  	defer testOverrideVersion(t, "1.2.3")()
   133  	f, err := ioutil.TempFile("", "tf")
   134  	if err != nil {
   135  		t.Fatalf("err: %s", err)
   136  	}
   137  	f.Close()
   138  	defer os.Remove(f.Name())
   139  
   140  	ls := testFilesystem(t)
   141  	ls.path = f.Name()
   142  	defer os.Remove(ls.path)
   143  
   144  	TestFull(t, ls)
   145  }
   146  
   147  func TestFilesystem_backup(t *testing.T) {
   148  	defer testOverrideVersion(t, "1.2.3")()
   149  	f, err := ioutil.TempFile("", "tf")
   150  	if err != nil {
   151  		t.Fatalf("err: %s", err)
   152  	}
   153  	f.Close()
   154  	defer os.Remove(f.Name())
   155  
   156  	ls := testFilesystem(t)
   157  	backupPath := f.Name()
   158  	ls.SetBackupPath(backupPath)
   159  
   160  	TestFull(t, ls)
   161  
   162  	// The backup functionality should've saved a copy of the original state
   163  	// prior to all of the modifications that TestFull does.
   164  	bfh, err := os.Open(backupPath)
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  	bf, err := statefile.Read(bfh)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	origState := TestFullInitialState()
   173  	if !bf.State.Equal(origState) {
   174  		for _, problem := range deep.Equal(origState, bf.State) {
   175  			t.Error(problem)
   176  		}
   177  	}
   178  }
   179  
   180  // This test verifies a particularly tricky behavior where the input file
   181  // is overridden and backups are enabled at the same time. This combination
   182  // requires special care because we must ensure that when we create a backup
   183  // it is of the original contents of the output file (which we're overwriting),
   184  // not the contents of the input file (which is left unchanged).
   185  func TestFilesystem_backupAndReadPath(t *testing.T) {
   186  	defer testOverrideVersion(t, "1.2.3")()
   187  
   188  	workDir, err := ioutil.TempDir("", "tf")
   189  	if err != nil {
   190  		t.Fatalf("failed to create temporary directory: %s", err)
   191  	}
   192  	defer os.RemoveAll(workDir)
   193  
   194  	markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
   195  
   196  	outState := states.BuildState(func(ss *states.SyncState) {
   197  		ss.SetOutputValue(
   198  			markerOutput,
   199  			cty.StringVal("from-output-state"),
   200  			false, // not sensitive
   201  		)
   202  	})
   203  	outFile, err := os.Create(filepath.Join(workDir, "output.tfstate"))
   204  	if err != nil {
   205  		t.Fatalf("failed to create temporary outFile %s", err)
   206  	}
   207  	defer outFile.Close()
   208  	err = statefile.Write(&statefile.File{
   209  		Lineage:          "-",
   210  		Serial:           0,
   211  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   212  		State:            outState,
   213  	}, outFile)
   214  	if err != nil {
   215  		t.Fatalf("failed to write initial outfile state to %s: %s", outFile.Name(), err)
   216  	}
   217  
   218  	inState := states.BuildState(func(ss *states.SyncState) {
   219  		ss.SetOutputValue(
   220  			markerOutput,
   221  			cty.StringVal("from-input-state"),
   222  			false, // not sensitive
   223  		)
   224  	})
   225  	inFile, err := os.Create(filepath.Join(workDir, "input.tfstate"))
   226  	if err != nil {
   227  		t.Fatalf("failed to create temporary inFile %s", err)
   228  	}
   229  	defer inFile.Close()
   230  	err = statefile.Write(&statefile.File{
   231  		Lineage:          "-",
   232  		Serial:           0,
   233  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   234  		State:            inState,
   235  	}, inFile)
   236  	if err != nil {
   237  		t.Fatalf("failed to write initial infile state to %s: %s", inFile.Name(), err)
   238  	}
   239  
   240  	backupPath := outFile.Name() + ".backup"
   241  
   242  	ls := NewFilesystemBetweenPaths(inFile.Name(), outFile.Name())
   243  	ls.SetBackupPath(backupPath)
   244  
   245  	newState := states.BuildState(func(ss *states.SyncState) {
   246  		ss.SetOutputValue(
   247  			markerOutput,
   248  			cty.StringVal("from-new-state"),
   249  			false, // not sensitive
   250  		)
   251  	})
   252  	err = ls.WriteState(newState)
   253  	if err != nil {
   254  		t.Fatalf("failed to write new state: %s", err)
   255  	}
   256  
   257  	// The backup functionality should've saved a copy of the original contents
   258  	// of the _output_ file, even though the first snapshot was read from
   259  	// the _input_ file.
   260  	t.Run("backup file", func(t *testing.T) {
   261  		bfh, err := os.Open(backupPath)
   262  		if err != nil {
   263  			t.Fatal(err)
   264  		}
   265  		bf, err := statefile.Read(bfh)
   266  		if err != nil {
   267  			t.Fatal(err)
   268  		}
   269  		os := bf.State.OutputValue(markerOutput)
   270  		if got, want := os.Value, cty.StringVal("from-output-state"); !want.RawEquals(got) {
   271  			t.Errorf("wrong marker value in backup state file\ngot:  %#v\nwant: %#v", got, want)
   272  		}
   273  	})
   274  	t.Run("output file", func(t *testing.T) {
   275  		ofh, err := os.Open(outFile.Name())
   276  		if err != nil {
   277  			t.Fatal(err)
   278  		}
   279  		of, err := statefile.Read(ofh)
   280  		if err != nil {
   281  			t.Fatal(err)
   282  		}
   283  		os := of.State.OutputValue(markerOutput)
   284  		if got, want := os.Value, cty.StringVal("from-new-state"); !want.RawEquals(got) {
   285  			t.Errorf("wrong marker value in backup state file\ngot:  %#v\nwant: %#v", got, want)
   286  		}
   287  	})
   288  }
   289  
   290  func TestFilesystem_nonExist(t *testing.T) {
   291  	defer testOverrideVersion(t, "1.2.3")()
   292  	ls := NewFilesystem("ishouldntexist")
   293  	if err := ls.RefreshState(); err != nil {
   294  		t.Fatalf("err: %s", err)
   295  	}
   296  
   297  	if state := ls.State(); state != nil {
   298  		t.Fatalf("bad: %#v", state)
   299  	}
   300  }
   301  
   302  func TestFilesystem_impl(t *testing.T) {
   303  	defer testOverrideVersion(t, "1.2.3")()
   304  	var _ Reader = new(Filesystem)
   305  	var _ Writer = new(Filesystem)
   306  	var _ Persister = new(Filesystem)
   307  	var _ Refresher = new(Filesystem)
   308  	var _ Locker = new(Filesystem)
   309  }
   310  
   311  func testFilesystem(t *testing.T) *Filesystem {
   312  	f, err := ioutil.TempFile("", "tf")
   313  	if err != nil {
   314  		t.Fatalf("failed to create temporary file %s", err)
   315  	}
   316  	t.Logf("temporary state file at %s", f.Name())
   317  
   318  	err = statefile.Write(&statefile.File{
   319  		Lineage:          "test-lineage",
   320  		Serial:           0,
   321  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   322  		State:            TestFullInitialState(),
   323  	}, f)
   324  	if err != nil {
   325  		t.Fatalf("failed to write initial state to %s: %s", f.Name(), err)
   326  	}
   327  	f.Close()
   328  
   329  	ls := NewFilesystem(f.Name())
   330  	if err := ls.RefreshState(); err != nil {
   331  		t.Fatalf("initial refresh failed: %s", err)
   332  	}
   333  
   334  	return ls
   335  }
   336  
   337  // Make sure we can refresh while the state is locked
   338  func TestFilesystem_refreshWhileLocked(t *testing.T) {
   339  	defer testOverrideVersion(t, "1.2.3")()
   340  	f, err := ioutil.TempFile("", "tf")
   341  	if err != nil {
   342  		t.Fatalf("err: %s", err)
   343  	}
   344  
   345  	err = statefile.Write(&statefile.File{
   346  		Lineage:          "test-lineage",
   347  		Serial:           0,
   348  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   349  		State:            TestFullInitialState(),
   350  	}, f)
   351  	if err != nil {
   352  		t.Fatalf("err: %s", err)
   353  	}
   354  	f.Close()
   355  
   356  	s := NewFilesystem(f.Name())
   357  	defer os.Remove(s.path)
   358  
   359  	// lock first
   360  	info := NewLockInfo()
   361  	info.Operation = "test"
   362  	lockID, err := s.Lock(info)
   363  	if err != nil {
   364  		t.Fatal(err)
   365  	}
   366  	defer func() {
   367  		if err := s.Unlock(lockID); err != nil {
   368  			t.Fatal(err)
   369  		}
   370  	}()
   371  
   372  	if err := s.RefreshState(); err != nil {
   373  		t.Fatal(err)
   374  	}
   375  
   376  	readState := s.State()
   377  	if readState == nil {
   378  		t.Fatal("missing state")
   379  	}
   380  }
   381  
   382  func testOverrideVersion(t *testing.T, v string) func() {
   383  	oldVersionStr := tfversion.Version
   384  	oldPrereleaseStr := tfversion.Prerelease
   385  	oldSemVer := tfversion.SemVer
   386  
   387  	var newPrereleaseStr string
   388  	if dash := strings.Index(v, "-"); dash != -1 {
   389  		newPrereleaseStr = v[dash+1:]
   390  		v = v[:dash]
   391  	}
   392  
   393  	newSemVer, err := version.NewVersion(v)
   394  	if err != nil {
   395  		t.Errorf("invalid override version %q: %s", v, err)
   396  	}
   397  	newVersionStr := newSemVer.String()
   398  
   399  	tfversion.Version = newVersionStr
   400  	tfversion.Prerelease = newPrereleaseStr
   401  	tfversion.SemVer = newSemVer
   402  
   403  	return func() { // reset function
   404  		tfversion.Version = oldVersionStr
   405  		tfversion.Prerelease = oldPrereleaseStr
   406  		tfversion.SemVer = oldSemVer
   407  	}
   408  }