github.com/pdecat/terraform@v0.11.9-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_applyError(t *testing.T) { 118 b, cleanup := TestLocal(t) 119 defer cleanup() 120 p := TestLocalProvider(t, b, "test") 121 122 var lock sync.Mutex 123 errored := false 124 p.ApplyFn = func( 125 info *terraform.InstanceInfo, 126 s *terraform.InstanceState, 127 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 128 lock.Lock() 129 defer lock.Unlock() 130 131 if !errored && info.Id == "test_instance.bar" { 132 errored = true 133 return nil, fmt.Errorf("error") 134 } 135 136 return &terraform.InstanceState{ID: "foo"}, nil 137 } 138 p.DiffFn = func( 139 *terraform.InstanceInfo, 140 *terraform.InstanceState, 141 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 142 return &terraform.InstanceDiff{ 143 Attributes: map[string]*terraform.ResourceAttrDiff{ 144 "ami": &terraform.ResourceAttrDiff{ 145 New: "bar", 146 }, 147 }, 148 }, nil 149 } 150 151 mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-error") 152 defer modCleanup() 153 154 op := testOperationApply() 155 op.Module = mod 156 157 run, err := b.Operation(context.Background(), op) 158 if err != nil { 159 t.Fatalf("bad: %s", err) 160 } 161 <-run.Done() 162 if run.Err == nil { 163 t.Fatal("should error") 164 } 165 166 checkState(t, b.StateOutPath, ` 167 test_instance.foo: 168 ID = foo 169 provider = provider.test 170 `) 171 } 172 173 func TestLocal_applyBackendFail(t *testing.T) { 174 mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") 175 defer modCleanup() 176 177 b, cleanup := TestLocal(t) 178 defer cleanup() 179 180 wd, err := os.Getwd() 181 if err != nil { 182 t.Fatalf("failed to get current working directory") 183 } 184 err = os.Chdir(filepath.Dir(b.StatePath)) 185 if err != nil { 186 t.Fatalf("failed to set temporary working directory") 187 } 188 defer os.Chdir(wd) 189 190 b.Backend = &backendWithFailingState{} 191 b.CLI = new(cli.MockUi) 192 p := TestLocalProvider(t, b, "test") 193 194 p.ApplyReturn = &terraform.InstanceState{ID: "yes"} 195 196 op := testOperationApply() 197 op.Module = mod 198 199 run, err := b.Operation(context.Background(), op) 200 if err != nil { 201 t.Fatalf("bad: %s", err) 202 } 203 <-run.Done() 204 if run.Err == nil { 205 t.Fatalf("apply succeeded; want error") 206 } 207 208 errStr := run.Err.Error() 209 if !strings.Contains(errStr, "terraform state push errored.tfstate") { 210 t.Fatalf("wrong error message:\n%s", errStr) 211 } 212 213 msgStr := b.CLI.(*cli.MockUi).ErrorWriter.String() 214 if !strings.Contains(msgStr, "Failed to save state: fake failure") { 215 t.Fatalf("missing original error message in output:\n%s", msgStr) 216 } 217 218 // The fallback behavior should've created a file errored.tfstate in the 219 // current working directory. 220 checkState(t, "errored.tfstate", ` 221 test_instance.foo: 222 ID = yes 223 provider = provider.test 224 `) 225 } 226 227 type backendWithFailingState struct { 228 Local 229 } 230 231 func (b *backendWithFailingState) State(name string) (state.State, error) { 232 return &failingState{ 233 &state.LocalState{ 234 Path: "failing-state.tfstate", 235 }, 236 }, nil 237 } 238 239 type failingState struct { 240 *state.LocalState 241 } 242 243 func (s failingState) WriteState(state *terraform.State) error { 244 return errors.New("fake failure") 245 } 246 247 func testOperationApply() *backend.Operation { 248 return &backend.Operation{ 249 Type: backend.OperationTypeApply, 250 } 251 } 252 253 // testApplyState is just a common state that we use for testing refresh. 254 func testApplyState() *terraform.State { 255 return &terraform.State{ 256 Version: 2, 257 Modules: []*terraform.ModuleState{ 258 &terraform.ModuleState{ 259 Path: []string{"root"}, 260 Resources: map[string]*terraform.ResourceState{ 261 "test_instance.foo": &terraform.ResourceState{ 262 Type: "test_instance", 263 Primary: &terraform.InstanceState{ 264 ID: "bar", 265 }, 266 }, 267 }, 268 }, 269 }, 270 } 271 }