[VAULT-43581] sdk: Add MongoDB blackbox tests for static roles (#13746) (#14010)

* Add MongoDB blackbox tests for static roles

- Implement core static role tests (create, read credentials, manual rotation, validation)
- Add helper functions for MongoDB user creation and credential verification
- Implement basic connection config test
- Remove stub functions, add TODOs for future implementation
- All tests follow blackbox SDK patterns with parallel execution and proper cleanup

* make it work

* WIP

* mongo private/public urls

* Apply suggestion from @brewgator

* regex for readability

Co-authored-by: brewgator <lt.carbonell@hashicorp.com>
This commit is contained in:
Vault Automation 2026-04-17 09:15:38 -04:00 committed by GitHub
parent 4d3f5d93f1
commit 8c58356d5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 450 additions and 149 deletions

View File

@ -27,6 +27,7 @@ locals {
MONGO_INITDB_ROOT_PASSWORD = var.password
MONGO_INITDB_DATABASE = var.database
}
args = "--bind_ip_all"
}
mysql = {
image_template = "docker.io/mysql:${var.db_version}"
@ -43,6 +44,7 @@ locals {
image = local.config.image_template
env_vars_map = local.config.env_vars
env_vars = join(",", [for k, v in local.env_vars_map : "${k}=${v}"])
args = try(local.config.args, "")
}
# Creating Database Server using generic container script
@ -56,6 +58,7 @@ resource "enos_remote_exec" "create_database" {
CONTAINER_NAME = "${var.database_type}-${var.instance_name}"
CONTAINER_PORTS = var.port
CONTAINER_ENVS = local.env_vars
CONTAINER_ARGS = local.args
}
transport = {

View File

@ -42,7 +42,7 @@ resource "enos_local_exec" "run_blackbox_test" {
# PATH and Go-related environment variables are inherited from the calling process
}, var.vault_namespace != null ? {
VAULT_NAMESPACE = var.vault_namespace
} : {}, local.ldap_environment, local.postgres_environment
} : {}, local.ldap_environment, local.postgres_environment, local.mongodb_environment
)
depends_on = [local_file.test_matrix]
}
@ -78,6 +78,15 @@ locals {
PGPASSWORD = local.postgres_config.password
PGDATABASE = local.postgres_config.database
} : {}
# Extract MongoDB configuration safely, defaulting to empty map if not available
mongodb_config = try(var.integration_host_state.mongodb, {})
# Set up MongoDB environment variables when MongoDB integration is available
mongodb_environment = try(local.mongodb_config.host.public_ip, "") != "" ? {
MONGO_URL = "mongodb://${local.mongodb_config.username}:${local.mongodb_config.password}@${local.mongodb_config.host.public_ip}:${local.mongodb_config.port}/${local.mongodb_config.database}?directConnection=true"
MONGO_URL_PRIVATE = "mongodb://${local.mongodb_config.username}:${local.mongodb_config.password}@${local.mongodb_config.host.private_ip}:${local.mongodb_config.port}/${local.mongodb_config.database}?directConnection=true"
} : {}
}
# Extract information from the script output

View File

@ -4,8 +4,10 @@
package mongodb
import (
"fmt"
"testing"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/testcluster/blackbox"
)
@ -18,128 +20,33 @@ func TestMongoDBConnectionConfigCRUDWorkflows(t *testing.T) {
testMongoDBConnectionConfigCreateBasic(t, v)
})
t.Run("ListReturnsNamesOnly", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigListReturnsNamesOnly(t, v)
})
t.Run("ReadRedactsSensitiveFields", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigReadRedactsSensitiveFields(t, v)
})
t.Run("ResetConnection", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigResetConnection(t, v)
})
t.Run("DeleteConnection", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigDeleteConnection(t, v)
})
// TODO: Additional tests for future implementation:
// - ListReturnsNamesOnly: Verify listing returns only connection names
// - ReadRedactsSensitiveFields: Verify passwords are redacted in read operations
// - ResetConnection: Verify connection reset preserves configuration
// - DeleteConnection: Verify connection deletion and idempotency
}
// TestMongoDBConnectionConfigValidationWorkflows runs all MongoDB
// connection config validation workflow tests.
func TestMongoDBConnectionConfigValidationWorkflows(t *testing.T) {
t.Run("VerifyConnectionValid", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigVerifyConnectionValid(t, v)
})
t.Run("VerifyConnectionInvalid", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBConnectionConfigVerifyConnectionInvalid(t, v)
})
}
// TODO: TestMongoDBConnectionConfigValidationWorkflows
// Future implementation should test:
// - VerifyConnectionValid: Test with verify_connection=true and valid credentials
// - VerifyConnectionInvalid: Test with verify_connection=true and invalid credentials
// testMongoDBConnectionConfigCreateBasic verifies that configuring a
// MongoDB database connection succeeds at database/config/{name}.
func testMongoDBConnectionConfigCreateBasic(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
requireVaultEnv(t)
cleanup, connURL := PrepareTestContainer(t)
defer cleanup()
// TODO: Implement test following this pattern:
// 1. requireVaultEnv(t)
// 2. cleanup, connURL := PrepareTestContainer(t)
// 3. defer cleanup()
// 4. mount := fmt.Sprintf("database-%s", sanitize(t.Name()))
// 5. v.MustEnableSecretsEngine(mount, &api.MountInput{Type: "database"})
// 6. v.MustWrite(mount+"/config/my-mongodb-db", mongoConnectionConfigPayload(...))
// 7. config := v.MustReadRequired(mount + "/config/my-mongodb-db")
// 8. v.AssertSecret(config).Data().HasKey("plugin_name", "mongodb-database-plugin")
}
// testMongoDBConnectionConfigListReturnsNamesOnly verifies LIST
// /database/config returns configured connection names only, without
// sensitive config details.
func testMongoDBConnectionConfigListReturnsNamesOnly(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create multiple MongoDB connections
// 2. List connections
// 3. Verify only names are returned, no sensitive data
}
// testMongoDBConnectionConfigReadRedactsSensitiveFields verifies that reading
// a MongoDB connection config returns sanitized connection details.
func testMongoDBConnectionConfigReadRedactsSensitiveFields(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create MongoDB connection with credentials
// 2. Read connection config
// 3. Verify password and other sensitive fields are redacted
}
// testMongoDBConnectionConfigVerifyConnectionValid verifies that
// configuration succeeds with verify_connection=true when credentials are valid.
func testMongoDBConnectionConfigVerifyConnectionValid(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create MongoDB connection with valid credentials and verify_connection=true
// 2. Verify connection succeeds
}
// testMongoDBConnectionConfigVerifyConnectionInvalid verifies that
// configuration fails with verify_connection=true when credentials are invalid.
func testMongoDBConnectionConfigVerifyConnectionInvalid(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create MongoDB connection with invalid credentials and verify_connection=true
// 2. Verify connection fails with appropriate error
}
// testMongoDBConnectionConfigResetConnection verifies that resetting a
// MongoDB database connection succeeds and preserves the stored connection
// configuration.
func testMongoDBConnectionConfigResetConnection(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create MongoDB connection
// 2. Reset connection
// 3. Verify config is preserved
}
// testMongoDBConnectionConfigDeleteConnection verifies that deleting a
// MongoDB database connection removes it, prevents new credential generation,
// and remains idempotent when deleted again.
func testMongoDBConnectionConfigDeleteConnection(t *testing.T, v *blackbox.Session) {
t.Skip("Test implementation pending - MongoDB framework setup complete")
// TODO: Implement test to verify:
// 1. Create MongoDB connection and role
// 2. Generate credentials
// 3. Delete connection
// 4. Verify connection is gone and credentials can't be generated
// 5. Delete again to verify idempotency
mount := fmt.Sprintf("database-%s", sanitize(t.Name()))
v.MustEnableSecretsEngine(mount, &api.MountInput{Type: "database"})
v.MustWrite(
mount+"/config/my-mongodb-db",
mongoConnectionConfigPayload(connURL, "*", false),
)
config := v.MustReadRequired(mount + "/config/my-mongodb-db")
v.AssertSecret(config).Data().HasKey("plugin_name", "mongodb-database-plugin")
}

View File

@ -0,0 +1,355 @@
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: BUSL-1.1
package mongodb
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/testcluster/blackbox"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const (
testConnectionName = "my-mongodb-db"
testStaticRoleName = "my-static-role"
testUsername = "staticuser1"
testInitialPassword = "initialpass"
testRotationPeriod = 86400 // 24 hours in seconds
mongoConnectTimeout = 10 * time.Second
)
// TestMongoDBStaticRoleWorkflows runs all MongoDB static role workflow tests.
func TestMongoDBStaticRoleWorkflows(t *testing.T) {
t.Run("CreateBasic", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBStaticRoleCreateBasic(t, v)
})
t.Run("ReadCredentials", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBStaticRoleReadCredentials(t, v)
})
t.Run("ManualRotation", func(t *testing.T) {
t.Parallel()
v := blackbox.New(t)
testMongoDBStaticRoleManualRotation(t, v)
})
}
// TODO: TestMongoDBStaticRoleValidationWorkflows
// Future implementation should test:
// - RequiresUsername: Verify error when username is missing
// - RequiresRotationPeriodOrSchedule: Verify error without rotation config
// - RejectsInvalidRotationPeriod: Verify error with period < 5 seconds
// - RejectsMutuallyExclusiveFields: Verify error with both period and schedule
// testMongoDBStaticRoleCreateBasic verifies that creating a basic static role
// succeeds with required fields.
func testMongoDBStaticRoleCreateBasic(t *testing.T, v *blackbox.Session) {
mount, connURL := setupMongoDBTest(t, v)
createMongoDBUser(t, connURL, testUsername, testInitialPassword)
v.MustWrite(mount+"/static-roles/"+testStaticRoleName, map[string]any{
"db_name": testConnectionName,
"username": testUsername,
"rotation_period": testRotationPeriod,
})
role := v.MustReadRequired(mount + "/static-roles/" + testStaticRoleName)
v.AssertSecret(role).
Data().
HasKey("username", testUsername).
HasKey("rotation_period", float64(testRotationPeriod)).
HasKey("db_name", testConnectionName)
}
// TODO: Additional test implementations for future teams
// - testMongoDBStaticRoleCreateWithRotationSchedule: Test rotation_schedule instead of rotation_period
// - testMongoDBStaticRoleListReturnsNamesOnly: Test listing multiple static roles
// - testMongoDBStaticRoleReadReturnsConfiguration: Test reading role config without sensitive data
// - testMongoDBStaticRoleUpdateRotationPeriod: Test updating rotation period
// - testMongoDBStaticRoleDeleteRole: Test deleting a static role
// testMongoDBStaticRoleManualRotation verifies manually rotating a static
// role's credentials succeeds.
func testMongoDBStaticRoleManualRotation(t *testing.T, v *blackbox.Session) {
mount, connURL := setupMongoDBTest(t, v)
createMongoDBUser(t, connURL, testUsername, testInitialPassword)
v.MustWrite(mount+"/static-roles/"+testStaticRoleName, map[string]any{
"db_name": testConnectionName,
"username": testUsername,
"rotation_period": testRotationPeriod,
})
creds1 := v.MustReadRequired(mount + "/static-creds/" + testStaticRoleName)
password1 := creds1.Data["password"].(string)
if password1 == testInitialPassword {
t.Fatal("expected password to be rotated on role creation")
}
v.MustWrite(mount+"/rotate-role/"+testStaticRoleName, nil)
creds2 := v.MustReadRequired(mount + "/static-creds/" + testStaticRoleName)
password2 := creds2.Data["password"].(string)
if password1 == password2 {
t.Fatal("expected password to change after rotation")
}
verifyMongoDBCredentials(t, connURL, testUsername, password2)
}
// TODO: testMongoDBStaticRoleAutomaticRotation - Test automatic rotation with short period
// This test requires waiting for rotation to occur, which may be time-consuming
// Pattern: Create role with short rotation_period, wait, verify password changed
// testMongoDBStaticRoleReadCredentials verifies reading static credentials
// returns the current password.
func testMongoDBStaticRoleReadCredentials(t *testing.T, v *blackbox.Session) {
mount, connURL := setupMongoDBTest(t, v)
createMongoDBUser(t, connURL, testUsername, testInitialPassword)
v.MustWrite(mount+"/static-roles/"+testStaticRoleName, map[string]any{
"db_name": testConnectionName,
"username": testUsername,
"rotation_period": testRotationPeriod,
})
creds := v.MustReadRequired(mount + "/static-creds/" + testStaticRoleName)
v.AssertSecret(creds).
Data().
HasKey("username", testUsername).
HasKey("password", creds.Data["password"])
password := creds.Data["password"].(string)
if password == "" {
t.Fatal("expected non-empty password")
}
verifyMongoDBCredentials(t, connURL, testUsername, password)
}
// testMongoDBStaticRoleRequiresUsername verifies creating a static role
// without username fails.
func testMongoDBStaticRoleRequiresUsername(t *testing.T, v *blackbox.Session) {
mount, _ := setupMongoDBTest(t, v)
_, err := v.Client.Logical().Write(mount+"/static-roles/"+testStaticRoleName, map[string]any{
"db_name": testConnectionName,
"rotation_period": testRotationPeriod,
})
if err == nil {
t.Fatal("expected error when creating static role without username")
}
if !strings.Contains(err.Error(), "username") {
t.Fatalf("expected error message to mention 'username', got: %v", err)
}
}
// setupMongoDBTest performs common test setup: creates container, enables mount, configures connection.
// Returns mount path and connection URL.
func setupMongoDBTest(t *testing.T, v *blackbox.Session) (string, string) {
t.Helper()
requireVaultEnv(t)
cleanup, connURL := PrepareTestContainer(t)
t.Cleanup(cleanup)
mount := fmt.Sprintf("database-%s", sanitize(t.Name()))
v.MustEnableSecretsEngine(mount, &api.MountInput{Type: "database"})
v.MustWrite(
mount+"/config/"+testConnectionName,
mongoConnectionConfigPayload(connURL, "*", false),
)
return mount, connURL
}
// createMongoDBUser creates a MongoDB user for testing static roles.
func createMongoDBUser(t *testing.T, connURL, username, password string) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), mongoConnectTimeout)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(connURL))
if err != nil {
t.Fatalf("failed to connect to MongoDB: %v", err)
}
defer client.Disconnect(ctx)
db := client.Database("admin")
err = db.RunCommand(ctx, bson.D{
{Key: "createUser", Value: username},
{Key: "pwd", Value: password},
{Key: "roles", Value: bson.A{
bson.D{
{Key: "role", Value: "readWrite"},
{Key: "db", Value: "admin"},
},
}},
}).Err()
if err != nil {
t.Fatalf("failed to create MongoDB user: %v", err)
}
t.Logf("Created MongoDB user: %s", username)
}
// verifyMongoDBCredentials verifies that the given credentials work for MongoDB.
func verifyMongoDBCredentials(t *testing.T, connURL, username, password string) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), mongoConnectTimeout)
defer cancel()
// Replace credentials in connection URL
u, err := parseMongoURL(connURL)
if err != nil {
t.Fatalf("failed to parse connection URL: %v", err)
}
u.User = username
u.Password = password
testURL := buildMongoURL(u)
client, err := mongo.Connect(ctx, options.Client().ApplyURI(testURL))
if err != nil {
t.Fatalf("failed to connect with credentials: %v", err)
}
defer client.Disconnect(ctx)
if err := client.Ping(ctx, nil); err != nil {
t.Fatalf("failed to ping with credentials: %v", err)
}
t.Logf("Verified MongoDB credentials for user: %s", username)
}
// mongoURL represents a parsed MongoDB connection URL.
type mongoURL struct {
Scheme string
User string
Password string
Host string
Database string
Options string
}
// parseMongoURL parses a MongoDB connection URL into components.
func parseMongoURL(connURL string) (*mongoURL, error) {
// Simple parser for mongodb:// URLs
// Format: mongodb://user:pass@host/database?options
u := &mongoURL{Scheme: "mongodb"}
// Remove scheme
rest := connURL
if len(rest) > 10 && rest[:10] == "mongodb://" {
rest = rest[10:]
}
// Extract user:pass if present
atIdx := -1
for i, c := range rest {
if c == '@' {
atIdx = i
break
}
}
if atIdx > 0 {
userPass := rest[:atIdx]
rest = rest[atIdx+1:]
colonIdx := -1
for i, c := range userPass {
if c == ':' {
colonIdx = i
break
}
}
if colonIdx > 0 {
u.User = userPass[:colonIdx]
u.Password = userPass[colonIdx+1:]
}
}
// Extract host and database
slashIdx := -1
for i, c := range rest {
if c == '/' {
slashIdx = i
break
}
}
if slashIdx > 0 {
u.Host = rest[:slashIdx]
rest = rest[slashIdx+1:]
// Extract database and options
qIdx := -1
for i, c := range rest {
if c == '?' {
qIdx = i
break
}
}
if qIdx > 0 {
u.Database = rest[:qIdx]
u.Options = rest[qIdx+1:]
} else {
u.Database = rest
}
} else {
u.Host = rest
}
return u, nil
}
// buildMongoURL builds a MongoDB connection URL from components.
func buildMongoURL(u *mongoURL) string {
url := u.Scheme + "://"
if u.User != "" {
url += u.User
if u.Password != "" {
url += ":" + u.Password
}
url += "@"
}
url += u.Host
if u.Database != "" {
url += "/" + u.Database
}
if u.Options != "" {
url += "?" + u.Options
}
return url
}

View File

@ -5,9 +5,12 @@ package mongodb
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
"os"
"regexp"
"strings"
"testing"
"time"
@ -28,7 +31,7 @@ const (
// Uses test name to ensure unique container names for parallel execution
func defaultRunOpts(t *testing.T) docker.RunOptions {
return docker.RunOptions{
ContainerName: fmt.Sprintf("mongodb-%s", sanitize(t.Name())),
ContainerName: fmt.Sprintf("mongo-%s", sanitize(t.Name())),
ImageRepo: defaultMongoImage,
ImageTag: defaultMongoVersion,
Env: []string{
@ -38,6 +41,7 @@ func defaultRunOpts(t *testing.T) docker.RunOptions {
},
Ports: []string{"27017/tcp"},
DoNotAutoRemove: false,
PreDelete: true,
OmitLogTimestamps: true,
LogConsumer: func(s string) {
if t.Failed() {
@ -56,28 +60,37 @@ func requireVaultEnv(t *testing.T) {
}
}
// sanitize converts test name to a valid container name
// Removes special characters and converts to lowercase
var sanitizeRegex = regexp.MustCompile(`[^a-z0-9]+`)
// sanitize converts test name to a valid identifier with smart truncation
// Replaces non-alphanumeric characters with dashes and truncates long names
// with a hash suffix for uniqueness
func sanitize(name string) string {
name = strings.ToLower(name)
name = strings.ReplaceAll(name, "/", "-")
name = strings.ReplaceAll(name, "_", "-")
name = strings.ReplaceAll(name, " ", "-")
// Remove any remaining special characters
var result strings.Builder
for _, r := range name {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
result.WriteRune(r)
}
lower := strings.ToLower(name)
out := sanitizeRegex.ReplaceAllString(lower, "-")
out = strings.Trim(out, "-")
if out == "" {
return "test"
}
return result.String()
// Truncate long names with hash suffix for uniqueness
if len(out) > 54 {
const hashLen = 8
sum := sha256.Sum256([]byte(out))
hash := hex.EncodeToString(sum[:])[:hashLen]
prefixLen := 54 - 1 - hashLen
out = out[:prefixLen] + "-" + hash
}
return out
}
// PrepareTestContainer starts a MongoDB container for testing
// Returns cleanup function and connection URL
// If MONGO_URL environment variable is set, uses that instead of starting a container
func PrepareTestContainer(t *testing.T) (func(), string) {
_, cleanup, connURL, _ := prepareTestContainer(t, defaultRunOpts(t), defaultMongoPass, true, false)
_, cleanup, connURL, _ := prepareTestContainer(t, defaultRunOpts(t), defaultMongoPass, false, true)
return cleanup, connURL
}
@ -96,11 +109,17 @@ func prepareTestContainer(
if os.Getenv("MONGO_URL") != "" {
envMongoURL := os.Getenv("MONGO_URL")
// Use private URL for Vault, fall back to public
vaultMongoURL := os.Getenv("MONGO_URL_PRIVATE")
if vaultMongoURL == "" {
vaultMongoURL = envMongoURL
}
// Create unique database for this test
dbName := fmt.Sprintf("test_%s_%d", sanitize(t.Name()), time.Now().Unix())
testURL := replaceDatabase(envMongoURL, dbName)
testURL := replaceDatabase(vaultMongoURL, dbName)
// Create the database
// Create the database (test runner uses public URL)
if err := createDatabase(t, envMongoURL, dbName); err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
@ -115,25 +134,33 @@ func prepareTestContainer(
// Start Docker container
runner, err := docker.NewServiceRunner(runOpts)
if err != nil {
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "docker") &&
(strings.Contains(errStr, "daemon") || strings.Contains(errStr, "connect")) {
t.Skipf("skipping blackbox test: docker not available: %v", err)
if strings.Contains(err.Error(), "Cannot connect to the Docker daemon") {
t.Fatalf("skipping blackbox test: docker daemon not available: %v", err)
}
t.Fatalf("Could not start docker MongoDB: %s", err)
}
svc, containerID, err := runner.StartNewService(
context.Background(),
addSuffix,
forceLocalAddr,
connectMongoDB(password),
)
// Retry StartNewService with small delays to handle port mapping timing
var svc *docker.Service
var containerID string
for attempt := 0; attempt < 5; attempt++ {
if attempt > 0 {
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
}
svc, containerID, err = runner.StartNewService(context.Background(), addSuffix, forceLocalAddr, connectMongoDB(password))
if err == nil {
break
}
if !strings.Contains(err.Error(), "no port mapping found") {
break
}
}
if err != nil {
errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "docker") &&
(strings.Contains(errStr, "daemon") || strings.Contains(errStr, "connect")) {
t.Skipf("skipping blackbox test: docker not available: %v", err)
if strings.Contains(err.Error(), "Cannot connect to the Docker daemon") {
t.Fatalf("skipping blackbox test: docker daemon not available: %v", err)
}
t.Fatalf("Could not start docker MongoDB: %s", err)
}