Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:01:35 +08:00
commit 7d21263064
13 changed files with 3073 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "testcontainers-go",
"description": "A comprehensive guide for using Testcontainers for Go to write reliable integration tests with Docker containers in Go projects. Supports 62+ pre-configured modules for databases, message queues, cloud services, and more.",
"version": "1.0.0",
"author": {
"name": "Testcontainers",
"email": "info@testcontainers.org"
},
"skills": [
"./"
]
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Testcontainers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# testcontainers-go
A comprehensive guide for using Testcontainers for Go to write reliable integration tests with Docker containers in Go projects. Supports 62+ pre-configured modules for databases, message queues, cloud services, and more.

1253
SKILL.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
package examples_test
import (
"context"
"database/sql"
"testing"
_ "github.com/lib/pq"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
)
// TestBasicPostgres demonstrates the most basic usage of the PostgreSQL module
func TestBasicPostgres(t *testing.T) {
ctx := context.Background()
// Start PostgreSQL container with default settings
pgContainer, err := postgres.Run(ctx, "postgres:16-alpine", postgres.BasicWaitStrategies())
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
// Get connection string
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
// Connect to database
db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Verify connection
err = db.Ping()
require.NoError(t, err)
// Run a simple query
var result int
err = db.QueryRow("SELECT 1 + 1").Scan(&result)
require.NoError(t, err)
require.Equal(t, 2, result)
t.Log("Successfully connected to PostgreSQL and ran a query")
}
// TestPostgresWithCustomConfig demonstrates using custom database, user, and password
func TestPostgresWithCustomConfig(t *testing.T) {
ctx := context.Background()
// Start PostgreSQL with custom configuration
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("testdb"),
postgres.WithUsername("testuser"),
postgres.WithPassword("testpass"),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
// Get connection string
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
// Verify the connection string contains our custom values
require.Contains(t, connStr, "testuser")
require.Contains(t, connStr, "testpass")
require.Contains(t, connStr, "testdb")
// Connect and verify
db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
err = db.Ping()
require.NoError(t, err)
t.Log("Successfully connected to PostgreSQL with custom configuration")
}
// TestPostgresWithSchema demonstrates using init scripts to set up a schema
func TestPostgresWithSchema(t *testing.T) {
ctx := context.Background()
// Note: In a real test, you would create a schema.sql file in testdata/
// For this example, we'll use WithDatabase and create the table manually
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("appdb"),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Create a simple schema
_, err = db.Exec(`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
require.NoError(t, err)
// Insert a test record
_, err = db.Exec(`INSERT INTO users (name, email) VALUES ($1, $2)`, "Alice", "alice@example.com")
require.NoError(t, err)
// Query the record
var name, email string
err = db.QueryRow(`SELECT name, email FROM users WHERE email = $1`, "alice@example.com").Scan(&name, &email)
require.NoError(t, err)
require.Equal(t, "Alice", name)
require.Equal(t, "alice@example.com", email)
t.Log("Successfully created schema and inserted data")
}

View File

@@ -0,0 +1,181 @@
package examples_test
import (
"context"
"database/sql"
"testing"
_ "github.com/lib/pq"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
)
// TestPostgresSnapshot demonstrates using snapshots for test isolation
// This is useful when you want to run multiple tests against the same initial state
func TestPostgresSnapshot(t *testing.T) {
ctx := context.Background()
// Start PostgreSQL container with a custom database (required for snapshots)
// Note: Cannot snapshot the default 'postgres' system database
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("snapshotdb"),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Create initial schema and data
_, err = db.Exec(`
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10, 2) NOT NULL
)
`)
require.NoError(t, err)
_, err = db.Exec(`INSERT INTO products (name, price) VALUES ($1, $2)`, "Widget", 9.99)
require.NoError(t, err)
// Close connection before snapshot (PostgreSQL can't snapshot a database with active connections)
db.Close()
// Take a snapshot of the initial state
err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("initial_state"))
require.NoError(t, err)
t.Log("Snapshot created with initial state")
// Reconnect to modify the database
db, err = sql.Open("postgres", connStr)
require.NoError(t, err)
// Modify the database
_, err = db.Exec(`INSERT INTO products (name, price) VALUES ($1, $2)`, "Gadget", 19.99)
require.NoError(t, err)
// Verify we have 2 products
var count int
err = db.QueryRow(`SELECT COUNT(*) FROM products`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 2, count)
t.Log("Added second product, count is now 2")
// Close connection before restore
db.Close()
// Restore to the snapshot
err = pgContainer.Restore(ctx, postgres.WithSnapshotName("initial_state"))
require.NoError(t, err)
t.Log("Restored to initial snapshot")
// Reconnect after restore
db, err = sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Verify we're back to 1 product
err = db.QueryRow(`SELECT COUNT(*) FROM products`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 1, count, "After restore, should have only 1 product")
// Verify it's the original product
var name string
var price float64
err = db.QueryRow(`SELECT name, price FROM products WHERE id = 1`).Scan(&name, &price)
require.NoError(t, err)
require.Equal(t, "Widget", name)
require.Equal(t, 9.99, price)
t.Log("Successfully restored to initial state")
}
// TestPostgresMultipleSnapshots demonstrates using multiple named snapshots
func TestPostgresMultipleSnapshots(t *testing.T) {
ctx := context.Background()
// Use a custom database name (not 'postgres') for snapshots to work properly
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("testdb"),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Create table
_, err = db.Exec(`CREATE TABLE counters (id INT PRIMARY KEY, value INT)`)
require.NoError(t, err)
// State 1: Empty table
// Close connection before snapshot (PostgreSQL can't snapshot a database with active connections)
db.Close()
err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("empty"))
require.NoError(t, err)
t.Log("Snapshot 'empty' created")
// Reconnect to make changes
db, err = sql.Open("postgres", connStr)
require.NoError(t, err)
// State 2: One record
_, err = db.Exec(`INSERT INTO counters (id, value) VALUES (1, 10)`)
require.NoError(t, err)
// Close and snapshot
db.Close()
err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("one_record"))
require.NoError(t, err)
t.Log("Snapshot 'one_record' created")
// Reconnect to make changes
db, err = sql.Open("postgres", connStr)
require.NoError(t, err)
// State 3: Two records
_, err = db.Exec(`INSERT INTO counters (id, value) VALUES (2, 20)`)
require.NoError(t, err)
// Close and snapshot
db.Close()
err = pgContainer.Snapshot(ctx, postgres.WithSnapshotName("two_records"))
require.NoError(t, err)
t.Log("Snapshot 'two_records' created")
// Now restore to "one_record" state
err = pgContainer.Restore(ctx, postgres.WithSnapshotName("one_record"))
require.NoError(t, err)
// Reconnect after restore
db, err = sql.Open("postgres", connStr)
require.NoError(t, err)
defer db.Close()
// Verify we have exactly 1 record
var count int
err = db.QueryRow(`SELECT COUNT(*) FROM counters`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 1, count, "After restoring to 'one_record', should have 1 record")
t.Log("Successfully restored to 'one_record' snapshot")
}

View File

@@ -0,0 +1,191 @@
package examples_test
import (
"context"
"testing"
"time"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
)
// TestBasicRedis demonstrates basic Redis operations
func TestBasicRedis(t *testing.T) {
ctx := context.Background()
// Start Redis container
redisContainer, err := tcredis.Run(ctx, "redis:7-alpine")
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
// Get connection string
connStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
// Connect to Redis
opt, err := redis.ParseURL(connStr)
require.NoError(t, err)
client := redis.NewClient(opt)
defer client.Close()
// Test SET and GET
err = client.Set(ctx, "greeting", "Hello, Testcontainers!", 0).Err()
require.NoError(t, err)
val, err := client.Get(ctx, "greeting").Result()
require.NoError(t, err)
require.Equal(t, "Hello, Testcontainers!", val)
t.Log("Successfully performed SET and GET operations")
}
// TestRedisWithExpiration demonstrates key expiration
func TestRedisWithExpiration(t *testing.T) {
ctx := context.Background()
redisContainer, err := tcredis.Run(ctx, "redis:7-alpine")
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
connStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
opt, err := redis.ParseURL(connStr)
require.NoError(t, err)
client := redis.NewClient(opt)
defer client.Close()
// Set a key with 2-second expiration
err = client.Set(ctx, "temporary", "I will expire", 2*time.Second).Err()
require.NoError(t, err)
// Verify key exists
val, err := client.Get(ctx, "temporary").Result()
require.NoError(t, err)
require.Equal(t, "I will expire", val)
t.Log("Key set with 2-second expiration")
// Wait for expiration
time.Sleep(3 * time.Second)
// Verify key is gone
_, err = client.Get(ctx, "temporary").Result()
require.Equal(t, redis.Nil, err, "Key should have expired")
t.Log("Key successfully expired")
}
// TestRedisListOperations demonstrates list operations
func TestRedisListOperations(t *testing.T) {
ctx := context.Background()
redisContainer, err := tcredis.Run(ctx, "redis:7-alpine")
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
connStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
opt, err := redis.ParseURL(connStr)
require.NoError(t, err)
client := redis.NewClient(opt)
defer client.Close()
// Push items to list
err = client.RPush(ctx, "tasks", "task1", "task2", "task3").Err()
require.NoError(t, err)
// Get list length
length, err := client.LLen(ctx, "tasks").Result()
require.NoError(t, err)
require.Equal(t, int64(3), length)
// Pop items from list
task, err := client.LPop(ctx, "tasks").Result()
require.NoError(t, err)
require.Equal(t, "task1", task)
// Get remaining items
tasks, err := client.LRange(ctx, "tasks", 0, -1).Result()
require.NoError(t, err)
require.Equal(t, []string{"task2", "task3"}, tasks)
t.Log("Successfully performed list operations")
}
// TestRedisHashOperations demonstrates hash operations
func TestRedisHashOperations(t *testing.T) {
ctx := context.Background()
redisContainer, err := tcredis.Run(ctx, "redis:7-alpine")
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
connStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
opt, err := redis.ParseURL(connStr)
require.NoError(t, err)
client := redis.NewClient(opt)
defer client.Close()
// Set hash fields
err = client.HSet(ctx, "user:1000", map[string]interface{}{
"name": "John Doe",
"email": "john@example.com",
"age": "30",
}).Err()
require.NoError(t, err)
// Get single field
name, err := client.HGet(ctx, "user:1000", "name").Result()
require.NoError(t, err)
require.Equal(t, "John Doe", name)
// Get all fields
user, err := client.HGetAll(ctx, "user:1000").Result()
require.NoError(t, err)
require.Equal(t, "John Doe", user["name"])
require.Equal(t, "john@example.com", user["email"])
require.Equal(t, "30", user["age"])
t.Log("Successfully performed hash operations")
}
// TestRedisWithConfig demonstrates using Redis with custom configuration
func TestRedisWithConfig(t *testing.T) {
ctx := context.Background()
// Start Redis with snapshotting and verbose logging
redisContainer, err := tcredis.Run(
ctx,
"redis:7-alpine",
tcredis.WithSnapshotting(10, 1), // Save after 1 key changes within 10 seconds
tcredis.WithLogLevel(tcredis.LogLevelVerbose),
)
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
connStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
opt, err := redis.ParseURL(connStr)
require.NoError(t, err)
client := redis.NewClient(opt)
defer client.Close()
// Verify Redis is running
pong, err := client.Ping(ctx).Result()
require.NoError(t, err)
require.Equal(t, "PONG", pong)
t.Log("Redis running with custom configuration")
}

View File

@@ -0,0 +1,265 @@
package examples_test
import (
"context"
"database/sql"
"io"
"testing"
"time"
_ "github.com/lib/pq"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/modules/postgres"
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
"github.com/testcontainers/testcontainers-go/network"
)
// TestMultiContainerNetwork demonstrates connecting multiple containers on a custom network
func TestMultiContainerNetwork(t *testing.T) {
ctx := context.Background()
// Create a custom network
nw, err := network.New(ctx)
testcontainers.CleanupNetwork(t, nw)
require.NoError(t, err)
t.Log("Custom network created")
// Start PostgreSQL on the network with alias "database"
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("appdb"),
network.WithNetwork([]string{"database"}, nw),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
t.Log("PostgreSQL started with network alias 'database'")
// Start Redis on the same network with alias "cache"
redisContainer, err := tcredis.Run(
ctx,
"redis:7-alpine",
network.WithNetwork([]string{"cache"}, nw),
)
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
t.Log("Redis started with network alias 'cache'")
// Connect to PostgreSQL from host
pgConnStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sql.Open("postgres", pgConnStr)
require.NoError(t, err)
defer db.Close()
err = db.Ping()
require.NoError(t, err)
t.Log("Successfully connected to PostgreSQL from host")
// Connect to Redis from host
redisConnStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
redisOpt, err := redis.ParseURL(redisConnStr)
require.NoError(t, err)
redisClient := redis.NewClient(redisOpt)
defer redisClient.Close()
pong, err := redisClient.Ping(ctx).Result()
require.NoError(t, err)
require.Equal(t, "PONG", pong)
t.Log("Successfully connected to Redis from host")
// Demonstrate that containers can resolve each other by alias
// We'll use exec to ping from postgres to redis (if tools were available)
// In a real scenario, your application container would connect using these aliases
t.Log("Both containers are on the same network and can communicate")
t.Log("An application container could connect to:")
t.Log(" - PostgreSQL at: database:5432")
t.Log(" - Redis at: cache:6379")
}
// TestApplicationWithDependencies simulates an application container that depends on database and cache
func TestApplicationWithDependencies(t *testing.T) {
ctx := context.Background()
// Create network
nw, err := network.New(ctx)
testcontainers.CleanupNetwork(t, nw)
require.NoError(t, err)
// Start PostgreSQL
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
postgres.WithDatabase("myapp"),
postgres.WithUsername("appuser"),
postgres.WithPassword("apppass"),
network.WithNetwork([]string{"postgres"}, nw),
postgres.BasicWaitStrategies(),
)
testcontainers.CleanupContainer(t, pgContainer)
require.NoError(t, err)
// Start Redis
redisContainer, err := tcredis.Run(
ctx,
"redis:7-alpine",
network.WithNetwork([]string{"redis"}, nw),
)
testcontainers.CleanupContainer(t, redisContainer)
require.NoError(t, err)
// In a real test, you would start your application container here with environment variables:
// appContainer, err := testcontainers.Run(
// ctx,
// "myapp:latest",
// testcontainers.WithEnv(map[string]string{
// "DATABASE_URL": "postgres://appuser:apppass@postgres:5432/myapp?sslmode=disable",
// "REDIS_URL": "redis://redis:6379",
// }),
// network.WithNetwork([]string{"app"}, nw),
// testcontainers.WithWaitStrategy(
// wait.ForHTTP("/health").WithPort("8080/tcp"),
// ),
// )
// For this example, we'll verify the dependencies are ready
pgConnStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
db, err := sql.Open("postgres", pgConnStr)
require.NoError(t, err)
defer db.Close()
err = db.Ping()
require.NoError(t, err)
redisConnStr, err := redisContainer.ConnectionString(ctx)
require.NoError(t, err)
redisOpt, err := redis.ParseURL(redisConnStr)
require.NoError(t, err)
redisClient := redis.NewClient(redisOpt)
defer redisClient.Close()
_, err = redisClient.Ping(ctx).Result()
require.NoError(t, err)
t.Log("All dependencies are ready for application container")
}
// TestContainerCommunication demonstrates how to verify containers can communicate
func TestContainerCommunication(t *testing.T) {
ctx := context.Background()
// Create network
nw, err := network.New(ctx)
testcontainers.CleanupNetwork(t, nw)
require.NoError(t, err)
// Start two alpine containers for testing communication
alpine1, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd("sleep", "300"),
network.WithNetwork([]string{"host1"}, nw),
)
testcontainers.CleanupContainer(t, alpine1)
require.NoError(t, err)
alpine2, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd("sleep", "300"),
network.WithNetwork([]string{"host2"}, nw),
)
testcontainers.CleanupContainer(t, alpine2)
require.NoError(t, err)
// Test connectivity by pinging host2 (ping is available in alpine by default)
exitCode, reader, err := alpine1.Exec(ctx, []string{"ping", "-c", "1", "host2"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode, "Should be able to ping host2 from host1")
// Read output to verify ping succeeded
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.Contains(t, string(output), "1 packets transmitted, 1 packets received")
t.Log("Containers can successfully communicate over custom network")
}
// TestWaitForMultipleContainers demonstrates starting multiple containers and waiting for all to be ready
func TestWaitForMultipleContainers(t *testing.T) {
ctx := context.Background()
nw, err := network.New(ctx)
testcontainers.CleanupNetwork(t, nw)
require.NoError(t, err)
// Start containers concurrently (they'll wait for their respective services)
type containerResult struct {
name string
err error
}
results := make(chan containerResult, 2)
// Start PostgreSQL
go func() {
pgContainer, err := postgres.Run(
ctx,
"postgres:16-alpine",
network.WithNetwork([]string{"db"}, nw),
postgres.BasicWaitStrategies(),
)
if err == nil {
testcontainers.CleanupContainer(t, pgContainer)
}
results <- containerResult{name: "postgres", err: err}
}()
// Start Redis
go func() {
redisContainer, err := tcredis.Run(
ctx,
"redis:7-alpine",
network.WithNetwork([]string{"cache"}, nw),
)
if err == nil {
testcontainers.CleanupContainer(t, redisContainer)
}
results <- containerResult{name: "redis", err: err}
}()
// Wait for both to be ready
timeout := time.After(60 * time.Second)
readyCount := 0
for readyCount < 2 {
select {
case result := <-results:
require.NoError(t, result.err, "Failed to start %s", result.name)
t.Logf("%s is ready", result.name)
readyCount++
case <-timeout:
t.Fatal("Timeout waiting for containers to be ready")
}
}
t.Log("All containers started successfully")
}

View File

@@ -0,0 +1,382 @@
package examples_test
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/wait"
)
// TestGenericNginx demonstrates using a generic container with nginx
func TestGenericNginx(t *testing.T) {
ctx := context.Background()
// Start nginx container
nginxContainer, err := testcontainers.Run(
ctx,
"nginx:alpine",
testcontainers.WithExposedPorts("80/tcp"),
testcontainers.WithWaitStrategy(
wait.ForListeningPort("80/tcp").WithStartupTimeout(30*time.Second),
),
)
testcontainers.CleanupContainer(t, nginxContainer)
require.NoError(t, err)
// Get endpoint
endpoint, err := nginxContainer.Endpoint(ctx, "http")
require.NoError(t, err)
// Test the nginx default page
resp, err := http.Get(endpoint)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "Welcome to nginx")
t.Log("Successfully accessed nginx container")
}
// TestGenericContainerWithCustomHTML demonstrates serving custom content with nginx
func TestGenericContainerWithCustomHTML(t *testing.T) {
ctx := context.Background()
customHTML := `<!DOCTYPE html>
<html>
<head><title>Test Page</title></head>
<body><h1>Hello from Testcontainers!</h1></body>
</html>`
// Start nginx with custom HTML
nginxContainer, err := testcontainers.Run(
ctx,
"nginx:alpine",
testcontainers.WithExposedPorts("80/tcp"),
testcontainers.WithFiles(testcontainers.ContainerFile{
Reader: strings.NewReader(customHTML),
ContainerFilePath: "/usr/share/nginx/html/index.html",
FileMode: 0o644,
}),
testcontainers.WithWaitStrategy(
wait.ForListeningPort("80/tcp"),
),
)
testcontainers.CleanupContainer(t, nginxContainer)
require.NoError(t, err)
endpoint, err := nginxContainer.Endpoint(ctx, "http")
require.NoError(t, err)
resp, err := http.Get(endpoint)
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Contains(t, string(body), "Hello from Testcontainers!")
t.Log("Successfully served custom HTML from nginx")
}
// TestGenericContainerWithEnv demonstrates using environment variables
func TestGenericContainerWithEnv(t *testing.T) {
ctx := context.Background()
// Start alpine container that echoes an environment variable
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithEnv(map[string]string{
"MY_VAR": "test_value",
"ANOTHER_VAR": "another_value",
}),
testcontainers.WithCmd("sleep", "300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Execute command to read environment variable
exitCode, reader, err := alpineContainer.Exec(ctx, []string{"sh", "-c", "echo $MY_VAR"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.Contains(t, string(output), "test_value")
t.Log("Successfully used environment variables in container")
}
// TestGenericContainerWithCommand demonstrates running a custom command
func TestGenericContainerWithCommand(t *testing.T) {
ctx := context.Background()
// Start alpine with a custom command that creates a file
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd("sh", "-c", "echo 'Hello' > /tmp/hello.txt && sleep 300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Give it a moment to create the file
time.Sleep(1 * time.Second)
// Read the file we created
exitCode, reader, err := alpineContainer.Exec(ctx, []string{"cat", "/tmp/hello.txt"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.Contains(t, string(output), "Hello")
t.Log("Successfully ran custom command in container")
}
// TestGenericContainerWithLabels demonstrates using labels
func TestGenericContainerWithLabels(t *testing.T) {
ctx := context.Background()
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithLabels(map[string]string{
"app": "testapp",
"environment": "test",
"version": "1.0",
}),
testcontainers.WithCmd("sleep", "300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Inspect container to verify labels
inspect, err := alpineContainer.Inspect(ctx)
require.NoError(t, err)
require.Equal(t, "testapp", inspect.Config.Labels["app"])
require.Equal(t, "test", inspect.Config.Labels["environment"])
require.Equal(t, "1.0", inspect.Config.Labels["version"])
t.Log("Successfully set and verified container labels")
}
// TestGenericContainerWithTmpfs demonstrates using temporary filesystems
func TestGenericContainerWithTmpfs(t *testing.T) {
ctx := context.Background()
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithTmpfs(map[string]string{
"/tmp": "rw,size=100m",
}),
testcontainers.WithCmd("sleep", "300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Verify tmpfs is mounted
exitCode, reader, err := alpineContainer.Exec(ctx, []string{"mount"}, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.Contains(t, string(output), "tmpfs on /tmp")
t.Log("Successfully mounted tmpfs in container")
}
// TestGenericContainerLogs demonstrates accessing container logs
func TestGenericContainerLogs(t *testing.T) {
ctx := context.Background()
// Start container that produces logs
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd("sh", "-c", "echo 'Starting...'; sleep 1; echo 'Running...'; sleep 300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Wait a moment for logs to be written
time.Sleep(2 * time.Second)
// Read logs
logs, err := alpineContainer.Logs(ctx)
require.NoError(t, err)
defer logs.Close()
logContent, err := io.ReadAll(logs)
require.NoError(t, err)
logStr := string(logContent)
require.Contains(t, logStr, "Starting...")
require.Contains(t, logStr, "Running...")
t.Log("Successfully read container logs")
}
// TestGenericContainerExec demonstrates executing commands in a running container
func TestGenericContainerExec(t *testing.T) {
ctx := context.Background()
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd("sleep", "300"),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// Execute multiple commands
tests := []struct {
name string
cmd []string
expected string
}{
{
name: "echo",
cmd: []string{"echo", "hello world"},
expected: "hello world",
},
{
name: "pwd",
cmd: []string{"pwd"},
expected: "/",
},
{
name: "uname",
cmd: []string{"uname", "-s"},
expected: "Linux",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exitCode, reader, err := alpineContainer.Exec(ctx, tt.cmd, exec.Multiplexed())
require.NoError(t, err)
require.Equal(t, 0, exitCode)
output, err := io.ReadAll(reader)
require.NoError(t, err)
require.Contains(t, string(output), tt.expected)
})
}
t.Log("Successfully executed multiple commands")
}
// TestGenericContainerHTTPWait demonstrates waiting for an HTTP endpoint
func TestGenericContainerHTTPWait(t *testing.T) {
ctx := context.Background()
nginxContainer, err := testcontainers.Run(
ctx,
"nginx:alpine",
testcontainers.WithExposedPorts("80/tcp"),
testcontainers.WithWaitStrategy(
wait.ForListeningPort("80/tcp"),
wait.ForHTTP("/").
WithPort("80/tcp").
WithStatusCodeMatcher(func(status int) bool {
return status == http.StatusOK
}).
WithStartupTimeout(30*time.Second),
),
)
testcontainers.CleanupContainer(t, nginxContainer)
require.NoError(t, err)
endpoint, err := nginxContainer.Endpoint(ctx, "http")
require.NoError(t, err)
// Container is already ready because wait strategy succeeded
resp, err := http.Get(endpoint)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
t.Log("HTTP wait strategy worked correctly")
}
// TestGenericContainerLogWait demonstrates waiting for a log message
func TestGenericContainerLogWait(t *testing.T) {
ctx := context.Background()
alpineContainer, err := testcontainers.Run(
ctx,
"alpine:latest",
testcontainers.WithCmd(
"sh", "-c",
"echo 'Initializing...'; sleep 2; echo 'Ready!'; sleep 300",
),
testcontainers.WithWaitStrategy(
wait.ForLog("Ready!").WithStartupTimeout(10*time.Second),
),
)
testcontainers.CleanupContainer(t, alpineContainer)
require.NoError(t, err)
// If we got here, the "Ready!" message was logged
t.Log("Container became ready after logging expected message")
}
// TestGenericContainerPortInfo demonstrates getting port information
func TestGenericContainerPortInfo(t *testing.T) {
ctx := context.Background()
nginxContainer, err := testcontainers.Run(
ctx,
"nginx:alpine",
testcontainers.WithExposedPorts("80/tcp", "443/tcp"),
testcontainers.WithWaitStrategy(wait.ForListeningPort("80/tcp")),
)
testcontainers.CleanupContainer(t, nginxContainer)
require.NoError(t, err)
// Method 1: Get mapped port
port80, err := nginxContainer.MappedPort(ctx, "80/tcp")
require.NoError(t, err)
t.Logf("Port 80 is mapped to: %s", port80.Port())
// Method 2: Get host
host, err := nginxContainer.Host(ctx)
require.NoError(t, err)
t.Logf("Container host: %s", host)
// Method 3: Get endpoint
endpoint, err := nginxContainer.Endpoint(ctx, "http")
require.NoError(t, err)
t.Logf("HTTP endpoint: %s", endpoint)
// Method 4: Get all ports
ports, err := nginxContainer.Ports(ctx)
require.NoError(t, err)
t.Logf("All ports: %v", ports)
// Verify we can access port 80
resp, err := http.Get(fmt.Sprintf("http://%s:%s", host, port80.Port()))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
t.Log("Successfully retrieved and used port information")
}

263
examples/README.md Normal file
View File

@@ -0,0 +1,263 @@
# Testcontainers for Go Examples
This directory contains practical, runnable examples demonstrating various features and patterns of Testcontainers for Go.
## Prerequisites
Before running these examples, you need:
1. **Go 1.24+** installed
2. **Docker** running locally
3. Required Go packages:
```bash
go get github.com/testcontainers/testcontainers-go
go get github.com/testcontainers/testcontainers-go/modules/postgres
go get github.com/testcontainers/testcontainers-go/modules/redis
go get github.com/stretchr/testify/require
go get github.com/lib/pq
go get github.com/redis/go-redis/v9
```
## Examples Overview
### 01_postgres_basic_test.go
**Basic PostgreSQL Usage**
Demonstrates:
- Starting a PostgreSQL container with default settings
- Connecting to PostgreSQL
- Custom database configuration (database name, username, password)
- Creating schemas and inserting data
Run with:
```bash
go test -v -run TestBasicPostgres
go test -v -run TestPostgresWithCustomConfig
go test -v -run TestPostgresWithSchema
```
### 02_postgres_snapshot_test.go
**PostgreSQL Snapshots for Test Isolation**
Demonstrates:
- Creating database snapshots
- Modifying data and restoring to previous state
- Using multiple named snapshots
This is extremely useful for:
- Running multiple tests against the same initial state
- Test isolation without restarting containers
- Fast test execution
Run with:
```bash
go test -v -run TestPostgresSnapshot
go test -v -run TestPostgresMultipleSnapshots
```
### 03_redis_cache_test.go
**Redis Operations**
Demonstrates:
- Basic Redis key-value operations
- Key expiration
- List operations (RPUSH, LPOP, LRANGE)
- Hash operations (HSET, HGET, HGETALL)
- Custom Redis configuration (snapshotting, log levels)
Run with:
```bash
go test -v -run TestBasicRedis
go test -v -run TestRedisWithExpiration
go test -v -run TestRedisListOperations
go test -v -run TestRedisHashOperations
go test -v -run TestRedisWithConfig
```
### 04_multi_container_network_test.go
**Multi-Container Networking**
Demonstrates:
- Creating custom Docker networks
- Connecting multiple containers on the same network
- Container-to-container communication using network aliases
- Simulating microservices architectures
- Waiting for multiple containers concurrently
This is essential for:
- Integration testing with multiple services
- Testing service dependencies
- Simulating production-like environments
Run with:
```bash
go test -v -run TestMultiContainerNetwork
go test -v -run TestApplicationWithDependencies
go test -v -run TestContainerCommunication
go test -v -run TestWaitForMultipleContainers
```
### 05_generic_container_test.go
**Generic Container Patterns**
Demonstrates:
- Using containers without pre-configured modules
- Custom HTML content with nginx
- Environment variables
- Custom commands
- Container labels
- Temporary filesystems (tmpfs)
- Reading container logs
- Executing commands in running containers
- Different wait strategies (HTTP, log-based)
- Getting port information
Run with:
```bash
go test -v -run TestGenericNginx
go test -v -run TestGenericContainerWithCustomHTML
go test -v -run TestGenericContainerWithEnv
go test -v -run TestGenericContainerExec
# ... and many more
```
## Running All Examples
To run all examples:
```bash
# Run all tests
go test -v ./examples/
# Run all tests with more details
go test -v -count=1 ./examples/
# Run a specific example file
go test -v ./examples/01_postgres_basic_test.go
```
## Common Patterns
### 1. Basic Pattern (with Module)
```go
ctx := context.Background()
// Start container
pgContainer, err := postgres.Run(ctx, "postgres:16-alpine")
testcontainers.CleanupContainer(t, pgContainer) // BEFORE error check!
require.NoError(t, err)
// Get connection details
connStr, err := pgContainer.ConnectionString(ctx)
require.NoError(t, err)
// Use the container...
```
### 2. Generic Container Pattern
```go
ctx := context.Background()
ctr, err := testcontainers.Run(
ctx,
"image:tag",
testcontainers.WithExposedPorts("8080/tcp"),
testcontainers.WithEnv(map[string]string{"KEY": "value"}),
testcontainers.WithWaitStrategy(wait.ForListeningPort("8080/tcp")),
)
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)
```
### 3. Multi-Container Pattern
```go
ctx := context.Background()
// Create network
nw, err := network.New(ctx)
testcontainers.CleanupNetwork(t, nw)
require.NoError(t, err)
// Start containers on network
db, err := postgres.Run(ctx, "postgres:16-alpine",
network.WithNetwork([]string{"database"}, nw))
testcontainers.CleanupContainer(t, db)
app, err := testcontainers.Run(ctx, "myapp:latest",
network.WithNetwork([]string{"app"}, nw))
testcontainers.CleanupContainer(t, app)
```
## Tips and Best Practices
1. **Always register cleanup before checking errors**
```go
ctr, err := testcontainers.Run(ctx, "image")
testcontainers.CleanupContainer(t, ctr) // Call this first!
require.NoError(t, err) // Then check error
```
2. **Use pre-configured modules when available**
- Modules provide sensible defaults
- Helper methods like `ConnectionString()`
- Automatic credential management
3. **Use snapshots for test isolation**
- Much faster than restarting containers
- Perfect for test suites with shared setup
4. **Use custom networks for multi-container tests**
- Containers can communicate via aliases
- More realistic than host networking
5. **Use appropriate wait strategies**
- `ForListeningPort` - when service listens on a port
- `ForLog` - when service logs a ready message
- `ForHTTP` - when service has an HTTP health endpoint
## Troubleshooting
### Container won't start
- Check if Docker is running: `docker ps`
- Check Docker logs: add `testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{})`
- Increase timeout: `wait.ForListeningPort("80/tcp").WithStartupTimeout(60*time.Second)`
### Port conflicts
- Testcontainers auto-assigns random ports
- Don't manually specify host ports
### Image pull failures
- Pull manually first: `docker pull postgres:16-alpine`
- Check network connectivity
- For private registries: `docker login registry.example.com`
### Cleanup issues
- Verify Ryuk is running: `docker ps | grep ryuk`
- Check cleanup order: network cleanup after container cleanup
- Enable Ryuk logging: `export RYUK_VERBOSE=true`
## Additional Resources
- [Testcontainers for Go Documentation](https://golang.testcontainers.org/)
- [Available Modules](https://golang.testcontainers.org/modules/)
- [GitHub Repository](https://github.com/testcontainers/testcontainers-go)
## Module Dependencies
To run specific examples, you may need additional module dependencies:
```bash
# For PostgreSQL examples
go get github.com/lib/pq
go get github.com/testcontainers/testcontainers-go/modules/postgres
# For Redis examples
go get github.com/redis/go-redis/v9
go get github.com/testcontainers/testcontainers-go/modules/redis
# Note: network is part of the main testcontainers-go module, not a separate module
```

75
examples/go.mod Normal file
View File

@@ -0,0 +1,75 @@
module github.com/testcontainers/testcontainers-go/examples
go 1.24.0
toolchain go1.24.7
require (
github.com/lib/pq v1.10.9
github.com/redis/go-redis/v9 v9.7.3
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.39.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0
github.com/testcontainers/testcontainers-go/modules/redis v0.39.0
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mdelapenya/tlscert v0.2.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

220
examples/go.sum Normal file
View File

@@ -0,0 +1,220 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0 h1:REJz+XwNpGC/dCgTfYvM4SKqobNqDBfvhq74s2oHTUM=
github.com/testcontainers/testcontainers-go/modules/postgres v0.39.0/go.mod h1:4K2OhtHEeT+JSIFX4V8DkGKsyLa96Y2vLdd3xsxD5HE=
github.com/testcontainers/testcontainers-go/modules/redis v0.39.0 h1:p54qELdCx4Gftkxzf44k9RJRRhaO/S5ehP9zo8SUTLM=
github.com/testcontainers/testcontainers-go/modules/redis v0.39.0/go.mod h1:P1mTbHruHqAU2I26y0RADz1BitF59FLbQr7ceqN9bt4=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=

81
plugin.lock.json Normal file
View File

@@ -0,0 +1,81 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:testcontainers/claude-skills:testcontainers-go",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "2032c49f293a8f3615bad72afd6b86ce81eff615",
"treeHash": "d18459abf7a17e71524d86043a4b1528aee665caeb5747bf2c269065f7cfb4ac",
"generatedAt": "2025-11-28T10:28:39.295819Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "testcontainers-go",
"description": "A comprehensive guide for using Testcontainers for Go to write reliable integration tests with Docker containers in Go projects. Supports 62+ pre-configured modules for databases, message queues, cloud services, and more.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "LICENSE",
"sha256": "d123f6c77b4c4dcf22191360afab21e10c349463efd7453af0f6486b65487c82"
},
{
"path": "README.md",
"sha256": "2891c81eb565669faa454f44b81e3ca0a34079b3d77aeb936c6b777abb991dcb"
},
{
"path": "SKILL.md",
"sha256": "e1f3a53284118c865ea6c72be2d1b9bca3d6882188a802f7dc9df105174d4799"
},
{
"path": "examples/02_postgres_snapshot_test.go",
"sha256": "01a88b6387f1dfe0c7b462c8eec6711782453bd849d31d838c3d08faa1af0eb5"
},
{
"path": "examples/go.mod",
"sha256": "d8e4deaad2abca28ff7f142c19a877f139b1c1acf16a89e6adcfcff1a4c06073"
},
{
"path": "examples/03_redis_cache_test.go",
"sha256": "df8936199084492e7b78f402c117f990e96a40b90d3891f2f75c3e1c634b0bc2"
},
{
"path": "examples/05_generic_container_test.go",
"sha256": "2adcadfa4a13f039150b7f62547c3a7c38e35ce71344413090cb0d94b4c9a268"
},
{
"path": "examples/04_multi_container_network_test.go",
"sha256": "a0ba9896da71694b1e4304a43199b84e72bbb42aa2eb45ed296515d946d33388"
},
{
"path": "examples/go.sum",
"sha256": "c71b606d469e904cd8a723e9540bc34dd058f6f80d75f64848b8e41de7aaf837"
},
{
"path": "examples/01_postgres_basic_test.go",
"sha256": "1065e9468a2fe5377fc9f3ad876ddff1b9d593bbc0afb586532c44b0311d90c4"
},
{
"path": "examples/README.md",
"sha256": "20916b15f75cce1b1d6f87837403589758039d54db510012285fd9cee6c6e48b"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "51cc5eeb826fbe668541ce844bfd5e6f86cdfd8a2059ad04519deca4790f6ca4"
}
],
"dirSha256": "d18459abf7a17e71524d86043a4b1528aee665caeb5747bf2c269065f7cfb4ac"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}