Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
56 changes: 56 additions & 0 deletions docs/modules/aerospike.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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" %}

## Examples

### Aerospike Client

<!--codeinclude-->
[Aerospike Client](../../modules/aerospike/examples_test.go) inside_block:usingClient
<!--/codeinclude-->
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
}
57 changes: 57 additions & 0 deletions modules/aerospike/aerospike_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package aerospike_test

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go"
tcaerospike "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("invalid-image-fails", func(t *testing.T) {
ctx := context.Background()
_, err := tcaerospike.Run(ctx, "invalid-aerospike-image")
require.Error(t, err)
require.Contains(t, err.Error(), "failed to start Aerospike container")
})

t.Run("valid-image-succeeds", func(t *testing.T) {
ctx := context.Background()
container, err := tcaerospike.Run(ctx, aerospikeImage)
require.NoError(t, err)
require.NotNil(t, container)

testcontainers.CleanupContainer(t, container)
})

t.Run("applies-container-customizations", func(t *testing.T) {
ctx := context.Background()
customEnv := "TEST_ENV=value"
container, err := tcaerospike.Run(ctx, aerospikeImage,
testcontainers.WithEnv(map[string]string{"CUSTOM_ENV": customEnv}))
require.NoError(t, err)
require.NotNil(t, container)
testcontainers.CleanupContainer(t, container)
})

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

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

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

aero "github.com/aerospike/aerospike-client-go/v8"

"github.com/testcontainers/testcontainers-go"
tcaerospike "github.com/testcontainers/testcontainers-go/modules/aerospike"
)

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

aerospikedbContainer, err := tcaerospike.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 := tcaerospike.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 := []*aero.Host{aero.NewHost(host, port.Int())}

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

// Create a client
client, err := aero.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 := aero.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 := aero.BinMap{
"version": version,
"created_at": nowStr,
"updated_at": nowStr,
"description": description,
}

// Never expire the schema info
writePolicy := aero.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