github.com/hugorut/terraform@v1.1.3/src/backend/local/backend_local_test.go (about)

     1  package local
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/hugorut/terraform/src/backend"
    10  	"github.com/hugorut/terraform/src/command/arguments"
    11  	"github.com/hugorut/terraform/src/command/clistate"
    12  	"github.com/hugorut/terraform/src/command/views"
    13  	"github.com/hugorut/terraform/src/configs/configload"
    14  	"github.com/hugorut/terraform/src/configs/configschema"
    15  	"github.com/hugorut/terraform/src/initwd"
    16  	"github.com/hugorut/terraform/src/plans"
    17  	"github.com/hugorut/terraform/src/plans/planfile"
    18  	"github.com/hugorut/terraform/src/states"
    19  	"github.com/hugorut/terraform/src/states/statefile"
    20  	"github.com/hugorut/terraform/src/states/statemgr"
    21  	"github.com/hugorut/terraform/src/terminal"
    22  	"github.com/hugorut/terraform/src/tfdiags"
    23  	"github.com/zclconf/go-cty/cty"
    24  )
    25  
    26  func TestLocalRun(t *testing.T) {
    27  	configDir := "./testdata/empty"
    28  	b := TestLocal(t)
    29  
    30  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    31  	defer configCleanup()
    32  
    33  	streams, _ := terminal.StreamsForTesting(t)
    34  	view := views.NewView(streams)
    35  	stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
    36  
    37  	op := &backend.Operation{
    38  		ConfigDir:    configDir,
    39  		ConfigLoader: configLoader,
    40  		Workspace:    backend.DefaultStateName,
    41  		StateLocker:  stateLocker,
    42  	}
    43  
    44  	_, _, diags := b.LocalRun(op)
    45  	if diags.HasErrors() {
    46  		t.Fatalf("unexpected error: %s", diags.Err().Error())
    47  	}
    48  
    49  	// LocalRun() retains a lock on success
    50  	assertBackendStateLocked(t, b)
    51  }
    52  
    53  func TestLocalRun_error(t *testing.T) {
    54  	configDir := "./testdata/invalid"
    55  	b := TestLocal(t)
    56  
    57  	// This backend will return an error when asked to RefreshState, which
    58  	// should then cause LocalRun to return with the state unlocked.
    59  	b.Backend = backendWithStateStorageThatFailsRefresh{}
    60  
    61  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    62  	defer configCleanup()
    63  
    64  	streams, _ := terminal.StreamsForTesting(t)
    65  	view := views.NewView(streams)
    66  	stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
    67  
    68  	op := &backend.Operation{
    69  		ConfigDir:    configDir,
    70  		ConfigLoader: configLoader,
    71  		Workspace:    backend.DefaultStateName,
    72  		StateLocker:  stateLocker,
    73  	}
    74  
    75  	_, _, diags := b.LocalRun(op)
    76  	if !diags.HasErrors() {
    77  		t.Fatal("unexpected success")
    78  	}
    79  
    80  	// LocalRun() unlocks the state on failure
    81  	assertBackendStateUnlocked(t, b)
    82  }
    83  
    84  func TestLocalRun_stalePlan(t *testing.T) {
    85  	configDir := "./testdata/apply"
    86  	b := TestLocal(t)
    87  
    88  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    89  	defer configCleanup()
    90  
    91  	// Write an empty state file with serial 3
    92  	sf, err := os.Create(b.StatePath)
    93  	if err != nil {
    94  		t.Fatalf("unexpected error creating state file %s: %s", b.StatePath, err)
    95  	}
    96  	if err := statefile.Write(statefile.New(states.NewState(), "boop", 3), sf); err != nil {
    97  		t.Fatalf("unexpected error writing state file: %s", err)
    98  	}
    99  
   100  	// Refresh the state
   101  	sm, err := b.StateMgr("")
   102  	if err != nil {
   103  		t.Fatalf("unexpected error: %s", err)
   104  	}
   105  	if err := sm.RefreshState(); err != nil {
   106  		t.Fatalf("unexpected error refreshing state: %s", err)
   107  	}
   108  
   109  	// Create a minimal plan which also has state file serial 2, so is stale
   110  	backendConfig := cty.ObjectVal(map[string]cty.Value{
   111  		"path":          cty.NullVal(cty.String),
   112  		"workspace_dir": cty.NullVal(cty.String),
   113  	})
   114  	backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type())
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  	plan := &plans.Plan{
   119  		UIMode:  plans.NormalMode,
   120  		Changes: plans.NewChanges(),
   121  		Backend: plans.Backend{
   122  			Type:   "local",
   123  			Config: backendConfigRaw,
   124  		},
   125  		PrevRunState: states.NewState(),
   126  		PriorState:   states.NewState(),
   127  	}
   128  	prevStateFile := statefile.New(plan.PrevRunState, "boop", 1)
   129  	stateFile := statefile.New(plan.PriorState, "boop", 2)
   130  
   131  	// Roundtrip through serialization as expected by the operation
   132  	outDir := t.TempDir()
   133  	defer os.RemoveAll(outDir)
   134  	planPath := filepath.Join(outDir, "plan.tfplan")
   135  	planfileArgs := planfile.CreateArgs{
   136  		ConfigSnapshot:       configload.NewEmptySnapshot(),
   137  		PreviousRunStateFile: prevStateFile,
   138  		StateFile:            stateFile,
   139  		Plan:                 plan,
   140  	}
   141  	if err := planfile.Create(planPath, planfileArgs); err != nil {
   142  		t.Fatalf("unexpected error writing planfile: %s", err)
   143  	}
   144  	planFile, err := planfile.Open(planPath)
   145  	if err != nil {
   146  		t.Fatalf("unexpected error reading planfile: %s", err)
   147  	}
   148  
   149  	streams, _ := terminal.StreamsForTesting(t)
   150  	view := views.NewView(streams)
   151  	stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
   152  
   153  	op := &backend.Operation{
   154  		ConfigDir:    configDir,
   155  		ConfigLoader: configLoader,
   156  		PlanFile:     planFile,
   157  		Workspace:    backend.DefaultStateName,
   158  		StateLocker:  stateLocker,
   159  	}
   160  
   161  	_, _, diags := b.LocalRun(op)
   162  	if !diags.HasErrors() {
   163  		t.Fatal("unexpected success")
   164  	}
   165  
   166  	// LocalRun() unlocks the state on failure
   167  	assertBackendStateUnlocked(t, b)
   168  }
   169  
   170  type backendWithStateStorageThatFailsRefresh struct {
   171  }
   172  
   173  var _ backend.Backend = backendWithStateStorageThatFailsRefresh{}
   174  
   175  func (b backendWithStateStorageThatFailsRefresh) StateMgr(workspace string) (statemgr.Full, error) {
   176  	return &stateStorageThatFailsRefresh{}, nil
   177  }
   178  
   179  func (b backendWithStateStorageThatFailsRefresh) ConfigSchema() *configschema.Block {
   180  	return &configschema.Block{}
   181  }
   182  
   183  func (b backendWithStateStorageThatFailsRefresh) PrepareConfig(in cty.Value) (cty.Value, tfdiags.Diagnostics) {
   184  	return in, nil
   185  }
   186  
   187  func (b backendWithStateStorageThatFailsRefresh) Configure(cty.Value) tfdiags.Diagnostics {
   188  	return nil
   189  }
   190  
   191  func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string) error {
   192  	return fmt.Errorf("unimplemented")
   193  }
   194  
   195  func (b backendWithStateStorageThatFailsRefresh) Workspaces() ([]string, error) {
   196  	return []string{"default"}, nil
   197  }
   198  
   199  type stateStorageThatFailsRefresh struct {
   200  	locked bool
   201  }
   202  
   203  func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, error) {
   204  	if s.locked {
   205  		return "", fmt.Errorf("already locked")
   206  	}
   207  	s.locked = true
   208  	return "locked", nil
   209  }
   210  
   211  func (s *stateStorageThatFailsRefresh) Unlock(id string) error {
   212  	if !s.locked {
   213  		return fmt.Errorf("not locked")
   214  	}
   215  	s.locked = false
   216  	return nil
   217  }
   218  
   219  func (s *stateStorageThatFailsRefresh) State() *states.State {
   220  	return nil
   221  }
   222  
   223  func (s *stateStorageThatFailsRefresh) WriteState(*states.State) error {
   224  	return fmt.Errorf("unimplemented")
   225  }
   226  
   227  func (s *stateStorageThatFailsRefresh) RefreshState() error {
   228  	return fmt.Errorf("intentionally failing for testing purposes")
   229  }
   230  
   231  func (s *stateStorageThatFailsRefresh) PersistState() error {
   232  	return fmt.Errorf("unimplemented")
   233  }