Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: minio testcontainers impl #9

Merged
merged 1 commit into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ require (
github.com/mattn/go-sqlite3 v1.14.18
golang.org/x/sync v0.5.0
)

require (
github.com/go-sql-driver/mysql v1.7.1 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
6 changes: 3 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
Expand Down Expand Up @@ -342,8 +342,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
Expand Down Expand Up @@ -404,8 +404,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
114 changes: 114 additions & 0 deletions testcontainers/minio/examples/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// https://github.com/minio/minio/blob/master/docs/orchestration/docker-compose/docker-compose.yaml
// https://github.com/mmadfox/testcontainers/blob/master/minio/minio_test.go
// https://github.com/mmadfox/testcontainers/blob/master/minio/minio.go
// https://github.com/testcontainers/testcontainers-go/blob/main/examples/cockroachdb/cockroachdb.go
// https://dev.to/minhblues/easy-file-uploads-in-go-fiber-with-minio-393c
package examples

import (
"context"
"fmt"
"io"
"testing"
"time"

"github.com/adoublef/sdk/bytest"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

func TestMinio(t *testing.T) {
ctx := context.Background()

minioC, err := setupMinio(ctx)
if err != nil {
t.Fatal(err)
}

t.Cleanup(func() {
if err := minioC.Terminate(ctx); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
})

c, err := minio.New(minioC.URI, &minio.Options{
Creds: credentials.NewStaticV4("minioadmin", "minioadmin", ""), // seems to play no affect
Secure: false,
})
if err != nil {
t.Fatal(err)
}

bucketName := "testcontainers"
location := "eu-west-2"

// create bucket
err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
if err != nil {
t.Fatal(err)
}

objectName := "testdata"
contentType := "applcation/octet-stream"

uploadInfo, err := c.PutObject(ctx, bucketName, objectName, bytest.NewReader(bytest.MB*16), (bytest.MB * 16), minio.PutObjectOptions{ContentType: contentType})
if err != nil {
t.Fatal(err)
}

// object is a readSeekCloser
object, err := c.GetObject(ctx, uploadInfo.Bucket, uploadInfo.Key, minio.GetObjectOptions{})
if err != nil {
t.Fatal(err)
}
defer object.Close()

n, err := io.Copy(io.Discard, object)
if err != nil {
t.Fatal(err)
}

if n != bytest.MB*16 {
t.Fatalf("expected %d; got %d", bytest.MB*16, n)
}
}

type minioContainer struct {
testcontainers.Container
URI string
}

func setupMinio(ctx context.Context) (*minioContainer, error) {
req := testcontainers.ContainerRequest{
Image: "minio/minio:RELEASE.2024-01-16T16-07-38Z",
ExposedPorts: []string{"9000/tcp", "9001/tcp"},
Env: map[string]string{
"MINIO_ROOT_USER": "minioadmin",
"MINIO_ROOT_PASSWORD": "minioadmin",
},
Cmd: []string{"server", "/data"},
WaitingFor: wait.ForListeningPort("9000").WithStartupTimeout(time.Minute * 2),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return nil, err
}

mappedPort, err := container.MappedPort(ctx, "9000")
if err != nil {
return nil, err
}

hostIP, err := container.Host(ctx)
if err != nil {
return nil, err
}

uri := fmt.Sprintf("%s:%s", hostIP, mappedPort.Port())
return &minioContainer{Container: container, URI: uri}, nil
}
89 changes: 89 additions & 0 deletions testcontainers/minio/minio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package minio

import (
"context"
"fmt"
"time"

"github.com/testcontainers/testcontainers-go"

Check failure on line 8 in testcontainers/minio/minio.go

View workflow job for this annotation

GitHub Actions / test

github.com/go-sql-driver/[email protected]: missing go.sum entry for go.mod file; to add it:
"github.com/testcontainers/testcontainers-go/wait"

Check failure on line 9 in testcontainers/minio/minio.go

View workflow job for this annotation

GitHub Actions / test

github.com/go-sql-driver/[email protected]: missing go.sum entry for go.mod file; to add it:
)

const (
defaultUser = "minioadmin"
defaultPassword = "minioadmin"
defaultImage = "minio/minio:RELEASE.2024-01-16T16-07-38Z"
)

// MinioContainer
type MinioContainer struct {
testcontainers.Container
Username string
Password string
}

func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) {
req := testcontainers.ContainerRequest{
Image: defaultImage,
ExposedPorts: []string{"9000/tcp"},
WaitingFor: wait.ForListeningPort("9000").WithStartupTimeout(time.Minute * 2),
Env: map[string]string{
"MINIO_ROOT_USER": defaultUser,
"MINIO_ROOT_PASSWORD": defaultPassword,
},
Cmd: []string{"server", "/data"},
}

genericContainerReq := testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
}

for _, opt := range opts {
opt.Customize(&genericContainerReq)
}
username := req.Env["MINIO_ROOT_USER"]
password := req.Env["MINIO_ROOT_PASSWORD"]
if username == "" || password == "" {
return nil, fmt.Errorf("username or password has not been set")
}

container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
if err != nil {
return nil, err
}
return &MinioContainer{Container: container, Username: username, Password: password}, nil
}

// WithUsername
func WithUsername(username string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) {
if username == "" {
username = defaultUser
}
req.Env["MINIO_ROOT_USER"] = username
}
}

// WithPassword
func WithPassword(password string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) {
if password == "" {
password = defaultPassword
}
req.Env["MINIO_ROOT_PASSWORD"] = password
}
}

// ConnectionString
func (c *MinioContainer) ConnectionString(ctx context.Context) (string, error) {
host, err := c.Host(ctx)
if err != nil {
return "", err
}
port, err := c.MappedPort(ctx, "9000/tcp")
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s", host, port.Port()), nil
}
73 changes: 73 additions & 0 deletions testcontainers/minio/minio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package minio_test

import (
"context"
"io"
"testing"

"github.com/adoublef/sdk/bytest"
. "github.com/adoublef/sdk/testcontainers/minio"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)

func TestContainer(t *testing.T) {
ctx := context.Background()

minioContainer, err := RunContainer(ctx, WithUsername("username"), WithPassword("password"))
if err != nil {
t.Fatal(err)
}

defer func() {
if err := minioContainer.Terminate(ctx); err != nil {
panic(err)
}
}()

url, err := minioContainer.ConnectionString(ctx)
if err != nil {
t.Fatal(err)
}

minioClient, err := minio.New(url, &minio.Options{
Creds: credentials.NewStaticV4(minioContainer.Username, minioContainer.Password, ""), // seems to play no affect
Secure: false,
})
if err != nil {
t.Fatal(err)
}

bucketName := "testcontainers"
location := "eu-west-2"

// create bucket
err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
if err != nil {
t.Fatal(err)
}

objectName := "testdata"
contentType := "applcation/octet-stream"

uploadInfo, err := minioClient.PutObject(ctx, bucketName, objectName, bytest.NewReader(bytest.MB*16), (bytest.MB * 16), minio.PutObjectOptions{ContentType: contentType})
if err != nil {
t.Fatal(err)
}

// object is a readSeekCloser
object, err := minioClient.GetObject(ctx, uploadInfo.Bucket, uploadInfo.Key, minio.GetObjectOptions{})
if err != nil {
t.Fatal(err)
}
defer object.Close()

n, err := io.Copy(io.Discard, object)
if err != nil {
t.Fatal(err)
}

if n != bytest.MB*16 {
t.Fatalf("expected %d; got %d", bytest.MB*16, n)
}
}
Loading