kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/backend/local/backend_local_test.go (about)

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