Skip to content

Commit 4aca65b

Browse files
authored
PBM-1475 add tests for s3 gcp storage access (#1101)
* add testcontainers minio * add wip s3 test * update name * add wip gcs test * update container config * wip gcs test * update test coverage * update s3 coverage * extract to helper * increase coverage * update mod * fix reviewdog
1 parent a972e30 commit 4aca65b

File tree

9 files changed

+694
-1
lines changed

9 files changed

+694
-1
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/aws/aws-sdk-go-v2/service/sts v1.33.8
1515
github.com/aws/smithy-go v1.22.1
1616
github.com/docker/docker v27.1.1+incompatible
17+
github.com/docker/go-connections v0.5.0
1718
github.com/fsnotify/fsnotify v1.7.0
1819
github.com/golang/snappy v0.0.4
1920
github.com/google/uuid v1.6.0
@@ -26,6 +27,7 @@ require (
2627
github.com/spf13/cobra v1.8.1
2728
github.com/spf13/viper v1.19.0
2829
github.com/testcontainers/testcontainers-go v0.34.0
30+
github.com/testcontainers/testcontainers-go/modules/minio v0.34.0
2931
github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0
3032
go.mongodb.org/mongo-driver v1.17.1
3133
golang.org/x/mod v0.19.0
@@ -62,7 +64,6 @@ require (
6264
github.com/cpuguy83/dockercfg v0.3.2 // indirect
6365
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
6466
github.com/distribution/reference v0.6.0 // indirect
65-
github.com/docker/go-connections v0.5.0 // indirect
6667
github.com/docker/go-units v0.5.0 // indirect
6768
github.com/felixge/httpsnoop v1.0.4 // indirect
6869
github.com/go-logr/logr v1.4.1 // indirect

go.sum

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
9898
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
9999
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
100100
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
101+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
102+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
101103
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
102104
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
103105
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -171,12 +173,16 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
171173
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
172174
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
173175
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
176+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
177+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
174178
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
175179
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
176180
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
177181
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
178182
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
179183
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
184+
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
185+
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
180186
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
181187
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
182188
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -189,6 +195,12 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
189195
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
190196
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
191197
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
198+
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
199+
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
200+
github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ=
201+
github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
202+
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
203+
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
192204
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
193205
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
194206
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -201,6 +213,10 @@ github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
201213
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
202214
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
203215
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
216+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
217+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
218+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
219+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
204220
github.com/mongodb/mongo-tools v0.0.0-20240723193119-837c2bc263f4 h1:23sRjM+3p+4yFL9tOg9qfNJHtBMl5PN5XA2iLWrYR+Y=
205221
github.com/mongodb/mongo-tools v0.0.0-20240723193119-837c2bc263f4/go.mod h1:mq5q2Rrbw6+VEtDc+p5haujgWoQv3foL2YS5YISr2UA=
206222
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
@@ -227,6 +243,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
227243
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
228244
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
229245
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
246+
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
247+
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
230248
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
231249
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
232250
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@@ -273,6 +291,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
273291
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
274292
github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
275293
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
294+
github.com/testcontainers/testcontainers-go/modules/minio v0.34.0 h1:OpUqT7VV/d+wriDMHcCZCUfOoFE6wiHnGVzJOXqq8lU=
295+
github.com/testcontainers/testcontainers-go/modules/minio v0.34.0/go.mod h1:0iaOtVNCzu04KcXHgmdNE7aelKaMUwC9x1M0oe6h1sw=
276296
github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0 h1:o3bgcECyBFfMwqexCH/6vIJ8XzbCffCP/Euesu33rgY=
277297
github.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0/go.mod h1:ljLR42dN7k40CX0dp30R8BRIB3OOdvr7rBANEpfmMs4=
278298
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=

pbm/storage/gcs/gcs_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package gcs
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
gcs "cloud.google.com/go/storage"
11+
"github.com/docker/docker/api/types/container"
12+
"github.com/docker/go-connections/nat"
13+
"github.com/testcontainers/testcontainers-go"
14+
"github.com/testcontainers/testcontainers-go/wait"
15+
"google.golang.org/api/option"
16+
17+
"github.com/percona/percona-backup-mongodb/pbm/storage"
18+
)
19+
20+
func TestGCS(t *testing.T) {
21+
ctx := context.Background()
22+
23+
req := testcontainers.ContainerRequest{
24+
Image: "fsouza/fake-gcs-server",
25+
ExposedPorts: []string{"4443/tcp"},
26+
Cmd: []string{"-public-host", "localhost:4443", "-scheme", "http", "-port", "4443"},
27+
WaitingFor: wait.ForLog("server started at"),
28+
HostConfigModifier: func(hc *container.HostConfig) {
29+
hc.PortBindings = nat.PortMap{
30+
"4443/tcp": {{HostIP: "0.0.0.0", HostPort: "4443"}},
31+
}
32+
},
33+
}
34+
35+
gcsContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
36+
ContainerRequest: req,
37+
Started: true,
38+
})
39+
if err != nil {
40+
t.Fatalf("failed to start GCS container: %s", err)
41+
}
42+
defer func() {
43+
if err = testcontainers.TerminateContainer(gcsContainer); err != nil {
44+
t.Fatalf("failed to terminate container: %s", err)
45+
}
46+
}()
47+
48+
host, err := gcsContainer.Host(ctx)
49+
if err != nil {
50+
t.Fatalf("failed to get container host: %s", err)
51+
}
52+
port, err := gcsContainer.MappedPort(ctx, "4443")
53+
if err != nil {
54+
t.Fatalf("failed to get mapped port: %s", err)
55+
}
56+
endpoint := fmt.Sprintf("http://%s:%s", host, port.Port())
57+
fmt.Println(endpoint)
58+
59+
_ = os.Setenv("STORAGE_EMULATOR_HOST", endpoint)
60+
defer func() {
61+
_ = os.Unsetenv("STORAGE_EMULATOR_HOST")
62+
}()
63+
64+
gcsClient, err := gcs.NewClient(ctx, option.WithoutAuthentication())
65+
if err != nil {
66+
t.Fatalf("failed to create GCS client: %s", err)
67+
}
68+
defer func() {
69+
if err = gcsClient.Close(); err != nil {
70+
t.Fatalf("failed to close client: %s", err)
71+
}
72+
}()
73+
74+
bucketName := "test-bucket"
75+
bucket := gcsClient.Bucket(bucketName)
76+
if err = bucket.Create(ctx, "fakeProject", nil); err != nil {
77+
t.Fatalf("failed to create bucket: %s", err)
78+
}
79+
80+
chunkSize := 1024
81+
82+
opts := &Config{
83+
Bucket: bucketName,
84+
ChunkSize: &chunkSize,
85+
Credentials: Credentials{
86+
ClientEmail: "[email protected]",
87+
PrivateKey: "-----BEGIN PRIVATE KEY-----\nKey\n-----END PRIVATE KEY-----\n",
88+
},
89+
Retryer: &Retryer{
90+
BackoffInitial: 1,
91+
BackoffMax: 2,
92+
BackoffMultiplier: 1,
93+
},
94+
}
95+
96+
stg, err := New(opts, "node", nil)
97+
if err != nil {
98+
t.Fatalf("failed to create gcs storage: %s", err)
99+
}
100+
101+
storage.RunStorageTests(t, stg, storage.GCS)
102+
103+
t.Run("Delete fails", func(t *testing.T) {
104+
name := "not_found.txt"
105+
err := stg.Delete(name)
106+
107+
if err == nil {
108+
t.Errorf("expected error when deleting non-existing file, got nil")
109+
}
110+
})
111+
}
112+
113+
func TestConfig(t *testing.T) {
114+
opts := &Config{
115+
Bucket: "bucketName",
116+
Prefix: "prefix",
117+
Credentials: Credentials{
118+
ClientEmail: "[email protected]",
119+
PrivateKey: "-----BEGIN PRIVATE KEY-----\nKey\n-----END PRIVATE KEY-----\n",
120+
},
121+
}
122+
123+
t.Run("Clone", func(t *testing.T) {
124+
clone := opts.Clone()
125+
if clone == opts {
126+
t.Error("expected clone to be a different pointer")
127+
}
128+
129+
if !opts.Equal(clone) {
130+
t.Error("expected clone to be equal")
131+
}
132+
133+
opts.Bucket = "updatedName"
134+
if opts.Equal(clone) {
135+
t.Error("expected clone to be unchanged when updating original")
136+
}
137+
})
138+
139+
t.Run("Equal fails", func(t *testing.T) {
140+
if opts.Equal(nil) {
141+
t.Error("expected not to be equal other nil")
142+
}
143+
144+
clone := opts.Clone()
145+
clone.Prefix = "updatedPrefix"
146+
if opts.Equal(clone) {
147+
t.Error("expected not to be equal when updating prefix")
148+
}
149+
150+
clone = opts.Clone()
151+
clone.Credentials.ClientEmail = "[email protected]"
152+
if opts.Equal(clone) {
153+
t.Error("expected not to be equal when updating credentials")
154+
}
155+
})
156+
}
157+
158+
func TestEmptyCredentialsFail(t *testing.T) {
159+
opts := &Config{
160+
Bucket: "bucketName",
161+
}
162+
163+
_, err := New(opts, "node", nil)
164+
165+
if err == nil {
166+
t.Fatalf("expected error when not specifying credentials")
167+
}
168+
169+
if !strings.Contains(err.Error(), "required for GCS credentials") {
170+
t.Errorf("expected required credentials, got %s", err)
171+
}
172+
}

0 commit comments

Comments
 (0)