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