create unique buckets for each test, and clean up

This creates a unique bucket name for each test, so that the tests in
parallel don't collide, and buckets left over from interrupted tests
don't cause future failures.

Also make sure that buckets are removed, regardless of content.
This commit is contained in:
James Bardin 2017-12-06 16:49:29 -05:00
parent aec45e6967
commit 9dea2f78d4
1 changed files with 54 additions and 64 deletions

View File

@ -2,10 +2,13 @@ package gcs
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
"cloud.google.com/go/storage"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
) )
@ -48,7 +51,8 @@ func TestStateFile(t *testing.T) {
func TestRemoteClient(t *testing.T) { func TestRemoteClient(t *testing.T) {
t.Parallel() t.Parallel()
be := setupBackend(t, noPrefix) bucket := bucketName(t)
be := setupBackend(t, bucket, noPrefix)
defer teardownBackend(t, be, noPrefix) defer teardownBackend(t, be, noPrefix)
ss, err := be.State(backend.DefaultStateName) ss, err := be.State(backend.DefaultStateName)
@ -67,7 +71,8 @@ func TestRemoteClient(t *testing.T) {
func TestRemoteLocks(t *testing.T) { func TestRemoteLocks(t *testing.T) {
t.Parallel() t.Parallel()
be := setupBackend(t, noPrefix) bucket := bucketName(t)
be := setupBackend(t, bucket, noPrefix)
defer teardownBackend(t, be, noPrefix) defer teardownBackend(t, be, noPrefix)
remoteClient := func() (remote.Client, error) { remoteClient := func() (remote.Client, error) {
@ -99,10 +104,12 @@ func TestRemoteLocks(t *testing.T) {
func TestBackend(t *testing.T) { func TestBackend(t *testing.T) {
t.Parallel() t.Parallel()
be0 := setupBackend(t, noPrefix) bucket := bucketName(t)
be0 := setupBackend(t, bucket, noPrefix)
defer teardownBackend(t, be0, noPrefix) defer teardownBackend(t, be0, noPrefix)
be1 := setupBackend(t, noPrefix) be1 := setupBackend(t, bucket, noPrefix)
backend.TestBackend(t, be0, be1) backend.TestBackend(t, be0, be1)
} }
@ -110,17 +117,18 @@ func TestBackendWithPrefix(t *testing.T) {
t.Parallel() t.Parallel()
prefix := "test/prefix" prefix := "test/prefix"
bucket := bucketName(t)
be0 := setupBackend(t, prefix) be0 := setupBackend(t, bucket, prefix)
defer teardownBackend(t, be0, prefix) defer teardownBackend(t, be0, prefix)
be1 := setupBackend(t, prefix+"/") be1 := setupBackend(t, bucket, prefix+"/")
backend.TestBackend(t, be0, be1) backend.TestBackend(t, be0, be1)
} }
// setupBackend returns a new GCS backend. // setupBackend returns a new GCS backend.
func setupBackend(t *testing.T, prefix string) backend.Backend { func setupBackend(t *testing.T, bucket, prefix string) backend.Backend {
t.Helper() t.Helper()
projectID := os.Getenv("GOOGLE_PROJECT") projectID := os.Getenv("GOOGLE_PROJECT")
@ -132,7 +140,7 @@ func setupBackend(t *testing.T, prefix string) backend.Backend {
config := map[string]interface{}{ config := map[string]interface{}{
"project": projectID, "project": projectID,
"bucket": toBucketName(projectID + "-" + t.Name()), "bucket": bucket,
"prefix": prefix, "prefix": prefix,
} }
@ -143,81 +151,63 @@ func setupBackend(t *testing.T, prefix string) backend.Backend {
t.Log("using default credentials; set GOOGLE_CREDENTIALS for custom credentials") t.Log("using default credentials; set GOOGLE_CREDENTIALS for custom credentials")
} }
return backend.TestBackendConfig(t, New(), config) b := backend.TestBackendConfig(t, New(), config)
be := b.(*gcsBackend)
// create the bucket if it doesn't exist
bkt := be.storageClient.Bucket(bucket)
_, err := bkt.Attrs(be.storageContext)
if err != nil {
if err != storage.ErrBucketNotExist {
t.Fatal(err)
}
attrs := &storage.BucketAttrs{
Location: be.region,
}
err := bkt.Create(be.storageContext, be.projectID, attrs)
if err != nil {
t.Fatal(err)
}
}
return b
} }
// teardownBackend deletes all states from be except the default state. // teardownBackend deletes all states from be except the default state.
func teardownBackend(t *testing.T, be backend.Backend, prefix string) { func teardownBackend(t *testing.T, be backend.Backend, prefix string) {
t.Helper() t.Helper()
// Delete all states. The bucket must be empty before it can be deleted.
states, err := be.States()
if err != nil {
t.Fatalf("be.States() = %v; manual clean-up may be required", err)
}
for _, st := range states {
if st == backend.DefaultStateName {
continue
}
if err := be.DeleteState(st); err != nil {
t.Fatalf("be.DeleteState(%q) = %v; manual clean-up may be required", st, err)
}
}
gcsBE, ok := be.(*gcsBackend) gcsBE, ok := be.(*gcsBackend)
if !ok { if !ok {
t.Fatalf("be is a %T, want a *gcsBackend", be) t.Fatalf("be is a %T, want a *gcsBackend", be)
} }
ctx := gcsBE.storageContext ctx := gcsBE.storageContext
// Delete the default state, which DeleteState() will refuse to do. bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
// It's okay if this fails, not all tests create a default state. objs := bucket.Objects(ctx, nil)
ds := "default.tfstate"
if prefix != "" { for o, err := objs.Next(); err == nil; o, err = objs.Next() {
ds = fmt.Sprintf("%s/%s", prefix, ds) if err := bucket.Object(o.Name).Delete(ctx); err != nil {
} log.Printf("Error trying to delete object: %s %s\n\n", o.Name, err)
if err := gcsBE.storageClient.Bucket(gcsBE.bucketName).Object(ds).Delete(ctx); err != nil { } else {
t.Logf("deleting \"%s\": %v; manual clean-up may be required", ds, err) log.Printf("Object deleted: %s", o.Name)
}
} }
// Delete the bucket itself. // Delete the bucket itself.
if err := gcsBE.storageClient.Bucket(gcsBE.bucketName).Delete(ctx); err != nil { if err := bucket.Delete(ctx); err != nil {
t.Fatalf("deleting bucket failed: %v; manual cleanup may be required, though later test runs will happily reuse an existing bucket", err) t.Errorf("deleting bucket %q failed, manual cleanup may be required: %v", gcsBE.bucketName, err)
} }
} }
// toBucketName returns a copy of in that is suitable for use as a bucket name. // bucketName returns a valid bucket name for this test.
// All upper case characters are converted to lower case, other invalid func bucketName(t *testing.T) string {
// characters are replaced by '_'. name := fmt.Sprintf("tf-%x-%s", time.Now().UnixNano(), t.Name())
func toBucketName(in string) string {
// Bucket names must contain only lowercase letters, numbers, dashes
// (-), and underscores (_).
isValid := func(r rune) bool {
switch {
case r >= 'a' && r <= 'z':
return true
case r >= '0' && r <= '9':
return true
case r == '-' || r == '_':
return true
default:
return false
}
}
out := make([]rune, 0, len(in))
for _, r := range strings.ToLower(in) {
if !isValid(r) {
r = '_'
}
out = append(out, r)
}
// Bucket names must contain 3 to 63 characters. // Bucket names must contain 3 to 63 characters.
if len(out) > 63 { if len(name) > 63 {
out = out[:63] name = name[:63]
} }
return string(out) return strings.ToLower(name)
} }