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,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=