github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/backend/remote-state/gcs/backend_test.go (about) 1 package gcs 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "testing" 8 9 "github.com/hashicorp/terraform/backend" 10 "github.com/hashicorp/terraform/state/remote" 11 ) 12 13 const noPrefix = "" 14 15 func TestStateFile(t *testing.T) { 16 t.Parallel() 17 18 cases := []struct { 19 prefix string 20 defaultStateFile string 21 name string 22 wantStateFile string 23 wantLockFile string 24 }{ 25 {"state", "", "default", "state/default.tfstate", "state/default.tflock"}, 26 {"state", "", "test", "state/test.tfstate", "state/test.tflock"}, 27 {"state", "legacy.tfstate", "default", "legacy.tfstate", "legacy.tflock"}, 28 {"state", "legacy.tfstate", "test", "state/test.tfstate", "state/test.tflock"}, 29 {"state", "legacy.state", "default", "legacy.state", "legacy.state.tflock"}, 30 {"state", "legacy.state", "test", "state/test.tfstate", "state/test.tflock"}, 31 } 32 for _, c := range cases { 33 b := &gcsBackend{ 34 prefix: c.prefix, 35 defaultStateFile: c.defaultStateFile, 36 } 37 38 if got := b.stateFile(c.name); got != c.wantStateFile { 39 t.Errorf("stateFile(%q) = %q, want %q", c.name, got, c.wantStateFile) 40 } 41 42 if got := b.lockFile(c.name); got != c.wantLockFile { 43 t.Errorf("lockFile(%q) = %q, want %q", c.name, got, c.wantLockFile) 44 } 45 } 46 } 47 48 func TestRemoteClient(t *testing.T) { 49 t.Parallel() 50 51 be := setupBackend(t, noPrefix) 52 defer teardownBackend(t, be, noPrefix) 53 54 ss, err := be.State(backend.DefaultStateName) 55 if err != nil { 56 t.Fatalf("be.State(%q) = %v", backend.DefaultStateName, err) 57 } 58 59 rs, ok := ss.(*remote.State) 60 if !ok { 61 t.Fatalf("be.State(): got a %T, want a *remote.State", ss) 62 } 63 64 remote.TestClient(t, rs.Client) 65 } 66 67 func TestRemoteLocks(t *testing.T) { 68 t.Parallel() 69 70 be := setupBackend(t, noPrefix) 71 defer teardownBackend(t, be, noPrefix) 72 73 remoteClient := func() (remote.Client, error) { 74 ss, err := be.State(backend.DefaultStateName) 75 if err != nil { 76 return nil, err 77 } 78 79 rs, ok := ss.(*remote.State) 80 if !ok { 81 return nil, fmt.Errorf("be.State(): got a %T, want a *remote.State", ss) 82 } 83 84 return rs.Client, nil 85 } 86 87 c0, err := remoteClient() 88 if err != nil { 89 t.Fatalf("remoteClient(0) = %v", err) 90 } 91 c1, err := remoteClient() 92 if err != nil { 93 t.Fatalf("remoteClient(1) = %v", err) 94 } 95 96 remote.TestRemoteLocks(t, c0, c1) 97 } 98 99 func TestBackend(t *testing.T) { 100 t.Parallel() 101 102 be0 := setupBackend(t, noPrefix) 103 defer teardownBackend(t, be0, noPrefix) 104 105 be1 := setupBackend(t, noPrefix) 106 107 backend.TestBackend(t, be0, be1) 108 } 109 func TestBackendWithPrefix(t *testing.T) { 110 t.Parallel() 111 112 prefix := "test/prefix" 113 114 be0 := setupBackend(t, prefix) 115 defer teardownBackend(t, be0, prefix) 116 117 be1 := setupBackend(t, prefix+"/") 118 119 backend.TestBackend(t, be0, be1) 120 } 121 122 // setupBackend returns a new GCS backend. 123 func setupBackend(t *testing.T, prefix string) backend.Backend { 124 t.Helper() 125 126 projectID := os.Getenv("GOOGLE_PROJECT") 127 if projectID == "" || os.Getenv("TF_ACC") == "" { 128 t.Skip("This test creates a bucket in GCS and populates it. " + 129 "Since this may incur costs, it will only run if " + 130 "the TF_ACC and GOOGLE_PROJECT environment variables are set.") 131 } 132 133 config := map[string]interface{}{ 134 "project": projectID, 135 "bucket": toBucketName(projectID + "-" + t.Name()), 136 "prefix": prefix, 137 } 138 139 if creds := os.Getenv("GOOGLE_CREDENTIALS"); creds != "" { 140 config["credentials"] = creds 141 t.Logf("using credentials from %q", creds) 142 } else { 143 t.Log("using default credentials; set GOOGLE_CREDENTIALS for custom credentials") 144 } 145 146 return backend.TestBackendConfig(t, New(), config) 147 } 148 149 // teardownBackend deletes all states from be except the default state. 150 func teardownBackend(t *testing.T, be backend.Backend, prefix string) { 151 t.Helper() 152 153 // Delete all states. The bucket must be empty before it can be deleted. 154 states, err := be.States() 155 if err != nil { 156 t.Fatalf("be.States() = %v; manual clean-up may be required", err) 157 } 158 for _, st := range states { 159 if st == backend.DefaultStateName { 160 continue 161 } 162 if err := be.DeleteState(st); err != nil { 163 t.Fatalf("be.DeleteState(%q) = %v; manual clean-up may be required", st, err) 164 } 165 } 166 167 gcsBE, ok := be.(*gcsBackend) 168 if !ok { 169 t.Fatalf("be is a %T, want a *gcsBackend", be) 170 } 171 ctx := gcsBE.storageContext 172 173 // Delete the default state, which DeleteState() will refuse to do. 174 // It's okay if this fails, not all tests create a default state. 175 ds := "default.tfstate" 176 if prefix != "" { 177 ds = fmt.Sprintf("%s/%s", prefix, ds) 178 } 179 if err := gcsBE.storageClient.Bucket(gcsBE.bucketName).Object(ds).Delete(ctx); err != nil { 180 t.Logf("deleting \"%s\": %v; manual clean-up may be required", ds, err) 181 } 182 183 // Delete the bucket itself. 184 if err := gcsBE.storageClient.Bucket(gcsBE.bucketName).Delete(ctx); err != nil { 185 t.Fatalf("deleting bucket failed: %v; manual cleanup may be required, though later test runs will happily reuse an existing bucket", err) 186 } 187 } 188 189 // toBucketName returns a copy of in that is suitable for use as a bucket name. 190 // All upper case characters are converted to lower case, other invalid 191 // characters are replaced by '_'. 192 func toBucketName(in string) string { 193 // Bucket names must contain only lowercase letters, numbers, dashes 194 // (-), and underscores (_). 195 isValid := func(r rune) bool { 196 switch { 197 case r >= 'a' && r <= 'z': 198 return true 199 case r >= '0' && r <= '9': 200 return true 201 case r == '-' || r == '_': 202 return true 203 default: 204 return false 205 206 } 207 } 208 209 out := make([]rune, 0, len(in)) 210 for _, r := range strings.ToLower(in) { 211 if !isValid(r) { 212 r = '_' 213 } 214 out = append(out, r) 215 } 216 217 // Bucket names must contain 3 to 63 characters. 218 if len(out) > 63 { 219 out = out[:63] 220 } 221 222 return string(out) 223 }