Skip to content

Commit 9966929

Browse files
committed
dx-422-local-s3-provider
1 parent e62f3f0 commit 9966929

File tree

7 files changed

+301
-0
lines changed

7 files changed

+301
-0
lines changed

framework/cmd/main.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"embed"
55
"fmt"
6+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/s3provider"
67
"io/fs"
78
"log"
89
"os"
@@ -243,6 +244,35 @@ func main() {
243244
return err
244245
},
245246
},
247+
{
248+
Name: "s3",
249+
Usage: "Controls local S3",
250+
Subcommands: []*cli.Command{
251+
{
252+
Name: "up",
253+
Usage: "ctf s3 up",
254+
Aliases: []string{"u"},
255+
Description: "Spins up a local S3 provider (minio)",
256+
Action: func(c *cli.Context) error {
257+
myS3, err := s3provider.NewMinioFactory().NewProvider(
258+
s3provider.WithPort(9000),
259+
s3provider.WithConsolePort(9001),
260+
)
261+
if err != nil {
262+
return err
263+
}
264+
L.Info().Str("label", "framework=ctf").Msg(fmt.Sprintf(
265+
"S3 provider running: Endpoint: %s\tAccessKey: %s\tSecretKey: %s\tBucket: %s",
266+
myS3.GetEndpoint(),
267+
myS3.GetAccessKey(),
268+
myS3.GetSecretKey(),
269+
myS3.GetBucket(),
270+
))
271+
return nil
272+
},
273+
},
274+
},
275+
},
246276
},
247277
}
248278

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package s3provider
2+
3+
type Provider interface {
4+
GetURL() string
5+
GetEndpoint() string
6+
GetConsoleURL() string
7+
GetSecretKey() string
8+
GetAccessKey() string
9+
GetBucket() string
10+
GetRegion() string
11+
}
12+
13+
type ProviderFactory interface {
14+
NewProvider(...Option) (Provider, error)
15+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package s3provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/docker/docker/api/types/container"
7+
"github.com/docker/go-connections/nat"
8+
"github.com/minio/minio-go/v7"
9+
"github.com/minio/minio-go/v7/pkg/credentials"
10+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
11+
tc "github.com/testcontainers/testcontainers-go"
12+
tcwait "github.com/testcontainers/testcontainers-go/wait"
13+
"math/rand"
14+
"strconv"
15+
)
16+
17+
const (
18+
DefaultImage = "minio/minio"
19+
DefaultName = "minio"
20+
DefaultBucket = "test-bucket"
21+
DefaultRegion = "us-east-1"
22+
DefaultPort = 9000
23+
DefaultConsolePort = 9001
24+
)
25+
26+
type Minio struct {
27+
host string
28+
port int
29+
consolePort int
30+
accessKey string
31+
secretKey string
32+
bucket string
33+
region string
34+
}
35+
36+
func (m Minio) GetSecretKey() string {
37+
return m.secretKey
38+
}
39+
40+
func (m Minio) GetAccessKey() string {
41+
return m.accessKey
42+
}
43+
44+
func (m Minio) GetBucket() string {
45+
return m.bucket
46+
}
47+
48+
func (m Minio) GetURL() string {
49+
return fmt.Sprintf("http://%s:%d", m.host, m.port)
50+
}
51+
52+
func (m Minio) GetConsoleURL() string {
53+
return fmt.Sprintf("http://%s:%d", m.host, m.consolePort)
54+
}
55+
56+
func (m Minio) GetEndpoint() string {
57+
return fmt.Sprintf("%s:%d", m.host, m.consolePort)
58+
}
59+
60+
func (m Minio) GetRegion() string {
61+
return m.region
62+
}
63+
64+
type Option func(*Minio)
65+
66+
type MinioFactory struct{}
67+
68+
func NewMinioFactory() ProviderFactory {
69+
return MinioFactory{}
70+
}
71+
72+
func (mf MinioFactory) NewProvider(options ...Option) (Provider, error) {
73+
m := &Minio{
74+
port: DefaultPort,
75+
consolePort: DefaultConsolePort,
76+
accessKey: randomStr(20),
77+
secretKey: randomStr(40),
78+
bucket: DefaultBucket,
79+
region: DefaultRegion,
80+
}
81+
82+
for _, opt := range options {
83+
opt(m)
84+
}
85+
86+
ctx := context.Background()
87+
containerName := framework.DefaultTCName(DefaultName)
88+
bindPort := fmt.Sprintf("%d/tcp", m.port)
89+
bindConsolePort := fmt.Sprintf("%d/tcp", m.consolePort)
90+
91+
req := tc.ContainerRequest{
92+
Name: containerName,
93+
Image: DefaultImage,
94+
Labels: framework.DefaultTCLabels(),
95+
Networks: []string{framework.DefaultNetworkName},
96+
NetworkAliases: map[string][]string{
97+
framework.DefaultNetworkName: {containerName},
98+
},
99+
ExposedPorts: []string{bindPort, bindConsolePort},
100+
Env: map[string]string{
101+
"MINIO_ROOT_USER": m.accessKey,
102+
"MINIO_ROOT_PASSWORD": m.secretKey,
103+
"MINIO_BUCKET": DefaultBucket,
104+
},
105+
Entrypoint: []string{
106+
"minio",
107+
"server",
108+
"data",
109+
"--address",
110+
fmt.Sprintf(":%d", m.port),
111+
"--console-address",
112+
fmt.Sprintf(":%d", m.consolePort),
113+
},
114+
HostConfigModifier: func(h *container.HostConfig) {
115+
framework.NoDNS(true, h)
116+
h.PortBindings = framework.MapTheSamePort(bindPort)
117+
},
118+
WaitingFor: tcwait.ForAll(
119+
tcwait.ForListeningPort(nat.Port(fmt.Sprintf("%d/tcp", m.port))),
120+
),
121+
}
122+
req.HostConfigModifier = func(h *container.HostConfig) {
123+
h.PortBindings = nat.PortMap{
124+
nat.Port(bindPort): []nat.PortBinding{
125+
{
126+
HostIP: "0.0.0.0",
127+
HostPort: strconv.Itoa(m.port),
128+
},
129+
},
130+
nat.Port(bindPort): []nat.PortBinding{
131+
{
132+
HostIP: "0.0.0.0",
133+
HostPort: strconv.Itoa(m.consolePort),
134+
},
135+
},
136+
}
137+
}
138+
139+
c, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
140+
ContainerRequest: req,
141+
Started: true,
142+
})
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
host, err := framework.GetHost(c)
148+
if err != nil {
149+
return nil, err
150+
}
151+
m.host = host
152+
153+
// Initialize minio client object.
154+
minioClient, err := minio.New(m.GetEndpoint(), &minio.Options{
155+
Creds: credentials.NewStaticV4(m.GetAccessKey(), m.GetSecretKey(), ""),
156+
Secure: false,
157+
})
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
// Initialize default bucket
163+
err = minioClient.MakeBucket(ctx, m.GetBucket(), minio.MakeBucketOptions{Region: m.GetRegion()})
164+
if err != nil {
165+
return nil, err
166+
}
167+
168+
return m, nil
169+
}
170+
171+
func WithPort(port int) Option {
172+
return func(m *Minio) {
173+
m.port = port
174+
}
175+
}
176+
177+
func WithConsolePort(consolePort int) Option {
178+
return func(m *Minio) {
179+
m.consolePort = consolePort
180+
}
181+
}
182+
183+
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
184+
185+
func randomStr(n int) string {
186+
b := make([]byte, n)
187+
for i := range b {
188+
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
189+
}
190+
return string(b)
191+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package s3provider
2+
3+
import (
4+
"context"
5+
"github.com/hashicorp/consul/sdk/freeport"
6+
"github.com/minio/minio-go/v7"
7+
"github.com/minio/minio-go/v7/pkg/credentials"
8+
"github.com/stretchr/testify/require"
9+
"testing"
10+
)
11+
12+
func TestNewMinioFactory(t *testing.T) {
13+
port := freeport.GetOne(t)
14+
consolePort := freeport.GetOne(t)
15+
s3provider, err := NewMinioFactory().NewProvider(WithPort(port), WithConsolePort(consolePort))
16+
require.NoError(t, err)
17+
18+
t.Logf("URL: %s", s3provider.GetURL())
19+
20+
// Initialize minio client object.
21+
minioClient, err := minio.New(s3provider.GetEndpoint(), &minio.Options{
22+
Creds: credentials.NewStaticV4(s3provider.GetAccessKey(), s3provider.GetSecretKey(), ""),
23+
Secure: false,
24+
})
25+
require.NoError(t, err)
26+
27+
// Test file upload
28+
filename := "test.txt"
29+
filePath := "./" + filename
30+
contentType := "application/octet-stream"
31+
32+
info, err := minioClient.FPutObject(
33+
context.Background(),
34+
s3provider.GetBucket(),
35+
filename,
36+
filePath,
37+
minio.PutObjectOptions{ContentType: contentType},
38+
)
39+
require.NoError(t, err)
40+
require.Equal(t, int64(7), info.Size)
41+
42+
t.Logf("successfully uploaded %s of size %d bytes\n", filename, info.Size)
43+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
example

framework/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/go-resty/resty/v2 v2.15.3
2222
github.com/google/go-github/v50 v50.2.0
2323
github.com/google/uuid v1.6.0
24+
github.com/minio/minio-go/v7 v7.0.68
2425
github.com/pelletier/go-toml v1.9.5
2526
github.com/pelletier/go-toml/v2 v2.2.3
2627
github.com/pkg/errors v0.9.1
@@ -77,6 +78,7 @@ require (
7778
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
7879
github.com/distribution/reference v0.6.0 // indirect
7980
github.com/docker/go-units v0.5.0 // indirect
81+
github.com/dustin/go-humanize v1.0.1 // indirect
8082
github.com/ebitengine/purego v0.8.2 // indirect
8183
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
8284
github.com/ethereum/go-verkle v0.2.2 // indirect
@@ -92,6 +94,7 @@ require (
9294
github.com/gogo/protobuf v1.3.2 // indirect
9395
github.com/google/go-querystring v1.1.0 // indirect
9496
github.com/gorilla/websocket v1.5.1 // indirect
97+
github.com/hashicorp/consul/sdk v0.16.2 // indirect
9598
github.com/holiman/uint256 v1.3.2 // indirect
9699
github.com/json-iterator/go v1.1.12 // indirect
97100
github.com/klauspost/compress v1.17.11 // indirect
@@ -102,6 +105,8 @@ require (
102105
github.com/mattn/go-colorable v0.1.13 // indirect
103106
github.com/mattn/go-isatty v0.0.20 // indirect
104107
github.com/mattn/go-runewidth v0.0.16 // indirect
108+
github.com/minio/md5-simd v1.1.2 // indirect
109+
github.com/minio/sha256-simd v1.0.1 // indirect
105110
github.com/mitchellh/mapstructure v1.5.0 // indirect
106111
github.com/mmcloughlin/addchain v0.4.0 // indirect
107112
github.com/moby/docker-image-spec v1.3.1 // indirect
@@ -119,6 +124,7 @@ require (
119124
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
120125
github.com/prometheus/client_golang v1.20.5 // indirect
121126
github.com/rivo/uniseg v0.4.7 // indirect
127+
github.com/rs/xid v1.5.0 // indirect
122128
github.com/russross/blackfriday/v2 v2.1.0 // indirect
123129
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
124130
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
@@ -147,6 +153,7 @@ require (
147153
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
148154
google.golang.org/grpc v1.71.0 // indirect
149155
google.golang.org/protobuf v1.36.4 // indirect
156+
gopkg.in/ini.v1 v1.67.0 // indirect
150157
gopkg.in/yaml.v3 v3.0.1 // indirect
151158
gotest.tools/v3 v3.5.2 // indirect
152159
rsc.io/tmplfunc v0.0.3 // indirect

0 commit comments

Comments
 (0)