github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/backend/remote-state/s3/backend_test.go (about)

     1  package s3
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/service/dynamodb"
    12  	"github.com/aws/aws-sdk-go/service/s3"
    13  	"github.com/hashicorp/terraform/backend"
    14  	"github.com/hashicorp/terraform/state/remote"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  // verify that we are doing ACC tests or the S3 tests specifically
    19  func testACC(t *testing.T) {
    20  	skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_S3_TEST") == ""
    21  	if skip {
    22  		t.Log("s3 backend tests require setting TF_ACC or TF_S3_TEST")
    23  		t.Skip()
    24  	}
    25  	if os.Getenv("AWS_DEFAULT_REGION") == "" {
    26  		os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
    27  	}
    28  }
    29  
    30  func TestBackend_impl(t *testing.T) {
    31  	var _ backend.Backend = new(Backend)
    32  }
    33  
    34  func TestBackendConfig(t *testing.T) {
    35  	testACC(t)
    36  	config := map[string]interface{}{
    37  		"region":     "us-west-1",
    38  		"bucket":     "tf-test",
    39  		"key":        "state",
    40  		"encrypt":    true,
    41  		"lock_table": "dynamoTable",
    42  	}
    43  
    44  	b := backend.TestBackendConfig(t, New(), config).(*Backend)
    45  
    46  	if *b.s3Client.Config.Region != "us-west-1" {
    47  		t.Fatalf("Incorrect region was populated")
    48  	}
    49  	if b.bucketName != "tf-test" {
    50  		t.Fatalf("Incorrect bucketName was populated")
    51  	}
    52  	if b.keyName != "state" {
    53  		t.Fatalf("Incorrect keyName was populated")
    54  	}
    55  
    56  	credentials, err := b.s3Client.Config.Credentials.Get()
    57  	if err != nil {
    58  		t.Fatalf("Error when requesting credentials")
    59  	}
    60  	if credentials.AccessKeyID == "" {
    61  		t.Fatalf("No Access Key Id was populated")
    62  	}
    63  	if credentials.SecretAccessKey == "" {
    64  		t.Fatalf("No Secret Access Key was populated")
    65  	}
    66  }
    67  
    68  func TestBackend(t *testing.T) {
    69  	testACC(t)
    70  
    71  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
    72  	keyName := "testState"
    73  
    74  	b := backend.TestBackendConfig(t, New(), map[string]interface{}{
    75  		"bucket":  bucketName,
    76  		"key":     keyName,
    77  		"encrypt": true,
    78  	}).(*Backend)
    79  
    80  	createS3Bucket(t, b.s3Client, bucketName)
    81  	defer deleteS3Bucket(t, b.s3Client, bucketName)
    82  
    83  	backend.TestBackend(t, b, nil)
    84  }
    85  
    86  func TestBackendLocked(t *testing.T) {
    87  	testACC(t)
    88  
    89  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
    90  	keyName := "test/state"
    91  
    92  	b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    93  		"bucket":     bucketName,
    94  		"key":        keyName,
    95  		"encrypt":    true,
    96  		"lock_table": bucketName,
    97  	}).(*Backend)
    98  
    99  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
   100  		"bucket":     bucketName,
   101  		"key":        keyName,
   102  		"encrypt":    true,
   103  		"lock_table": bucketName,
   104  	}).(*Backend)
   105  
   106  	createS3Bucket(t, b1.s3Client, bucketName)
   107  	defer deleteS3Bucket(t, b1.s3Client, bucketName)
   108  	createDynamoDBTable(t, b1.dynClient, bucketName)
   109  	defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
   110  
   111  	backend.TestBackend(t, b1, b2)
   112  }
   113  
   114  // add some extra junk in S3 to try and confuse the env listing.
   115  func TestBackendExtraPaths(t *testing.T) {
   116  	testACC(t)
   117  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
   118  	keyName := "test/state/tfstate"
   119  
   120  	b := backend.TestBackendConfig(t, New(), map[string]interface{}{
   121  		"bucket":  bucketName,
   122  		"key":     keyName,
   123  		"encrypt": true,
   124  	}).(*Backend)
   125  
   126  	createS3Bucket(t, b.s3Client, bucketName)
   127  	defer deleteS3Bucket(t, b.s3Client, bucketName)
   128  
   129  	// put multiple states in old env paths.
   130  	s1 := terraform.NewState()
   131  	s2 := terraform.NewState()
   132  
   133  	// RemoteClient to Put things in various paths
   134  	client := &RemoteClient{
   135  		s3Client:             b.s3Client,
   136  		dynClient:            b.dynClient,
   137  		bucketName:           b.bucketName,
   138  		path:                 b.path("s1"),
   139  		serverSideEncryption: b.serverSideEncryption,
   140  		acl:                  b.acl,
   141  		kmsKeyID:             b.kmsKeyID,
   142  		lockTable:            b.lockTable,
   143  	}
   144  
   145  	stateMgr := &remote.State{Client: client}
   146  	stateMgr.WriteState(s1)
   147  	if err := stateMgr.PersistState(); err != nil {
   148  		t.Fatal(err)
   149  	}
   150  
   151  	client.path = b.path("s2")
   152  	stateMgr.WriteState(s2)
   153  	if err := stateMgr.PersistState(); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  
   161  	// put a state in an env directory name
   162  	client.path = keyEnvPrefix + "/error"
   163  	stateMgr.WriteState(terraform.NewState())
   164  	if err := stateMgr.PersistState(); err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil {
   168  		t.Fatal(err)
   169  	}
   170  
   171  	// add state with the wrong key for an existing env
   172  	client.path = keyEnvPrefix + "/s2/notTestState"
   173  	stateMgr.WriteState(terraform.NewState())
   174  	if err := stateMgr.PersistState(); err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	// remove the state with extra subkey
   182  	if err := b.DeleteState("s2"); err != nil {
   183  		t.Fatal(err)
   184  	}
   185  
   186  	if err := checkStateList(b, []string{"default", "s1"}); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	// fetch that state again, which should produce a new lineage
   191  	s2Mgr, err := b.State("s2")
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	if err := s2Mgr.RefreshState(); err != nil {
   196  		t.Fatal(err)
   197  	}
   198  
   199  	if s2Mgr.State().Lineage == s2.Lineage {
   200  		t.Fatal("state s2 was not deleted")
   201  	}
   202  	s2 = s2Mgr.State()
   203  
   204  	// add a state with a key that matches an existing environment dir name
   205  	client.path = keyEnvPrefix + "/s2/"
   206  	stateMgr.WriteState(terraform.NewState())
   207  	if err := stateMgr.PersistState(); err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	// make sure s2 is OK
   212  	s2Mgr, err = b.State("s2")
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  	if err := s2Mgr.RefreshState(); err != nil {
   217  		t.Fatal(err)
   218  	}
   219  
   220  	if s2Mgr.State().Lineage != s2.Lineage {
   221  		t.Fatal("we got the wrong state for s2")
   222  	}
   223  
   224  	if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil {
   225  		t.Fatal(err)
   226  	}
   227  }
   228  
   229  func checkStateList(b backend.Backend, expected []string) error {
   230  	states, err := b.States()
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	if !reflect.DeepEqual(states, expected) {
   236  		return fmt.Errorf("incorrect states listed: %q", states)
   237  	}
   238  	return nil
   239  }
   240  
   241  func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
   242  	createBucketReq := &s3.CreateBucketInput{
   243  		Bucket: &bucketName,
   244  	}
   245  
   246  	// Be clear about what we're doing in case the user needs to clean
   247  	// this up later.
   248  	t.Logf("creating S3 bucket %s in %s", bucketName, *s3Client.Config.Region)
   249  	_, err := s3Client.CreateBucket(createBucketReq)
   250  	if err != nil {
   251  		t.Fatal("failed to create test S3 bucket:", err)
   252  	}
   253  }
   254  
   255  func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
   256  	warning := "WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)"
   257  
   258  	// first we have to get rid of the env objects, or we can't delete the bucket
   259  	resp, err := s3Client.ListObjects(&s3.ListObjectsInput{Bucket: &bucketName})
   260  	if err != nil {
   261  		t.Logf(warning, err)
   262  		return
   263  	}
   264  	for _, obj := range resp.Contents {
   265  		if _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil {
   266  			// this will need cleanup no matter what, so just warn and exit
   267  			t.Logf(warning, err)
   268  			return
   269  		}
   270  	}
   271  
   272  	if _, err := s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucketName}); err != nil {
   273  		t.Logf(warning, err)
   274  	}
   275  }
   276  
   277  // create the dynamoDB table, and wait until we can query it.
   278  func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
   279  	createInput := &dynamodb.CreateTableInput{
   280  		AttributeDefinitions: []*dynamodb.AttributeDefinition{
   281  			{
   282  				AttributeName: aws.String("LockID"),
   283  				AttributeType: aws.String("S"),
   284  			},
   285  		},
   286  		KeySchema: []*dynamodb.KeySchemaElement{
   287  			{
   288  				AttributeName: aws.String("LockID"),
   289  				KeyType:       aws.String("HASH"),
   290  			},
   291  		},
   292  		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
   293  			ReadCapacityUnits:  aws.Int64(5),
   294  			WriteCapacityUnits: aws.Int64(5),
   295  		},
   296  		TableName: aws.String(tableName),
   297  	}
   298  
   299  	_, err := dynClient.CreateTable(createInput)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	// now wait until it's ACTIVE
   305  	start := time.Now()
   306  	time.Sleep(time.Second)
   307  
   308  	describeInput := &dynamodb.DescribeTableInput{
   309  		TableName: aws.String(tableName),
   310  	}
   311  
   312  	for {
   313  		resp, err := dynClient.DescribeTable(describeInput)
   314  		if err != nil {
   315  			t.Fatal(err)
   316  		}
   317  
   318  		if *resp.Table.TableStatus == "ACTIVE" {
   319  			return
   320  		}
   321  
   322  		if time.Since(start) > time.Minute {
   323  			t.Fatalf("timed out creating DynamoDB table %s", tableName)
   324  		}
   325  
   326  		time.Sleep(3 * time.Second)
   327  	}
   328  
   329  }
   330  
   331  func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
   332  	params := &dynamodb.DeleteTableInput{
   333  		TableName: aws.String(tableName),
   334  	}
   335  	_, err := dynClient.DeleteTable(params)
   336  	if err != nil {
   337  		t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err)
   338  	}
   339  }