github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/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 }