github.com/truecar-ops/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 }