github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/remote/remote_test.go (about) 1 package remote 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/base64" 7 "encoding/json" 8 "io/ioutil" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "path/filepath" 13 "testing" 14 15 "github.com/hashicorp/terraform/terraform" 16 ) 17 18 func TestEnsureDirectory(t *testing.T) { 19 err := EnsureDirectory() 20 if err != nil { 21 t.Fatalf("Err: %v", err) 22 } 23 24 cwd, _ := os.Getwd() 25 path := filepath.Join(cwd, LocalDirectory) 26 27 _, err = os.Stat(path) 28 if err != nil { 29 t.Fatalf("err: %v", err) 30 } 31 } 32 33 func TestHiddenStatePath(t *testing.T) { 34 path, err := HiddenStatePath() 35 if err != nil { 36 t.Fatalf("err: %v", err) 37 } 38 39 cwd, _ := os.Getwd() 40 expect := filepath.Join(cwd, LocalDirectory, HiddenStateFile) 41 42 if path != expect { 43 t.Fatalf("bad: %v", path) 44 } 45 } 46 47 func TestValidConfig(t *testing.T) { 48 conf := &terraform.RemoteState{ 49 Type: "", 50 Config: map[string]string{}, 51 } 52 if err := ValidConfig(conf); err == nil { 53 t.Fatalf("blank should be not be valid: %v", err) 54 } 55 conf.Config["name"] = "hashicorp/test-remote-state" 56 conf.Config["access_token"] = "abcd" 57 if err := ValidConfig(conf); err != nil { 58 t.Fatalf("should be valid") 59 } 60 if conf.Type != "atlas" { 61 t.Fatalf("should default to atlas") 62 } 63 } 64 65 func TestRefreshState_Init(t *testing.T) { 66 defer testFixCwd(testDir(t)) 67 remote, srv := testRemote(t, nil) 68 defer srv.Close() 69 70 sc, err := RefreshState(remote) 71 if err != nil { 72 t.Fatalf("err: %v", err) 73 } 74 75 if sc != StateChangeInit { 76 t.Fatalf("bad: %s", sc) 77 } 78 79 local := testReadLocal(t) 80 if !local.Remote.Equals(remote) { 81 t.Fatalf("Bad: %#v", local) 82 } 83 if local.Serial != 1 { 84 t.Fatalf("Bad: %#v", local) 85 } 86 } 87 88 func TestRefreshState_NewVersion(t *testing.T) { 89 defer testFixCwd(testDir(t)) 90 91 rs := terraform.NewState() 92 rs.Serial = 100 93 rs.Version = terraform.StateVersion + 1 94 remote, srv := testRemote(t, rs) 95 defer srv.Close() 96 97 local := terraform.NewState() 98 local.Serial = 99 99 testWriteLocal(t, local) 100 101 _, err := RefreshState(remote) 102 if err == nil { 103 t.Fatalf("New version should fail!") 104 } 105 } 106 107 func TestRefreshState_Noop(t *testing.T) { 108 defer testFixCwd(testDir(t)) 109 110 rs := terraform.NewState() 111 rs.Serial = 100 112 remote, srv := testRemote(t, rs) 113 defer srv.Close() 114 115 local := terraform.NewState() 116 local.Serial = 100 117 testWriteLocal(t, local) 118 119 sc, err := RefreshState(remote) 120 if err != nil { 121 t.Fatalf("err: %v", err) 122 } 123 124 if sc != StateChangeNoop { 125 t.Fatalf("bad: %s", sc) 126 } 127 } 128 129 func TestRefreshState_UpdateLocal(t *testing.T) { 130 defer testFixCwd(testDir(t)) 131 132 rs := terraform.NewState() 133 rs.Serial = 100 134 remote, srv := testRemote(t, rs) 135 defer srv.Close() 136 137 local := terraform.NewState() 138 local.Serial = 99 139 testWriteLocal(t, local) 140 141 sc, err := RefreshState(remote) 142 if err != nil { 143 t.Fatalf("err: %v", err) 144 } 145 146 if sc != StateChangeUpdateLocal { 147 t.Fatalf("bad: %s", sc) 148 } 149 150 // Should update 151 local2 := testReadLocal(t) 152 if local2.Serial != 100 { 153 t.Fatalf("Bad: %#v", local2) 154 } 155 } 156 157 func TestRefreshState_LocalNewer(t *testing.T) { 158 defer testFixCwd(testDir(t)) 159 160 rs := terraform.NewState() 161 rs.Serial = 99 162 remote, srv := testRemote(t, rs) 163 defer srv.Close() 164 165 local := terraform.NewState() 166 local.Serial = 100 167 testWriteLocal(t, local) 168 169 sc, err := RefreshState(remote) 170 if err != nil { 171 t.Fatalf("err: %v", err) 172 } 173 174 if sc != StateChangeLocalNewer { 175 t.Fatalf("bad: %s", sc) 176 } 177 } 178 179 func TestRefreshState_Conflict(t *testing.T) { 180 defer testFixCwd(testDir(t)) 181 182 rs := terraform.NewState() 183 rs.Serial = 50 184 rs.RootModule().Outputs["foo"] = "bar" 185 remote, srv := testRemote(t, rs) 186 defer srv.Close() 187 188 local := terraform.NewState() 189 local.Serial = 50 190 local.RootModule().Outputs["foo"] = "baz" 191 testWriteLocal(t, local) 192 193 sc, err := RefreshState(remote) 194 if err != nil { 195 t.Fatalf("err: %v", err) 196 } 197 198 if sc != StateChangeConflict { 199 t.Fatalf("bad: %s", sc) 200 } 201 } 202 203 func TestPushState_NoState(t *testing.T) { 204 defer testFixCwd(testDir(t)) 205 206 remote, srv := testRemotePush(t, 200) 207 defer srv.Close() 208 209 sc, err := PushState(remote, false) 210 if err.Error() != "No local state to push" { 211 t.Fatalf("err: %v", err) 212 } 213 if sc != StateChangeNoop { 214 t.Fatalf("Bad: %v", sc) 215 } 216 } 217 218 func TestPushState_Update(t *testing.T) { 219 defer testFixCwd(testDir(t)) 220 221 remote, srv := testRemotePush(t, 200) 222 defer srv.Close() 223 224 local := terraform.NewState() 225 testWriteLocal(t, local) 226 227 sc, err := PushState(remote, false) 228 if err != nil { 229 t.Fatalf("err: %v", err) 230 } 231 if sc != StateChangeUpdateRemote { 232 t.Fatalf("Bad: %v", sc) 233 } 234 } 235 236 func TestPushState_RemoteNewer(t *testing.T) { 237 defer testFixCwd(testDir(t)) 238 239 remote, srv := testRemotePush(t, 412) 240 defer srv.Close() 241 242 local := terraform.NewState() 243 testWriteLocal(t, local) 244 245 sc, err := PushState(remote, false) 246 if err != nil { 247 t.Fatalf("err: %v", err) 248 } 249 if sc != StateChangeRemoteNewer { 250 t.Fatalf("Bad: %v", sc) 251 } 252 } 253 254 func TestPushState_Conflict(t *testing.T) { 255 defer testFixCwd(testDir(t)) 256 257 remote, srv := testRemotePush(t, 409) 258 defer srv.Close() 259 260 local := terraform.NewState() 261 testWriteLocal(t, local) 262 263 sc, err := PushState(remote, false) 264 if err != nil { 265 t.Fatalf("err: %v", err) 266 } 267 if sc != StateChangeConflict { 268 t.Fatalf("Bad: %v", sc) 269 } 270 } 271 272 func TestPushState_Error(t *testing.T) { 273 defer testFixCwd(testDir(t)) 274 275 remote, srv := testRemotePush(t, 500) 276 defer srv.Close() 277 278 local := terraform.NewState() 279 testWriteLocal(t, local) 280 281 sc, err := PushState(remote, false) 282 if err != ErrRemoteInternal { 283 t.Fatalf("err: %v", err) 284 } 285 if sc != StateChangeNoop { 286 t.Fatalf("Bad: %v", sc) 287 } 288 } 289 290 func TestDeleteState(t *testing.T) { 291 defer testFixCwd(testDir(t)) 292 293 remote, srv := testRemotePush(t, 200) 294 defer srv.Close() 295 296 local := terraform.NewState() 297 testWriteLocal(t, local) 298 299 err := DeleteState(remote) 300 if err != nil { 301 t.Fatalf("err: %v", err) 302 } 303 } 304 305 func TestBlankState(t *testing.T) { 306 remote := &terraform.RemoteState{ 307 Type: "http", 308 Config: map[string]string{ 309 "address": "http://foo.com/", 310 }, 311 } 312 r, err := blankState(remote) 313 if err != nil { 314 t.Fatalf("err: %v", err) 315 } 316 s, err := terraform.ReadState(bytes.NewReader(r)) 317 if err != nil { 318 t.Fatalf("err: %v", err) 319 } 320 if !remote.Equals(s.Remote) { 321 t.Fatalf("remote mismatch") 322 } 323 } 324 325 func TestPersist(t *testing.T) { 326 tmp, cwd := testDir(t) 327 defer testFixCwd(tmp, cwd) 328 329 EnsureDirectory() 330 331 // Place old state file, should backup 332 old := filepath.Join(tmp, LocalDirectory, HiddenStateFile) 333 ioutil.WriteFile(old, []byte("test"), 0777) 334 335 remote := &terraform.RemoteState{ 336 Type: "http", 337 Config: map[string]string{ 338 "address": "http://foo.com/", 339 }, 340 } 341 blank, _ := blankState(remote) 342 if err := Persist(bytes.NewReader(blank)); err != nil { 343 t.Fatalf("err: %v", err) 344 } 345 346 // Check for backup 347 backup := filepath.Join(tmp, LocalDirectory, BackupHiddenStateFile) 348 out, err := ioutil.ReadFile(backup) 349 if err != nil { 350 t.Fatalf("Err: %v", err) 351 } 352 if string(out) != "test" { 353 t.Fatalf("bad: %v", out) 354 } 355 356 // Read the state 357 out, err = ioutil.ReadFile(old) 358 if err != nil { 359 t.Fatalf("Err: %v", err) 360 } 361 s, err := terraform.ReadState(bytes.NewReader(out)) 362 if err != nil { 363 t.Fatalf("Err: %v", err) 364 } 365 366 // Check the remote 367 if !remote.Equals(s.Remote) { 368 t.Fatalf("remote mismatch") 369 } 370 } 371 372 // testRemote is used to make a test HTTP server to 373 // return a given state file 374 func testRemote(t *testing.T, s *terraform.State) (*terraform.RemoteState, *httptest.Server) { 375 var b64md5 string 376 buf := bytes.NewBuffer(nil) 377 378 if s != nil { 379 enc := json.NewEncoder(buf) 380 if err := enc.Encode(s); err != nil { 381 t.Fatalf("err: %v", err) 382 } 383 md5 := md5.Sum(buf.Bytes()) 384 b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) 385 } 386 387 cb := func(resp http.ResponseWriter, req *http.Request) { 388 if s == nil { 389 resp.WriteHeader(404) 390 return 391 } 392 resp.Header().Set("Content-MD5", b64md5) 393 resp.Write(buf.Bytes()) 394 } 395 srv := httptest.NewServer(http.HandlerFunc(cb)) 396 remote := &terraform.RemoteState{ 397 Type: "http", 398 Config: map[string]string{ 399 "address": srv.URL, 400 }, 401 } 402 return remote, srv 403 } 404 405 // testRemotePush is used to make a test HTTP server to 406 // return a given status code on push 407 func testRemotePush(t *testing.T, c int) (*terraform.RemoteState, *httptest.Server) { 408 cb := func(resp http.ResponseWriter, req *http.Request) { 409 resp.WriteHeader(c) 410 } 411 srv := httptest.NewServer(http.HandlerFunc(cb)) 412 remote := &terraform.RemoteState{ 413 Type: "http", 414 Config: map[string]string{ 415 "address": srv.URL, 416 }, 417 } 418 return remote, srv 419 } 420 421 // testDir is used to change the current working directory 422 // into a test directory that should be remoted after 423 func testDir(t *testing.T) (string, string) { 424 tmp, err := ioutil.TempDir("", "remote") 425 if err != nil { 426 t.Fatalf("err: %v", err) 427 } 428 cwd, err := os.Getwd() 429 if err != nil { 430 t.Fatalf("err: %v", err) 431 } 432 os.Chdir(tmp) 433 if err := EnsureDirectory(); err != nil { 434 t.Fatalf("err: %v", err) 435 } 436 return tmp, cwd 437 } 438 439 // testFixCwd is used to as a defer to testDir 440 func testFixCwd(tmp, cwd string) { 441 os.Chdir(cwd) 442 os.RemoveAll(tmp) 443 } 444 445 // testReadLocal is used to just get the local state 446 func testReadLocal(t *testing.T) *terraform.State { 447 path, err := HiddenStatePath() 448 if err != nil { 449 t.Fatalf("err: %v", err) 450 } 451 raw, err := ioutil.ReadFile(path) 452 if err != nil && !os.IsNotExist(err) { 453 t.Fatalf("err: %v", err) 454 } 455 if raw == nil { 456 return nil 457 } 458 s, err := terraform.ReadState(bytes.NewReader(raw)) 459 if err != nil { 460 t.Fatalf("err: %v", err) 461 } 462 return s 463 } 464 465 // testWriteLocal is used to write the local state 466 func testWriteLocal(t *testing.T, s *terraform.State) { 467 path, err := HiddenStatePath() 468 if err != nil { 469 t.Fatalf("err: %v", err) 470 } 471 buf := bytes.NewBuffer(nil) 472 enc := json.NewEncoder(buf) 473 if err := enc.Encode(s); err != nil { 474 t.Fatalf("err: %v", err) 475 } 476 err = ioutil.WriteFile(path, buf.Bytes(), 0777) 477 if err != nil { 478 t.Fatalf("err: %v", err) 479 } 480 }