Initial commit
This commit is contained in:
126
examples/01_postgres_basic_test.go
Normal file
126
examples/01_postgres_basic_test.go
Normal 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")
|
||||
}
|
||||
181
examples/02_postgres_snapshot_test.go
Normal file
181
examples/02_postgres_snapshot_test.go
Normal 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")
|
||||
}
|
||||
191
examples/03_redis_cache_test.go
Normal file
191
examples/03_redis_cache_test.go
Normal 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")
|
||||
}
|
||||
265
examples/04_multi_container_network_test.go
Normal file
265
examples/04_multi_container_network_test.go
Normal 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")
|
||||
}
|
||||
382
examples/05_generic_container_test.go
Normal file
382
examples/05_generic_container_test.go
Normal 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
263
examples/README.md
Normal 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
75
examples/go.mod
Normal 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
220
examples/go.sum
Normal 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=
|
||||
Reference in New Issue
Block a user