Skip to content

Commit 29480b8

Browse files
authored
chore(etcd)!: use Run function (#3409)
* chore(etcd): use Run function * chore(etcd)!: make options return errors
1 parent a5275d9 commit 29480b8

File tree

3 files changed

+70
-55
lines changed

3 files changed

+70
-55
lines changed

modules/etcd/etcd.go

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"github.com/testcontainers/testcontainers-go"
10+
tcexec "github.com/testcontainers/testcontainers-go/exec"
1011
tcnetwork "github.com/testcontainers/testcontainers-go/network"
1112
)
1213

@@ -59,24 +60,18 @@ func (c *EtcdContainer) Terminate(ctx context.Context, opts ...testcontainers.Te
5960

6061
// Run creates an instance of the etcd container type
6162
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*EtcdContainer, error) {
62-
req := testcontainers.ContainerRequest{
63-
Image: img,
64-
ExposedPorts: []string{clientPort, peerPort},
65-
Cmd: []string{},
63+
moduleOpts := []testcontainers.ContainerCustomizer{
64+
testcontainers.WithExposedPorts(clientPort, peerPort),
6665
}
6766

68-
genericContainerReq := testcontainers.GenericContainerRequest{
69-
ContainerRequest: req,
70-
Started: true,
71-
}
67+
moduleOpts = append(moduleOpts, opts...)
7268

73-
settings := defaultOptions(&req)
69+
settings := defaultOptions()
7470
for _, opt := range opts {
7571
if apply, ok := opt.(Option); ok {
76-
apply(&settings)
77-
}
78-
if err := opt.Customize(&genericContainerReq); err != nil {
79-
return nil, err
72+
if err := apply(&settings); err != nil {
73+
return nil, fmt.Errorf("apply option: %w", err)
74+
}
8075
}
8176
}
8277

@@ -86,7 +81,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
8681
}
8782

8883
// configure CMD with the nodes
89-
genericContainerReq.Cmd = configureCMD(settings)
84+
moduleOpts = append(moduleOpts, testcontainers.WithCmd(configureCMD(settings)...))
9085

9186
// Initialise the etcd container with the current settings.
9287
// The cluster network, if needed, is already part of the settings,
@@ -96,21 +91,33 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
9691

9792
if settings.clusterNetwork != nil {
9893
// apply the network to the current node
99-
err := tcnetwork.WithNetwork([]string{settings.nodeNames[settings.currentNode]}, settings.clusterNetwork)(&genericContainerReq)
100-
if err != nil {
101-
return c, fmt.Errorf("with network: %w", err)
102-
}
94+
moduleOpts = append(moduleOpts, tcnetwork.WithNetwork([]string{settings.nodeNames[settings.currentNode]}, settings.clusterNetwork))
95+
}
96+
97+
if settings.mountDataDir {
98+
moduleOpts = append(moduleOpts, testcontainers.WithAdditionalLifecycleHooks(testcontainers.ContainerLifecycleHooks{
99+
PostStarts: []testcontainers.ContainerHook{
100+
func(ctx context.Context, c testcontainers.Container) error {
101+
_, _, err := c.Exec(ctx, []string{"chmod", "o+rwx", "-R", dataDir}, tcexec.Multiplexed())
102+
if err != nil {
103+
return fmt.Errorf("chmod etcd data dir: %w", err)
104+
}
105+
106+
return nil
107+
},
108+
},
109+
}))
103110
}
104111

105-
if c.Container, err = testcontainers.GenericContainer(ctx, genericContainerReq); err != nil {
106-
return c, fmt.Errorf("generic container: %w", err)
112+
if c.Container, err = testcontainers.Run(ctx, img, moduleOpts...); err != nil {
113+
return c, fmt.Errorf("run etcd: %w", err)
107114
}
108115

109116
// only the first node creates the cluster
110117
if settings.currentNode == 0 {
111118
for i := 1; i < len(settings.nodeNames); i++ {
112119
// move to the next node
113-
childNode, err := Run(ctx, req.Image, append(clusterOpts, withCurrentNode(i))...)
120+
childNode, err := Run(ctx, img, append(clusterOpts, withCurrentNode(i))...)
114121
if err != nil {
115122
// return the parent cluster node and the error, so the caller can clean up.
116123
return c, fmt.Errorf("run cluster node: %w", err)

modules/etcd/etcd_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ func TestRun(t *testing.T) {
3030
require.Contains(t, string(output), "default")
3131
}
3232

33+
func TestRunWithDataDir(t *testing.T) {
34+
ctx := context.Background()
35+
36+
ctr, err := etcd.Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14", etcd.WithDataDir())
37+
testcontainers.CleanupContainer(t, ctr)
38+
require.NoError(t, err)
39+
40+
c, r, err := ctr.Exec(ctx, []string{"etcdctl", "member", "list"}, tcexec.Multiplexed())
41+
require.NoError(t, err)
42+
require.Zero(t, c)
43+
44+
output, err := io.ReadAll(r)
45+
require.NoError(t, err)
46+
require.Contains(t, string(output), "default")
47+
}
48+
3349
func TestPutGet(t *testing.T) {
3450
t.Run("single_node", func(t *testing.T) {
3551
ctr, err := etcd.Run(context.Background(), "gcr.io/etcd-development/etcd:v3.5.14")

modules/etcd/options.go

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
11
package etcd
22

33
import (
4-
"context"
54
"fmt"
65

76
"github.com/testcontainers/testcontainers-go"
8-
tcexec "github.com/testcontainers/testcontainers-go/exec"
97
)
108

119
type options struct {
12-
currentNode int
13-
clusterNetwork *testcontainers.DockerNetwork
14-
nodeNames []string
15-
clusterToken string
16-
additionalArgs []string
17-
mountDataDir bool // flag needed to avoid extra calculations with the lifecycle hooks
18-
containerRequest *testcontainers.ContainerRequest
10+
currentNode int
11+
clusterNetwork *testcontainers.DockerNetwork
12+
nodeNames []string
13+
clusterToken string
14+
additionalArgs []string
15+
mountDataDir bool // flag needed to avoid extra calculations with the lifecycle hooks
1916
}
2017

21-
func defaultOptions(req *testcontainers.ContainerRequest) options {
18+
func defaultOptions() options {
2219
return options{
23-
clusterToken: defaultClusterToken,
24-
containerRequest: req,
20+
clusterToken: defaultClusterToken,
2521
}
2622
}
2723

2824
// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface.
2925
var _ testcontainers.ContainerCustomizer = (Option)(nil)
3026

3127
// Option is an option for the Etcd container.
32-
type Option func(*options)
28+
type Option func(*options) error
3329

3430
// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface.
3531
func (o Option) Customize(*testcontainers.GenericContainerRequest) error {
@@ -40,68 +36,64 @@ func (o Option) Customize(*testcontainers.GenericContainerRequest) error {
4036
// WithAdditionalArgs is an option to pass additional arguments to the etcd container.
4137
// They will be appended last to the command line.
4238
func WithAdditionalArgs(args ...string) Option {
43-
return func(o *options) {
39+
return func(o *options) error {
4440
o.additionalArgs = args
41+
return nil
4542
}
4643
}
4744

4845
// WithDataDir is an option to mount the data directory, which is located at /data.etcd.
4946
// The option will add a lifecycle hook to the container to change the permissions of the data directory.
5047
func WithDataDir() Option {
51-
return func(o *options) {
48+
return func(o *options) error {
5249
// Avoid extra calculations with the lifecycle hooks
5350
o.mountDataDir = true
54-
55-
o.containerRequest.LifecycleHooks = append(o.containerRequest.LifecycleHooks, testcontainers.ContainerLifecycleHooks{
56-
PostStarts: []testcontainers.ContainerHook{
57-
func(ctx context.Context, c testcontainers.Container) error {
58-
_, _, err := c.Exec(ctx, []string{"chmod", "o+rwx", "-R", dataDir}, tcexec.Multiplexed())
59-
if err != nil {
60-
return fmt.Errorf("chmod etcd data dir: %w", err)
61-
}
62-
63-
return nil
64-
},
65-
},
66-
})
51+
return nil
6752
}
6853
}
6954

7055
// WithNodes is an option to set the nodes of the etcd cluster.
7156
// It should be used to create a cluster with more than one node.
7257
func WithNodes(node1 string, node2 string, nodes ...string) Option {
73-
return func(o *options) {
58+
return func(o *options) error {
7459
o.nodeNames = append([]string{node1, node2}, nodes...)
60+
return nil
7561
}
7662
}
7763

7864
// withCurrentNode is an option to set the current node index.
7965
// It's an internal option and should not be used by the user.
8066
func withCurrentNode(i int) Option {
81-
return func(o *options) {
67+
return func(o *options) error {
8268
o.currentNode = i
69+
return nil
8370
}
8471
}
8572

8673
// withClusterNetwork is an option to set the cluster network.
8774
// It's an internal option and should not be used by the user.
8875
func withClusterNetwork(n *testcontainers.DockerNetwork) Option {
89-
return func(o *options) {
76+
return func(o *options) error {
9077
o.clusterNetwork = n
78+
return nil
9179
}
9280
}
9381

9482
// WithClusterToken is an option to set the cluster token.
9583
func WithClusterToken(token string) Option {
96-
return func(o *options) {
84+
return func(o *options) error {
9785
o.clusterToken = token
86+
return nil
9887
}
9988
}
10089

10190
func withClusterOptions(opts []Option) Option {
102-
return func(o *options) {
91+
return func(o *options) error {
10392
for _, opt := range opts {
104-
opt(o)
93+
if err := opt(o); err != nil {
94+
return fmt.Errorf("with cluster options: %w", err)
95+
}
10596
}
97+
return nil
10698
}
10799
}

0 commit comments

Comments
 (0)