github.com/bridgecrewio/terraform@v0.11.12-beta1/backend/local/backend_apply_test.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  	"testing"
    12  
    13  	"github.com/hashicorp/terraform/backend"
    14  	"github.com/hashicorp/terraform/config/module"
    15  	"github.com/hashicorp/terraform/state"
    16  	"github.com/hashicorp/terraform/terraform"
    17  	"github.com/mitchellh/cli"
    18  )
    19  
    20  func TestLocal_applyBasic(t *testing.T) {
    21  	b, cleanup := TestLocal(t)
    22  	defer cleanup()
    23  	p := TestLocalProvider(t, b, "test")
    24  
    25  	p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
    26  
    27  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
    28  	defer modCleanup()
    29  
    30  	op := testOperationApply()
    31  	op.Module = mod
    32  
    33  	run, err := b.Operation(context.Background(), op)
    34  	if err != nil {
    35  		t.Fatalf("bad: %s", err)
    36  	}
    37  	<-run.Done()
    38  	if run.Err != nil {
    39  		t.Fatalf("err: %s", err)
    40  	}
    41  
    42  	if p.RefreshCalled {
    43  		t.Fatal("refresh should not be called")
    44  	}
    45  
    46  	if !p.DiffCalled {
    47  		t.Fatal("diff should be called")
    48  	}
    49  
    50  	if !p.ApplyCalled {
    51  		t.Fatal("apply should be called")
    52  	}
    53  
    54  	checkState(t, b.StateOutPath, `
    55  test_instance.foo:
    56    ID = yes
    57    provider = provider.test
    58  	`)
    59  }
    60  
    61  func TestLocal_applyEmptyDir(t *testing.T) {
    62  	b, cleanup := TestLocal(t)
    63  	defer cleanup()
    64  
    65  	p := TestLocalProvider(t, b, "test")
    66  
    67  	p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
    68  
    69  	op := testOperationApply()
    70  	op.Module = nil
    71  
    72  	run, err := b.Operation(context.Background(), op)
    73  	if err != nil {
    74  		t.Fatalf("bad: %s", err)
    75  	}
    76  	<-run.Done()
    77  	if run.Err == nil {
    78  		t.Fatal("should error")
    79  	}
    80  
    81  	if p.ApplyCalled {
    82  		t.Fatal("apply should not be called")
    83  	}
    84  
    85  	if _, err := os.Stat(b.StateOutPath); err == nil {
    86  		t.Fatal("should not exist")
    87  	}
    88  }
    89  
    90  func TestLocal_applyEmptyDirDestroy(t *testing.T) {
    91  	b, cleanup := TestLocal(t)
    92  	defer cleanup()
    93  	p := TestLocalProvider(t, b, "test")
    94  
    95  	p.ApplyReturn = nil
    96  
    97  	op := testOperationApply()
    98  	op.Module = nil
    99  	op.Destroy = true
   100  
   101  	run, err := b.Operation(context.Background(), op)
   102  	if err != nil {
   103  		t.Fatalf("bad: %s", err)
   104  	}
   105  	<-run.Done()
   106  	if run.Err != nil {
   107  		t.Fatalf("err: %s", err)
   108  	}
   109  
   110  	if p.ApplyCalled {
   111  		t.Fatal("apply should not be called")
   112  	}
   113  
   114  	checkState(t, b.StateOutPath, `<no state>`)
   115  }
   116  
   117  func TestLocal_applyWithVariables(t *testing.T) {
   118  	b, cleanup := TestLocal(t)
   119  	defer cleanup()
   120  	p := TestLocalProvider(t, b, "test")
   121  
   122  	p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
   123  
   124  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-vars")
   125  	defer modCleanup()
   126  
   127  	op := testOperationApply()
   128  	op.Module = mod
   129  	op.Variables = map[string]interface{}{"cli": "var"}
   130  
   131  	// Set some variables that would have been read from
   132  	// any terraform.tfvars or *.auto.tfvars files.
   133  	b.ContextOpts.Variables = map[string]interface{}{"foo": "bar"}
   134  
   135  	run, err := b.Operation(context.Background(), op)
   136  	if err != nil {
   137  		t.Fatalf("bad: %s", err)
   138  	}
   139  	<-run.Done()
   140  	if run.Err != nil {
   141  		t.Fatalf("err: %s", run.Err)
   142  	}
   143  
   144  	checkState(t, b.StateOutPath, `
   145  test_instance.foo:
   146    ID = yes
   147    provider = provider.test
   148  	`)
   149  }
   150  
   151  func TestLocal_applyError(t *testing.T) {
   152  	b, cleanup := TestLocal(t)
   153  	defer cleanup()
   154  	p := TestLocalProvider(t, b, "test")
   155  
   156  	var lock sync.Mutex
   157  	errored := false
   158  	p.ApplyFn = func(
   159  		info *terraform.InstanceInfo,
   160  		s *terraform.InstanceState,
   161  		d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   162  		lock.Lock()
   163  		defer lock.Unlock()
   164  
   165  		if !errored && info.Id == "test_instance.bar" {
   166  			errored = true
   167  			return nil, fmt.Errorf("error")
   168  		}
   169  
   170  		return &terraform.InstanceState{ID: "foo"}, nil
   171  	}
   172  	p.DiffFn = func(
   173  		*terraform.InstanceInfo,
   174  		*terraform.InstanceState,
   175  		*terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   176  		return &terraform.InstanceDiff{
   177  			Attributes: map[string]*terraform.ResourceAttrDiff{
   178  				"ami": &terraform.ResourceAttrDiff{
   179  					New: "bar",
   180  				},
   181  			},
   182  		}, nil
   183  	}
   184  
   185  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-error")
   186  	defer modCleanup()
   187  
   188  	op := testOperationApply()
   189  	op.Module = mod
   190  
   191  	run, err := b.Operation(context.Background(), op)
   192  	if err != nil {
   193  		t.Fatalf("bad: %s", err)
   194  	}
   195  	<-run.Done()
   196  	if run.Err == nil {
   197  		t.Fatal("should error")
   198  	}
   199  
   200  	checkState(t, b.StateOutPath, `
   201  test_instance.foo:
   202    ID = foo
   203    provider = provider.test
   204  	`)
   205  }
   206  
   207  func TestLocal_applyBackendFail(t *testing.T) {
   208  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   209  	defer modCleanup()
   210  
   211  	b, cleanup := TestLocal(t)
   212  	defer cleanup()
   213  
   214  	wd, err := os.Getwd()
   215  	if err != nil {
   216  		t.Fatalf("failed to get current working directory")
   217  	}
   218  	err = os.Chdir(filepath.Dir(b.StatePath))
   219  	if err != nil {
   220  		t.Fatalf("failed to set temporary working directory")
   221  	}
   222  	defer os.Chdir(wd)
   223  
   224  	b.Backend = &backendWithFailingState{}
   225  	b.CLI = new(cli.MockUi)
   226  	p := TestLocalProvider(t, b, "test")
   227  
   228  	p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
   229  
   230  	op := testOperationApply()
   231  	op.Module = mod
   232  
   233  	run, err := b.Operation(context.Background(), op)
   234  	if err != nil {
   235  		t.Fatalf("bad: %s", err)
   236  	}
   237  	<-run.Done()
   238  	if run.Err == nil {
   239  		t.Fatalf("apply succeeded; want error")
   240  	}
   241  
   242  	errStr := run.Err.Error()
   243  	if !strings.Contains(errStr, "terraform state push errored.tfstate") {
   244  		t.Fatalf("wrong error message:\n%s", errStr)
   245  	}
   246  
   247  	msgStr := b.CLI.(*cli.MockUi).ErrorWriter.String()
   248  	if !strings.Contains(msgStr, "Failed to save state: fake failure") {
   249  		t.Fatalf("missing original error message in output:\n%s", msgStr)
   250  	}
   251  
   252  	// The fallback behavior should've created a file errored.tfstate in the
   253  	// current working directory.
   254  	checkState(t, "errored.tfstate", `
   255  test_instance.foo:
   256    ID = yes
   257    provider = provider.test
   258  	`)
   259  }
   260  
   261  type backendWithFailingState struct {
   262  	Local
   263  }
   264  
   265  func (b *backendWithFailingState) State(name string) (state.State, error) {
   266  	return &failingState{
   267  		&state.LocalState{
   268  			Path: "failing-state.tfstate",
   269  		},
   270  	}, nil
   271  }
   272  
   273  type failingState struct {
   274  	*state.LocalState
   275  }
   276  
   277  func (s failingState) WriteState(state *terraform.State) error {
   278  	return errors.New("fake failure")
   279  }
   280  
   281  func testOperationApply() *backend.Operation {
   282  	return &backend.Operation{
   283  		Type: backend.OperationTypeApply,
   284  	}
   285  }
   286  
   287  // testApplyState is just a common state that we use for testing refresh.
   288  func testApplyState() *terraform.State {
   289  	return &terraform.State{
   290  		Version: 2,
   291  		Modules: []*terraform.ModuleState{
   292  			&terraform.ModuleState{
   293  				Path: []string{"root"},
   294  				Resources: map[string]*terraform.ResourceState{
   295  					"test_instance.foo": &terraform.ResourceState{
   296  						Type: "test_instance",
   297  						Primary: &terraform.InstanceState{
   298  							ID: "bar",
   299  						},
   300  					},
   301  				},
   302  			},
   303  		},
   304  	}
   305  }