Skip to content

Commit

Permalink
feat: add etcd module (#2788)
Browse files Browse the repository at this point in the history
* chore: scaffolding for etcd module

* feat: add etcd with cluster support

* feat: support for AutoTLS

* chore: add unit test for cmd

* fix: remove non-existent module

* chore: simplify removing auto-tls support

* docs: remove old deprecated function

* docs: document functions

* docs: remove commented code

* chore: more readable tests

* chore: move logic to the functional option

* chore: simplify passing current node opts

* fix: return the parent node when creating the child nodes

* chore: move to constant

* chore: use zero values

* fix: typo after wrong copy&paste

* chore: remove must methods

* chore: use private fields in the container

* chore: use private fields in the options struct

* chore: simplify public API

* chore: make constants private

* fix: lint

* chore: rename field to childNodes

* docs: enrich container comments

* fix: assert on the right node

* chore: bump dockercfg

* feat: implement termination of the cluster

* chore: use require.Zero

* chore: proper test name

* chore: preallocate slice

* chore: do not allow single-node clusters

* fix: join errors on terminate cluster

* fix: check parent container is terminated

* chore: extract to function

* fix: check if container is not nil

* chor: do not orphan the network

* chore: simplify

* docs: fix comment

* chore: wrap errors in endpoint functions

* Revert "chore: wrap errors in endpoint functions"

This reverts commit 1c4361b.

* chore: simplify eval

* fix: not needed

* chore: simplify container initialisation

* chore: simplify even more
  • Loading branch information
mdelapenya authored Sep 30, 2024
1 parent 7a1419d commit f954f6d
Show file tree
Hide file tree
Showing 14 changed files with 1,155 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
matrix:
go-version: [1.22.x, 1.x]
platform: [ubuntu-latest]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, dynamodb, elasticsearch, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, dynamodb, elasticsearch, etcd, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate]
uses: ./.github/workflows/ci-test-go.yml
with:
go-version: ${{ matrix.go-version }}
Expand Down
4 changes: 4 additions & 0 deletions .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
"name": "module / elasticsearch",
"path": "../modules/elasticsearch"
},
{
"name": "module / etcd",
"path": "../modules/etcd"
},
{
"name": "module / gcloud",
"path": "../modules/gcloud"
Expand Down
95 changes: 95 additions & 0 deletions docs/modules/etcd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# etcd

Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

## Introduction

The Testcontainers module for etcd.

## Adding this module to your project dependencies

Please run the following command to add the etcd module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/etcd
```

## Usage example

<!--codeinclude-->
[Creating a etcd container](../../modules/etcd/examples_test.go) inside_block:runetcdContainer
<!--/codeinclude-->

## Module Reference

### Run function

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

The etcd module exposes one entrypoint function to create the etcd container, and this function receives three parameters:

```golang
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*etcdContainer, error)
```

- `context.Context`, the Go context.
- `string`, the Docker image to use.
- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.

### Container Options

When starting the etcd container, you can pass options in a variadic way to configure it.

#### Image

If you need to set a different etcd Docker image, you can set a valid Docker image as the second argument in the `Run` function.
E.g. `Run(context.Background(), "bitnami/etcd:latest")`.

{% include "../features/common_functional_options.md" %}

#### WithAdditionalArgs

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

You can pass additional arguments to the etcd container by using the `WithAdditionalArgs` option. The arguments are passed to the CMD of the etcd container.

#### WithDataDir

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

You can set the data directory for the etcd container by using the `WithDataDir` boolean option. The data directory where the etcd data is stored is `/data.etcd`.

#### WithNodes

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

You can set the number of nodes for the etcd cluster by using the `WithNodes` option, passing the node names for each of the nodes. Single-node clusters are not allowed,
for that reason the functional option receives three string arguments: the first node, the second node, and a variadic argument for the rest of the nodes.
The module starts a container for each node, having the first node a reference to the other nodes. E.g. `WithNodes("etcd-1", "etcd-2")`, `WithNodes("etcd-1", "etcd-2", "etcd-3")` and so on.

The module creates a Docker network for the etcd cluster, and the nodes are connected to this network, so that they can communicate with each other through the network.

#### WithClusterToken

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

Sets the cluster token for the etcd cluster. The cluster token is used to identify the etcd cluster. The default value is `mys3cr3ttok3n`.
The etcd container holds a reference to the cluster token, so you can use it with e.g. `ctr.ClusterToken`.

### Container Methods

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

The etcd container exposes the following methods:

#### ClientEndpoint

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

Returns the client endpoint for the etcd container and an error, if any. In the case of a cluster, it returns the client endpoint for the first node.

#### PeerEndpoint

- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

Returns the peer endpoint for the etcd container and an error, if any. In the case of a cluster, it returns the peer endpoint for the first node.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ nav:
- modules/dolt.md
- modules/dynamodb.md
- modules/elasticsearch.md
- modules/etcd.md
- modules/gcloud.md
- modules/grafana-lgtm.md
- modules/inbucket.md
Expand Down
5 changes: 5 additions & 0 deletions modules/etcd/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include ../../commons-test.mk

.PHONY: test
test:
$(MAKE) test-etcd
108 changes: 108 additions & 0 deletions modules/etcd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package etcd

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_configureCMD(t *testing.T) {
t.Run("default", func(t *testing.T) {
got := configureCMD(options{})
want := []string{"etcd", "--name=default"}
require.Equal(t, want, got)
})

t.Run("with-node", func(t *testing.T) {
got := configureCMD(options{
nodeNames: []string{"node1"},
})
want := []string{
"etcd",
"--name=node1",
"--initial-advertise-peer-urls=http://node1:2380",
"--advertise-client-urls=http://node1:2379",
"--listen-peer-urls=http://0.0.0.0:2380",
"--listen-client-urls=http://0.0.0.0:2379",
"--initial-cluster-state=new",
"--initial-cluster=node1=http://node1:2380",
}
require.Equal(t, want, got)
})

t.Run("with-node-datadir", func(t *testing.T) {
got := configureCMD(options{
nodeNames: []string{"node1"},
mountDataDir: true,
})
want := []string{
"etcd",
"--name=node1",
"--initial-advertise-peer-urls=http://node1:2380",
"--advertise-client-urls=http://node1:2379",
"--listen-peer-urls=http://0.0.0.0:2380",
"--listen-client-urls=http://0.0.0.0:2379",
"--initial-cluster-state=new",
"--initial-cluster=node1=http://node1:2380",
"--data-dir=/data.etcd",
}
require.Equal(t, want, got)
})

t.Run("with-node-datadir-additional-args", func(t *testing.T) {
got := configureCMD(options{
nodeNames: []string{"node1"},
mountDataDir: true,
additionalArgs: []string{"--auto-compaction-retention=1"},
})
want := []string{
"etcd",
"--name=node1",
"--initial-advertise-peer-urls=http://node1:2380",
"--advertise-client-urls=http://node1:2379",
"--listen-peer-urls=http://0.0.0.0:2380",
"--listen-client-urls=http://0.0.0.0:2379",
"--initial-cluster-state=new",
"--initial-cluster=node1=http://node1:2380",
"--data-dir=/data.etcd",
"--auto-compaction-retention=1",
}
require.Equal(t, want, got)
})

t.Run("with-cluster", func(t *testing.T) {
got := configureCMD(options{
nodeNames: []string{"node1", "node2"},
})
want := []string{
"etcd",
"--name=node1",
"--initial-advertise-peer-urls=http://node1:2380",
"--advertise-client-urls=http://node1:2379",
"--listen-peer-urls=http://0.0.0.0:2380",
"--listen-client-urls=http://0.0.0.0:2379",
"--initial-cluster-state=new",
"--initial-cluster=node1=http://node1:2380,node2=http://node2:2380",
}
require.Equal(t, want, got)
})

t.Run("with-cluster-token", func(t *testing.T) {
got := configureCMD(options{
nodeNames: []string{"node1", "node2"},
clusterToken: "token",
})
want := []string{
"etcd",
"--name=node1",
"--initial-advertise-peer-urls=http://node1:2380",
"--advertise-client-urls=http://node1:2379",
"--listen-peer-urls=http://0.0.0.0:2380",
"--listen-client-urls=http://0.0.0.0:2379",
"--initial-cluster-state=new",
"--initial-cluster=node1=http://node1:2380,node2=http://node2:2380",
"--initial-cluster-token=token",
}
require.Equal(t, want, got)
})
}
Loading

0 comments on commit f954f6d

Please sign in to comment.