github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/state/remote/atlas_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  func TestAtlasClient_impl(t *testing.T) {
    16  	var _ Client = new(AtlasClient)
    17  }
    18  
    19  func TestAtlasClient(t *testing.T) {
    20  	if _, err := http.Get("http://google.com"); err != nil {
    21  		t.Skipf("skipping, internet seems to not be available: %s", err)
    22  	}
    23  
    24  	token := os.Getenv("ATLAS_TOKEN")
    25  	if token == "" {
    26  		t.Skipf("skipping, ATLAS_TOKEN must be set")
    27  	}
    28  
    29  	client, err := atlasFactory(map[string]string{
    30  		"access_token": token,
    31  		"name":         "hashicorp/test-remote-state",
    32  	})
    33  	if err != nil {
    34  		t.Fatalf("bad: %s", err)
    35  	}
    36  
    37  	testClient(t, client)
    38  }
    39  
    40  func TestAtlasClient_ReportedConflictEqualStates(t *testing.T) {
    41  	fakeAtlas := newFakeAtlas(t, testStateModuleOrderChange)
    42  	srv := fakeAtlas.Server()
    43  	defer srv.Close()
    44  	client, err := atlasFactory(map[string]string{
    45  		"access_token": "sometoken",
    46  		"name":         "someuser/some-test-remote-state",
    47  		"address":      srv.URL,
    48  	})
    49  	if err != nil {
    50  		t.Fatalf("err: %s", err)
    51  	}
    52  
    53  	state, err := terraform.ReadState(bytes.NewReader(testStateModuleOrderChange))
    54  	if err != nil {
    55  		t.Fatalf("err: %s", err)
    56  	}
    57  
    58  	var stateJson bytes.Buffer
    59  	if err := terraform.WriteState(state, &stateJson); err != nil {
    60  		t.Fatalf("err: %s", err)
    61  	}
    62  	if err := client.Put(stateJson.Bytes()); err != nil {
    63  		t.Fatalf("err: %s", err)
    64  	}
    65  }
    66  
    67  func TestAtlasClient_NoConflict(t *testing.T) {
    68  	fakeAtlas := newFakeAtlas(t, testStateSimple)
    69  	srv := fakeAtlas.Server()
    70  	defer srv.Close()
    71  	client, err := atlasFactory(map[string]string{
    72  		"access_token": "sometoken",
    73  		"name":         "someuser/some-test-remote-state",
    74  		"address":      srv.URL,
    75  	})
    76  	if err != nil {
    77  		t.Fatalf("err: %s", err)
    78  	}
    79  
    80  	state, err := terraform.ReadState(bytes.NewReader(testStateSimple))
    81  	if err != nil {
    82  		t.Fatalf("err: %s", err)
    83  	}
    84  
    85  	fakeAtlas.NoConflictAllowed(true)
    86  
    87  	var stateJson bytes.Buffer
    88  	if err := terraform.WriteState(state, &stateJson); err != nil {
    89  		t.Fatalf("err: %s", err)
    90  	}
    91  	if err := client.Put(stateJson.Bytes()); err != nil {
    92  		t.Fatalf("err: %s", err)
    93  	}
    94  }
    95  
    96  func TestAtlasClient_LegitimateConflict(t *testing.T) {
    97  	fakeAtlas := newFakeAtlas(t, testStateSimple)
    98  	srv := fakeAtlas.Server()
    99  	defer srv.Close()
   100  	client, err := atlasFactory(map[string]string{
   101  		"access_token": "sometoken",
   102  		"name":         "someuser/some-test-remote-state",
   103  		"address":      srv.URL,
   104  	})
   105  	if err != nil {
   106  		t.Fatalf("err: %s", err)
   107  	}
   108  
   109  	state, err := terraform.ReadState(bytes.NewReader(testStateSimple))
   110  	if err != nil {
   111  		t.Fatalf("err: %s", err)
   112  	}
   113  
   114  	// Changing the state but not the serial. Should generate a conflict.
   115  	state.RootModule().Outputs["drift"] = "happens"
   116  
   117  	var stateJson bytes.Buffer
   118  	if err := terraform.WriteState(state, &stateJson); err != nil {
   119  		t.Fatalf("err: %s", err)
   120  	}
   121  	if err := client.Put(stateJson.Bytes()); err == nil {
   122  		t.Fatal("Expected error from state conflict, got none.")
   123  	}
   124  }
   125  
   126  func TestAtlasClient_UnresolvableConflict(t *testing.T) {
   127  	fakeAtlas := newFakeAtlas(t, testStateSimple)
   128  
   129  	// Something unexpected causes Atlas to conflict in a way that we can't fix.
   130  	fakeAtlas.AlwaysConflict(true)
   131  
   132  	srv := fakeAtlas.Server()
   133  	defer srv.Close()
   134  	client, err := atlasFactory(map[string]string{
   135  		"access_token": "sometoken",
   136  		"name":         "someuser/some-test-remote-state",
   137  		"address":      srv.URL,
   138  	})
   139  	if err != nil {
   140  		t.Fatalf("err: %s", err)
   141  	}
   142  
   143  	state, err := terraform.ReadState(bytes.NewReader(testStateSimple))
   144  	if err != nil {
   145  		t.Fatalf("err: %s", err)
   146  	}
   147  
   148  	var stateJson bytes.Buffer
   149  	if err := terraform.WriteState(state, &stateJson); err != nil {
   150  		t.Fatalf("err: %s", err)
   151  	}
   152  	doneCh := make(chan struct{})
   153  	go func() {
   154  		defer close(doneCh)
   155  		if err := client.Put(stateJson.Bytes()); err == nil {
   156  			t.Fatal("Expected error from state conflict, got none.")
   157  		}
   158  	}()
   159  
   160  	select {
   161  	case <-doneCh:
   162  		// OK
   163  	case <-time.After(50 * time.Millisecond):
   164  		t.Fatalf("Timed out after 50ms, probably because retrying infinitely.")
   165  	}
   166  }
   167  
   168  // Stub Atlas HTTP API for a given state JSON string; does checksum-based
   169  // conflict detection equivalent to Atlas's.
   170  type fakeAtlas struct {
   171  	state []byte
   172  	t     *testing.T
   173  
   174  	// Used to test that we only do the special conflict handling retry once.
   175  	alwaysConflict bool
   176  
   177  	// Used to fail the test immediately if a conflict happens.
   178  	noConflictAllowed bool
   179  }
   180  
   181  func newFakeAtlas(t *testing.T, state []byte) *fakeAtlas {
   182  	return &fakeAtlas{
   183  		state: state,
   184  		t:     t,
   185  	}
   186  }
   187  
   188  func (f *fakeAtlas) Server() *httptest.Server {
   189  	return httptest.NewServer(http.HandlerFunc(f.handler))
   190  }
   191  
   192  func (f *fakeAtlas) CurrentState() *terraform.State {
   193  	currentState, err := terraform.ReadState(bytes.NewReader(f.state))
   194  	if err != nil {
   195  		f.t.Fatalf("err: %s", err)
   196  	}
   197  	return currentState
   198  }
   199  
   200  func (f *fakeAtlas) CurrentSerial() int64 {
   201  	return f.CurrentState().Serial
   202  }
   203  
   204  func (f *fakeAtlas) CurrentSum() [md5.Size]byte {
   205  	return md5.Sum(f.state)
   206  }
   207  
   208  func (f *fakeAtlas) AlwaysConflict(b bool) {
   209  	f.alwaysConflict = b
   210  }
   211  
   212  func (f *fakeAtlas) NoConflictAllowed(b bool) {
   213  	f.noConflictAllowed = b
   214  }
   215  
   216  func (f *fakeAtlas) handler(resp http.ResponseWriter, req *http.Request) {
   217  	switch req.Method {
   218  	case "GET":
   219  		// Respond with the current stored state.
   220  		resp.Header().Set("Content-Type", "application/json")
   221  		resp.Write(f.state)
   222  	case "PUT":
   223  		var buf bytes.Buffer
   224  		buf.ReadFrom(req.Body)
   225  		sum := md5.Sum(buf.Bytes())
   226  		state, err := terraform.ReadState(&buf)
   227  		if err != nil {
   228  			f.t.Fatalf("err: %s", err)
   229  		}
   230  		conflict := f.CurrentSerial() == state.Serial && f.CurrentSum() != sum
   231  		conflict = conflict || f.alwaysConflict
   232  		if conflict {
   233  			if f.noConflictAllowed {
   234  				f.t.Fatal("Got conflict when NoConflictAllowed was set.")
   235  			}
   236  			http.Error(resp, "Conflict", 409)
   237  		} else {
   238  			f.state = buf.Bytes()
   239  			resp.WriteHeader(200)
   240  		}
   241  	}
   242  }
   243  
   244  // This is a tfstate file with the module order changed, which is a structural
   245  // but not a semantic difference. Terraform will sort these modules as it
   246  // loads the state.
   247  var testStateModuleOrderChange = []byte(
   248  	`{
   249      "version": 1,
   250      "serial": 1,
   251      "modules": [
   252          {
   253              "path": [
   254                  "root",
   255                  "child2",
   256                  "grandchild"
   257              ],
   258              "outputs": {
   259                  "foo": "bar2"
   260              },
   261              "resources": null
   262          },
   263          {
   264              "path": [
   265                  "root",
   266                  "child1",
   267                  "grandchild"
   268              ],
   269              "outputs": {
   270                  "foo": "bar1"
   271              },
   272              "resources": null
   273          }
   274      ]
   275  }
   276  `)
   277  
   278  var testStateSimple = []byte(
   279  	`{
   280      "version": 1,
   281      "serial": 1,
   282      "modules": [
   283          {
   284              "path": [
   285                  "root"
   286              ],
   287              "outputs": {
   288                  "foo": "bar"
   289              },
   290              "resources": null
   291          }
   292      ]
   293  }
   294  `)