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