github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/internal/addrs"
    17  	"github.com/hashicorp/terraform/internal/states"
    18  	"github.com/hashicorp/terraform/internal/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", "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 := t.TempDir()
   189  
   190  	markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
   191  
   192  	outState := states.BuildState(func(ss *states.SyncState) {
   193  		ss.SetOutputValue(
   194  			markerOutput,
   195  			cty.StringVal("from-output-state"),
   196  			false, // not sensitive
   197  		)
   198  	})
   199  	outFile, err := os.Create(filepath.Join(workDir, "output.tfstate"))
   200  	if err != nil {
   201  		t.Fatalf("failed to create temporary outFile %s", err)
   202  	}
   203  	defer outFile.Close()
   204  	err = statefile.Write(&statefile.File{
   205  		Lineage:          "-",
   206  		Serial:           0,
   207  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   208  		State:            outState,
   209  	}, outFile)
   210  	if err != nil {
   211  		t.Fatalf("failed to write initial outfile state to %s: %s", outFile.Name(), err)
   212  	}
   213  
   214  	inState := states.BuildState(func(ss *states.SyncState) {
   215  		ss.SetOutputValue(
   216  			markerOutput,
   217  			cty.StringVal("from-input-state"),
   218  			false, // not sensitive
   219  		)
   220  	})
   221  	inFile, err := os.Create(filepath.Join(workDir, "input.tfstate"))
   222  	if err != nil {
   223  		t.Fatalf("failed to create temporary inFile %s", err)
   224  	}
   225  	defer inFile.Close()
   226  	err = statefile.Write(&statefile.File{
   227  		Lineage:          "-",
   228  		Serial:           0,
   229  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   230  		State:            inState,
   231  	}, inFile)
   232  	if err != nil {
   233  		t.Fatalf("failed to write initial infile state to %s: %s", inFile.Name(), err)
   234  	}
   235  
   236  	backupPath := outFile.Name() + ".backup"
   237  
   238  	ls := NewFilesystemBetweenPaths(inFile.Name(), outFile.Name())
   239  	ls.SetBackupPath(backupPath)
   240  
   241  	newState := states.BuildState(func(ss *states.SyncState) {
   242  		ss.SetOutputValue(
   243  			markerOutput,
   244  			cty.StringVal("from-new-state"),
   245  			false, // not sensitive
   246  		)
   247  	})
   248  	err = ls.WriteState(newState)
   249  	if err != nil {
   250  		t.Fatalf("failed to write new state: %s", err)
   251  	}
   252  
   253  	// The backup functionality should've saved a copy of the original contents
   254  	// of the _output_ file, even though the first snapshot was read from
   255  	// the _input_ file.
   256  	t.Run("backup file", func(t *testing.T) {
   257  		bfh, err := os.Open(backupPath)
   258  		if err != nil {
   259  			t.Fatal(err)
   260  		}
   261  		bf, err := statefile.Read(bfh)
   262  		if err != nil {
   263  			t.Fatal(err)
   264  		}
   265  		os := bf.State.OutputValue(markerOutput)
   266  		if got, want := os.Value, cty.StringVal("from-output-state"); !want.RawEquals(got) {
   267  			t.Errorf("wrong marker value in backup state file\ngot:  %#v\nwant: %#v", got, want)
   268  		}
   269  	})
   270  	t.Run("output file", func(t *testing.T) {
   271  		ofh, err := os.Open(outFile.Name())
   272  		if err != nil {
   273  			t.Fatal(err)
   274  		}
   275  		of, err := statefile.Read(ofh)
   276  		if err != nil {
   277  			t.Fatal(err)
   278  		}
   279  		os := of.State.OutputValue(markerOutput)
   280  		if got, want := os.Value, cty.StringVal("from-new-state"); !want.RawEquals(got) {
   281  			t.Errorf("wrong marker value in backup state file\ngot:  %#v\nwant: %#v", got, want)
   282  		}
   283  	})
   284  }
   285  
   286  func TestFilesystem_nonExist(t *testing.T) {
   287  	defer testOverrideVersion(t, "1.2.3")()
   288  	ls := NewFilesystem("ishouldntexist")
   289  	if err := ls.RefreshState(); err != nil {
   290  		t.Fatalf("err: %s", err)
   291  	}
   292  
   293  	if state := ls.State(); state != nil {
   294  		t.Fatalf("bad: %#v", state)
   295  	}
   296  }
   297  
   298  func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) {
   299  	info := NewLockInfo()
   300  	info.Operation = "test"
   301  
   302  	ls := testFilesystem(t)
   303  
   304  	// Delete the just-created tempfile so that Lock recreates it
   305  	os.Remove(ls.path)
   306  
   307  	// Lock the state, and in doing so recreate the tempfile
   308  	lockID, err := ls.Lock(info)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	if !ls.created {
   314  		t.Fatal("should have marked state as created")
   315  	}
   316  
   317  	if err := ls.Unlock(lockID); err != nil {
   318  		t.Fatal(err)
   319  	}
   320  
   321  	_, err = os.Stat(ls.path)
   322  	if os.IsNotExist(err) {
   323  		// Success! Unlocking the state successfully deleted the tempfile
   324  		return
   325  	} else if err != nil {
   326  		t.Fatalf("unexpected error from os.Stat: %s", err)
   327  	} else {
   328  		os.Remove(ls.readPath)
   329  		t.Fatal("should have removed path, but exists")
   330  	}
   331  }
   332  
   333  func TestFilesystem_impl(t *testing.T) {
   334  	defer testOverrideVersion(t, "1.2.3")()
   335  	var _ Reader = new(Filesystem)
   336  	var _ Writer = new(Filesystem)
   337  	var _ Persister = new(Filesystem)
   338  	var _ Refresher = new(Filesystem)
   339  	var _ OutputReader = new(Filesystem)
   340  	var _ Locker = new(Filesystem)
   341  }
   342  
   343  func testFilesystem(t *testing.T) *Filesystem {
   344  	f, err := ioutil.TempFile("", "tf")
   345  	if err != nil {
   346  		t.Fatalf("failed to create temporary file %s", err)
   347  	}
   348  	t.Logf("temporary state file at %s", f.Name())
   349  
   350  	err = statefile.Write(&statefile.File{
   351  		Lineage:          "test-lineage",
   352  		Serial:           0,
   353  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   354  		State:            TestFullInitialState(),
   355  	}, f)
   356  	if err != nil {
   357  		t.Fatalf("failed to write initial state to %s: %s", f.Name(), err)
   358  	}
   359  	f.Close()
   360  
   361  	ls := NewFilesystem(f.Name())
   362  	if err := ls.RefreshState(); err != nil {
   363  		t.Fatalf("initial refresh failed: %s", err)
   364  	}
   365  
   366  	return ls
   367  }
   368  
   369  // Make sure we can refresh while the state is locked
   370  func TestFilesystem_refreshWhileLocked(t *testing.T) {
   371  	defer testOverrideVersion(t, "1.2.3")()
   372  	f, err := ioutil.TempFile("", "tf")
   373  	if err != nil {
   374  		t.Fatalf("err: %s", err)
   375  	}
   376  
   377  	err = statefile.Write(&statefile.File{
   378  		Lineage:          "test-lineage",
   379  		Serial:           0,
   380  		TerraformVersion: version.Must(version.NewVersion("1.2.3")),
   381  		State:            TestFullInitialState(),
   382  	}, f)
   383  	if err != nil {
   384  		t.Fatalf("err: %s", err)
   385  	}
   386  	f.Close()
   387  
   388  	s := NewFilesystem(f.Name())
   389  	defer os.Remove(s.path)
   390  
   391  	// lock first
   392  	info := NewLockInfo()
   393  	info.Operation = "test"
   394  	lockID, err := s.Lock(info)
   395  	if err != nil {
   396  		t.Fatal(err)
   397  	}
   398  	defer func() {
   399  		if err := s.Unlock(lockID); err != nil {
   400  			t.Fatal(err)
   401  		}
   402  	}()
   403  
   404  	if err := s.RefreshState(); err != nil {
   405  		t.Fatal(err)
   406  	}
   407  
   408  	readState := s.State()
   409  	if readState == nil {
   410  		t.Fatal("missing state")
   411  	}
   412  }
   413  
   414  func TestFilesystem_GetRootOutputValues(t *testing.T) {
   415  	fs := testFilesystem(t)
   416  
   417  	outputs, err := fs.GetRootOutputValues()
   418  	if err != nil {
   419  		t.Errorf("Expected GetRootOutputValues to not return an error, but it returned %v", err)
   420  	}
   421  
   422  	if len(outputs) != 2 {
   423  		t.Errorf("Expected %d outputs, but received %d", 2, len(outputs))
   424  	}
   425  }
   426  
   427  func testOverrideVersion(t *testing.T, v string) func() {
   428  	oldVersionStr := tfversion.Version
   429  	oldPrereleaseStr := tfversion.Prerelease
   430  	oldSemVer := tfversion.SemVer
   431  
   432  	var newPrereleaseStr string
   433  	if dash := strings.Index(v, "-"); dash != -1 {
   434  		newPrereleaseStr = v[dash+1:]
   435  		v = v[:dash]
   436  	}
   437  
   438  	newSemVer, err := version.NewVersion(v)
   439  	if err != nil {
   440  		t.Errorf("invalid override version %q: %s", v, err)
   441  	}
   442  	newVersionStr := newSemVer.String()
   443  
   444  	tfversion.Version = newVersionStr
   445  	tfversion.Prerelease = newPrereleaseStr
   446  	tfversion.SemVer = newSemVer
   447  
   448  	return func() { // reset function
   449  		tfversion.Version = oldVersionStr
   450  		tfversion.Prerelease = oldPrereleaseStr
   451  		tfversion.SemVer = oldSemVer
   452  	}
   453  }