Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ updates:
- /examples/nginx
- /examples/toxiproxy
- /modulegen
- /modules/aerospike
- /modules/arangodb
- /modules/artemis
- /modules/azure
Expand Down
48 changes: 48 additions & 0 deletions docs/modules/aerospike.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Aerospike

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 Aerospike.

## Adding this module to your project dependencies

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

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

## Usage example

<!--codeinclude-->
[Creating a Aerospike container](../../modules/aerospike/examples_test.go) inside_block:runAerospikeContainer
<!--/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 Aerospike module exposes one entrypoint function to create the Aerospike container, and this function receives three parameters:

```golang
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*AerospikeContainer, 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 Aerospike container, you can pass options in a variadic way to configure it.

#### Image

Use the second argument in the `Run` function to set a valid Docker image.
In example: `Run(context.Background(), "aerospike/aerospike-server:latest")`.

{% include "../features/common_functional_options.md" %}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ nav:
- Walk: features/wait/walk.md
- Modules:
- modules/index.md
- modules/aerospike.md
- modules/arangodb.md
- modules/artemis.md
- modules/azure.md
Expand Down
5 changes: 5 additions & 0 deletions modules/aerospike/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include ../../commons-test.mk

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

import (
"context"
"fmt"
"time"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

const (
// port is the port used for client connections
port = "3000/tcp"
// fabricPort is the port used for Intra-cluster communication port.
// Replica writes, migrations, and other node-to-node communications use the Fabric port.
fabricPort = "3001/tcp"
// heartbeatPort is the port used for heartbeat communication
// between nodes in the Aerospike cluster
heartbeatPort = "3002/tcp"
// infoPort is the port used for info commands
infoPort = "3003/tcp"
)

// Container is the Aerospike container type used in the module
type Container struct {
testcontainers.Container
}

// Run creates an instance of the Aerospike container type
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
req := testcontainers.ContainerRequest{
Image: img,
ExposedPorts: []string{port, fabricPort, heartbeatPort, infoPort},
Env: map[string]string{
"AEROSPIKE_CONFIG_FILE": "/etc/aerospike/aerospike.conf",
},
WaitingFor: wait.ForAll(
wait.ForLog("migrations: complete"),
wait.ForListeningPort(port).WithStartupTimeout(10*time.Second),
wait.ForListeningPort(fabricPort).WithStartupTimeout(10*time.Second),
wait.ForListeningPort(heartbeatPort).WithStartupTimeout(10*time.Second),
),
}

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

for _, opt := range opts {
if err := opt.Customize(&genericContainerReq); err != nil {
return nil, fmt.Errorf("customize: %w", err)
}
}

container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
var c *Container
if container != nil {
c = &Container{Container: container}
}

if err != nil {
return c, fmt.Errorf("generic container: %w", err)
}

return c, nil
}
64 changes: 64 additions & 0 deletions modules/aerospike/aerospike_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package aerospike_test

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
as "github.com/testcontainers/testcontainers-go/modules/aerospike"
)

const (
aerospikeImage = "aerospike/aerospike-server:latest"
)

// TestAerospike tests the Aerospike container functionality
// It includes tests for starting the container with a valid image,
// applying container customizations, and handling context cancellation.
// It also includes a test for an invalid image to ensure proper error handling.
// The tests use the testcontainers-go library to manage container lifecycle
func TestAeroSpike(t *testing.T) {
t.Run("fails_with_invalid_image", func(t *testing.T) {
ctx := context.Background()
_, err := as.Run(ctx, "invalid-aerospike-image")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to start Aerospike container")
})

t.Run("succeeds_with_valid_image", func(t *testing.T) {
ctx := context.Background()
container, err := as.Run(ctx, aerospikeImage)
require.NoError(t, err)
require.NotNil(t, container)
defer container.Container.Terminate(ctx)

host, err := container.Host(ctx)
require.NoError(t, err)

port, err := container.MappedPort(ctx, "3000/tcp")
require.NoError(t, err)

require.NotEmpty(t, host)
require.NotEmpty(t, port)
})

t.Run("applies_container_customizations", func(t *testing.T) {
ctx := context.Background()
customEnv := "TEST_ENV=value"
container, err := as.Run(ctx, aerospikeImage,
testcontainers.WithEnv(map[string]string{"CUSTOM_ENV": customEnv}))
require.NoError(t, err)
require.NotNil(t, container)
defer container.Container.Terminate(ctx)
})

t.Run("respects_context_cancellation", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()

_, err := as.Run(ctx, aerospikeImage)
require.Error(t, err)
})
}
134 changes: 134 additions & 0 deletions modules/aerospike/examples_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package aerospike_test

import (
"context"
"fmt"
"log"
"time"

"github.com/aerospike/aerospike-client-go/v8"
"github.com/testcontainers/testcontainers-go"
as "github.com/testcontainers/testcontainers-go/modules/aerospike"
)

func ExampleRun() {
// runAerospikeContainer {
ctx := context.Background()

aerospikedbContainer, err := as.Run(ctx, "aerospike/aerospike-server:latest")
defer func() {
if err := testcontainers.TerminateContainer(aerospikedbContainer.Container); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()
if err != nil {
log.Printf("failed to start container: %s", err)
return
}
// }
state, err := aerospikedbContainer.State(ctx)
if err != nil {
log.Printf("failed to get container state: %s", err)
return
}

fmt.Println(state.Running)

// Output:
// true
}

func ExampleRun_usingClient() {
ctx := context.Background()

aerospikedbContainer, err := as.Run(
ctx, "aerospike/aerospike-server:latest",
)
defer func() {
if err := testcontainers.TerminateContainer(aerospikedbContainer.Container); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()
if err != nil {
log.Printf("failed to start container: %s", err)
return
}
// }

// Get the host and port
host, err := aerospikedbContainer.Host(ctx)
if err != nil {
log.Printf("failed to get container host: %s", err)
return
}

// Get the mapped port
port, err := aerospikedbContainer.MappedPort(ctx, "3000/tcp")
if err != nil {
log.Printf("failed to get container port: %s", err)
return
}

aeroHost := []*aerospike.Host{aerospike.NewHost(host, port.Int())}

// connect to the host
cp := aerospike.NewClientPolicy()
cp.Timeout = 10 * time.Second

// Create a client
client, err := aerospike.NewClientWithPolicyAndHost(cp, aeroHost...)
if err != nil {
log.Printf("Failed to create aerospike client: %v", err)
return
}

// Close the client
defer client.Close()

// Create a key
schemaKey, err := aerospike.NewKey("test", "test", "_schema_info")
if err != nil {
log.Printf("Failed to create key: %v", err)
return
}

version := 1
description := "test aerospike schema info"
nowStr := time.Now().Format(time.RFC3339)

// Create schema record
bins := aerospike.BinMap{
"version": version,
"created_at": nowStr,
"updated_at": nowStr,
"description": description,
}

// Never expire the schema info
writePolicy := aerospike.NewWritePolicy(0, 0)

// Store in Aerospike
err = client.Put(writePolicy, schemaKey, bins)
if err != nil {
log.Printf("Failed to put schema info: %v", err)
return
}

// Get schema record
record, err := client.Get(nil, schemaKey, "version", "created_at", "updated_at", "description")
if err != nil {
log.Printf("Failed to get schema info: %v", err)
return
}

// Schema exists, check version
existingVersion, _ := record.Bins["version"].(int)
existingDescription, _ := record.Bins["description"].(string)

fmt.Println(existingVersion)
fmt.Println(existingDescription)

// Output:
// 1
// test aerospike schema info
}
Loading
Loading