terraform/backend/remote-state/gcs/backend_test.go

250 lines
6.1 KiB
Go

package gcs
import (
"fmt"
"log"
"os"
"strings"
"testing"
"time"
"cloud.google.com/go/storage"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state/remote"
)
const (
noPrefix = ""
noEncryptionKey = ""
)
// See https://cloud.google.com/storage/docs/using-encryption-keys#generating_your_own_encryption_key
var encryptionKey = "yRyCOikXi1ZDNE0xN3yiFsJjg7LGimoLrGFcLZgQoVk="
func TestStateFile(t *testing.T) {
t.Parallel()
cases := []struct {
prefix string
defaultStateFile string
name string
wantStateFile string
wantLockFile string
}{
{"state", "", "default", "state/default.tfstate", "state/default.tflock"},
{"state", "", "test", "state/test.tfstate", "state/test.tflock"},
{"state", "legacy.tfstate", "default", "legacy.tfstate", "legacy.tflock"},
{"state", "legacy.tfstate", "test", "state/test.tfstate", "state/test.tflock"},
{"state", "legacy.state", "default", "legacy.state", "legacy.state.tflock"},
{"state", "legacy.state", "test", "state/test.tfstate", "state/test.tflock"},
}
for _, c := range cases {
b := &gcsBackend{
prefix: c.prefix,
defaultStateFile: c.defaultStateFile,
}
if got := b.stateFile(c.name); got != c.wantStateFile {
t.Errorf("stateFile(%q) = %q, want %q", c.name, got, c.wantStateFile)
}
if got := b.lockFile(c.name); got != c.wantLockFile {
t.Errorf("lockFile(%q) = %q, want %q", c.name, got, c.wantLockFile)
}
}
}
func TestRemoteClient(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, noPrefix, noEncryptionKey)
defer teardownBackend(t, be, noPrefix)
ss, err := be.State(backend.DefaultStateName)
if err != nil {
t.Fatalf("be.State(%q) = %v", backend.DefaultStateName, err)
}
rs, ok := ss.(*remote.State)
if !ok {
t.Fatalf("be.State(): got a %T, want a *remote.State", ss)
}
remote.TestClient(t, rs.Client)
}
func TestRemoteClientWithEncryption(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, noPrefix, encryptionKey)
defer teardownBackend(t, be, noPrefix)
ss, err := be.State(backend.DefaultStateName)
if err != nil {
t.Fatalf("be.State(%q) = %v", backend.DefaultStateName, err)
}
rs, ok := ss.(*remote.State)
if !ok {
t.Fatalf("be.State(): got a %T, want a *remote.State", ss)
}
remote.TestClient(t, rs.Client)
}
func TestRemoteLocks(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, noPrefix, noEncryptionKey)
defer teardownBackend(t, be, noPrefix)
remoteClient := func() (remote.Client, error) {
ss, err := be.State(backend.DefaultStateName)
if err != nil {
return nil, err
}
rs, ok := ss.(*remote.State)
if !ok {
return nil, fmt.Errorf("be.State(): got a %T, want a *remote.State", ss)
}
return rs.Client, nil
}
c0, err := remoteClient()
if err != nil {
t.Fatalf("remoteClient(0) = %v", err)
}
c1, err := remoteClient()
if err != nil {
t.Fatalf("remoteClient(1) = %v", err)
}
remote.TestRemoteLocks(t, c0, c1)
}
func TestBackend(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be0 := setupBackend(t, bucket, noPrefix, noEncryptionKey)
defer teardownBackend(t, be0, noPrefix)
be1 := setupBackend(t, bucket, noPrefix, noEncryptionKey)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
backend.TestBackendStateForceUnlock(t, be0, be1)
}
func TestBackendWithPrefix(t *testing.T) {
t.Parallel()
prefix := "test/prefix"
bucket := bucketName(t)
be0 := setupBackend(t, bucket, prefix, noEncryptionKey)
defer teardownBackend(t, be0, prefix)
be1 := setupBackend(t, bucket, prefix+"/", noEncryptionKey)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
func TestBackendWithEncryption(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be0 := setupBackend(t, bucket, noPrefix, encryptionKey)
defer teardownBackend(t, be0, noPrefix)
be1 := setupBackend(t, bucket, noPrefix, encryptionKey)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
// setupBackend returns a new GCS backend.
func setupBackend(t *testing.T, bucket, prefix, key string) backend.Backend {
t.Helper()
projectID := os.Getenv("GOOGLE_PROJECT")
if projectID == "" || os.Getenv("TF_ACC") == "" {
t.Skip("This test creates a bucket in GCS and populates it. " +
"Since this may incur costs, it will only run if " +
"the TF_ACC and GOOGLE_PROJECT environment variables are set.")
}
config := map[string]interface{}{
"project": projectID,
"bucket": bucket,
"prefix": prefix,
"encryption_key": key,
}
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.
func teardownBackend(t *testing.T, be backend.Backend, prefix string) {
t.Helper()
gcsBE, ok := be.(*gcsBackend)
if !ok {
t.Fatalf("be is a %T, want a *gcsBackend", be)
}
ctx := gcsBE.storageContext
bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
objs := bucket.Objects(ctx, nil)
for o, err := objs.Next(); err == nil; o, err = objs.Next() {
if err := bucket.Object(o.Name).Delete(ctx); err != nil {
log.Printf("Error trying to delete object: %s %s\n\n", o.Name, err)
} else {
log.Printf("Object deleted: %s", o.Name)
}
}
// Delete the bucket itself.
if err := bucket.Delete(ctx); err != nil {
t.Errorf("deleting bucket %q failed, manual cleanup may be required: %v", gcsBE.bucketName, err)
}
}
// bucketName returns a valid bucket name for this test.
func bucketName(t *testing.T) string {
name := fmt.Sprintf("tf-%x-%s", time.Now().UnixNano(), t.Name())
// Bucket names must contain 3 to 63 characters.
if len(name) > 63 {
name = name[:63]
}
return strings.ToLower(name)
}