From 6dc9c5cba42ee548b26d1f073b64662ce36ce515 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Fri, 8 Nov 2024 01:50:58 +0000 Subject: [PATCH] fix: various fixes and remove certs Various fixes and remove the ability to configure certs using the default generated ones instead. --- docs/features/tls.md | 4 +- docs/modules/cockroachdb.md | 33 +++- modules/cockroachdb/certs.go | 129 ------------- modules/cockroachdb/cockroachdb.go | 151 ++++++++------- modules/cockroachdb/cockroachdb_test.go | 232 +++++------------------- modules/cockroachdb/examples_test.go | 4 +- modules/cockroachdb/go.mod | 1 - modules/cockroachdb/go.sum | 2 - modules/cockroachdb/options.go | 51 ++++-- modules/rabbitmq/examples_test.go | 4 + 10 files changed, 206 insertions(+), 405 deletions(-) delete mode 100644 modules/cockroachdb/certs.go diff --git a/docs/features/tls.md b/docs/features/tls.md index fd8b95266d..130f789b5f 100644 --- a/docs/features/tls.md +++ b/docs/features/tls.md @@ -12,6 +12,6 @@ The example will also create a client that will connect to the server using the demonstrating how to use the generated certificate to communicate with a service. -[Create a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSelfSignedCert -[Sign a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSignSelfSignedCert +[Create a self-signed certificate](../../modules/rabbitmq/examples_test.go) inside_block:exampleSelfSignedCert +[Sign a self-signed certificate](../../modules/rabbitmq/examples_test.go) inside_block:exampleSignSelfSignedCert diff --git a/docs/modules/cockroachdb.md b/docs/modules/cockroachdb.md index 6bbdba0792..39956d5417 100644 --- a/docs/modules/cockroachdb.md +++ b/docs/modules/cockroachdb.md @@ -10,7 +10,7 @@ The Testcontainers module for CockroachDB. Please run the following command to add the CockroachDB module to your Go dependencies: -``` +```shell go get github.com/testcontainers/testcontainers-go/modules/cockroachdb ``` @@ -54,9 +54,11 @@ E.g. `Run(context.Background(), "cockroachdb/cockroach:latest-v23.1")`. Set the database that is created & dialled with `cockroachdb.WithDatabase`. -#### Password authentication +#### User and Password + +You can configured the container to create a user with a password by setting `cockroachdb.WithUser` and `cockroachdb.WithPassword`. -Disable insecure mode and connect with password authentication by setting `cockroachdb.WithUser` and `cockroachdb.WithPassword`. +`cockroachdb.WithPassword` is incompatible with `cockroachdb.WithInsecure`. #### Store size @@ -64,13 +66,21 @@ Control the maximum amount of memory used for storage, by default this is 100% b #### TLS authentication -`cockroachdb.WithTLS` lets you provide the CA certificate along with the certicate and key for the node & clients to connect with. -Internally CockroachDB requires a client certificate for the user to connect with. +`cockroachdb.WithInsecure` lets you disable the use of TLS on connections. + +`cockroachdb.WithInsecure` is incompatible with `cockroachdb.WithPassword`. + +#### Initialization Scripts + +`cockroachdb.WithInitScripts` adds the given scripts to those automatically run when the container starts. +These will be ignored if data exists in the `/cockroach/cockroach-data` directory within the container. -A helper `cockroachdb.NewTLSConfig` exists to generate all of this for you. +`cockroachdb.WithNoClusterDefaults` disables the default cluster settings script. -!!!warning - When TLS is enabled there's a very small, unlikely chance that the underlying driver can panic when registering the driver as part of waiting for CockroachDB to be ready to accept connections. If this is repeatedly happening please open an issue. +Without this option Cockroach containers run `data/cluster-defaults.sql` on startup +which configures the settings recommended by Cockroach Labs for +[local testing clusters](https://www.cockroachlabs.com/docs/stable/local-testing) +unless data exists in the `/cockroach/cockroach-data` directory within the container. ### Container Methods @@ -87,3 +97,10 @@ Same as `ConnectionString` but any error to generate the address will raise a pa #### TLSConfig Returns `*tls.Config` setup to allow you to dial your client over TLS, if enabled, else this will error with `cockroachdb.ErrTLSNotEnabled`. + +!!!info + The `TLSConfig()` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +#### ConnectionConfig + +Returns `*pgx.ConnConfig` which can be passed to `pgx.ConnectConfig` to open a new connection. diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go deleted file mode 100644 index 344e787f15..0000000000 --- a/modules/cockroachdb/certs.go +++ /dev/null @@ -1,129 +0,0 @@ -package cockroachdb - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "net" - "time" - - "github.com/mdelapenya/tlscert" - - "github.com/testcontainers/testcontainers-go" -) - -// TLSConfig is a [testcontainers.ContainerCustomizer] that enables TLS for CockroachDB. -type TLSConfig struct { - CACert *x509.Certificate - NodeCert []byte - NodeKey []byte - ClientCert []byte - ClientKey []byte - - cfg *tls.Config -} - -// customize implements the [customizer] interface. -// It sets the TLS config on the CockroachDBContainer. -func (c *TLSConfig) customize(ctr *CockroachDBContainer) error { - ctr.tlsConfig = c.cfg - return nil -} - -// Customize implements the [testcontainers.ContainerCustomizer] interface. -func (c *TLSConfig) Customize(req *testcontainers.GenericContainerRequest) error { - if req.Env[envUser] != defaultUser { - return fmt.Errorf("unsupported user %q with TLS, use %q", req.Env[envUser], defaultUser) - } - - req.Env[envOptionTLS] = "true" - - caBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: c.CACert.Raw, - }) - files := map[string][]byte{ - fileCACert: caBytes, - fileNodeCert: c.NodeCert, - fileNodeKey: c.NodeKey, - fileClientCert: c.ClientCert, - fileClientKey: c.ClientKey, - } - - for filename, contents := range files { - req.Files = append(req.Files, testcontainers.ContainerFile{ - Reader: bytes.NewReader(contents), - ContainerFilePath: filename, - FileMode: 0o600, - }) - } - - req.Cmd = append(req.Cmd, "--certs-dir="+certsDir) - - return nil -} - -// NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS. -func NewTLSConfig() (*TLSConfig, error) { - // exampleSelfSignedCert { - caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ - Name: "ca", - SubjectCommonName: "Cockroach Test CA", - Host: "localhost,127.0.0.1", - IsCA: true, - ValidFor: time.Hour, - }) - if caCert == nil { - return nil, errors.New("failed to generate CA certificate") - } - // } - - // exampleSignSelfSignedCert { - nodeCert := tlscert.SelfSignedFromRequest(tlscert.Request{ - Name: "node", - SubjectCommonName: "node", - Host: "localhost,127.0.0.1", - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, - ValidFor: time.Hour, - Parent: caCert, // using the CA certificate as parent - }) - if nodeCert == nil { - return nil, errors.New("failed to generate node certificate") - } - // } - - clientCert := tlscert.SelfSignedFromRequest(tlscert.Request{ - Name: "client", - SubjectCommonName: defaultUser, - Host: "localhost,127.0.0.1", - ValidFor: time.Hour, - Parent: caCert, // using the CA certificate as parent - }) - if clientCert == nil { - return nil, errors.New("failed to generate client certificate") - } - - keyPair, err := tls.X509KeyPair(clientCert.Bytes, clientCert.KeyBytes) - if err != nil { - return nil, fmt.Errorf("x509 key pair: %w", err) - } - - certPool := x509.NewCertPool() - certPool.AddCert(caCert.Cert) - - return &TLSConfig{ - CACert: caCert.Cert, - NodeCert: nodeCert.Bytes, - NodeKey: nodeCert.KeyBytes, - ClientCert: clientCert.Bytes, - ClientKey: clientCert.KeyBytes, - cfg: &tls.Config{ - RootCAs: certPool, - Certificates: []tls.Certificate{keyPair}, - ServerName: "localhost", - }, - }, nil -} diff --git a/modules/cockroachdb/cockroachdb.go b/modules/cockroachdb/cockroachdb.go index 33e68482a4..3460e32765 100644 --- a/modules/cockroachdb/cockroachdb.go +++ b/modules/cockroachdb/cockroachdb.go @@ -35,20 +35,25 @@ const ( // initDBPath is the path where the init scripts are placed in the container. initDBPath = "/docker-entrypoint-initdb.d" + // cockroachDir is the path where the CockroachDB files are placed in the container. + cockroachDir = "/cockroach" + // clusterDefaultsContainerFile is the path to the default cluster settings script in the container. clusterDefaultsContainerFile = initDBPath + "/__cluster_defaults.sql" // memStorageFlag is the flag to use in the start command to use an in-memory store. memStorageFlag = "--store=type=mem,size=" + // insecureFlag is the flag to use in the start command to disable TLS. + insecureFlag = "--insecure" + // env vars. - envUser = "COCKROACH_USER" - envPassword = "COCKROACH_PASSWORD" - envDatabase = "COCKROACH_DATABASE" - envOptionTLS = "__OPTION_TLS" + envUser = "COCKROACH_USER" + envPassword = "COCKROACH_PASSWORD" + envDatabase = "COCKROACH_DATABASE" // cert files. - certsDir = "/cockroach/certs" + certsDir = cockroachDir + "/certs" fileCACert = certsDir + "/ca.crt" fileNodeCert = certsDir + "/node.crt" fileNodeKey = certsDir + "/node.key" @@ -73,11 +78,16 @@ func newDefaultsReader(data []byte) *defaultsReader { // CockroachDBContainer represents the CockroachDB container type used in the module type CockroachDBContainer struct { testcontainers.Container + options +} +// options represents the options for the CockroachDBContainer type. +type options struct { // Settings. database string user string password string + insecure bool // Client certificate. clientCert []byte @@ -86,6 +96,59 @@ type CockroachDBContainer struct { tlsConfig *tls.Config } +// WaitUntilReady implements the [wait.Strategy] interface. +// If TLS is enabled, it waits for the CA, client cert and key for the configured user to be +// available in the container and uses them to setup the TLS config, otherwise it does nothing. +// +// This is defined on the options as it needs to know the customised values to operate correctly. +func (o *options) WaitUntilReady(ctx context.Context, target wait.StrategyTarget) error { + if o.insecure { + return nil + } + + return wait.ForAll( + wait.ForFile(fileCACert).WithMatcher(func(r io.Reader) error { + buf, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("read CA cert: %w", err) + } + + if !o.certPool.AppendCertsFromPEM(buf) { + return errors.New("invalid CA cert") + } + + return nil + }), + wait.ForFile(certsDir+"/client."+o.user+".crt").WithMatcher(func(r io.Reader) error { + var err error + if o.clientCert, err = io.ReadAll(r); err != nil { + return fmt.Errorf("read client cert: %w", err) + } + + return nil + }), + wait.ForFile(certsDir+"/client."+o.user+".key").WithMatcher(func(r io.Reader) error { + var err error + if o.clientKey, err = io.ReadAll(r); err != nil { + return fmt.Errorf("read client key: %w", err) + } + + cert, err := tls.X509KeyPair(o.clientCert, o.clientKey) + if err != nil { + return fmt.Errorf("x509 key pair: %w", err) + } + + o.tlsConfig = &tls.Config{ + RootCAs: o.certPool, + Certificates: []tls.Certificate{cert}, + ServerName: "127.0.0.1", + } + + return nil + }), + ).WaitUntilReady(ctx, target) +} + // MustConnectionString returns a connection string to open a new connection to CockroachDB // as described by [CockroachDBContainer.ConnectionString]. // It panics if an error occurs. @@ -159,7 +222,7 @@ func (c *CockroachDBContainer) connConfig(host string, port nat.Port) (*pgx.Conn sslMode := "disable" if c.tlsConfig != nil { - sslMode = "require" // We can't use "verify-full" as it might be a self signed cert. + sslMode = "verify-full" } params := url.Values{ "sslmode": []string{sslMode}, @@ -183,58 +246,17 @@ func (c *CockroachDBContainer) connConfig(host string, port nat.Port) (*pgx.Conn return cfg, nil } -// options sets the CockroachDBContainer options from a request. -func (c *CockroachDBContainer) options(req *testcontainers.GenericContainerRequest) { +// setOptions sets the CockroachDBContainer options from a request. +func (c *CockroachDBContainer) setOptions(req *testcontainers.GenericContainerRequest) { c.database = req.Env[envDatabase] c.user = req.Env[envUser] c.password = req.Env[envPassword] -} - -// WaitUntilReady implements the [wait.Strategy] interface for the CockroachDBContainer type. -// It waits for the CA, client cert and key for the configured user to be available in the container. -// This is defined on the container as it needs to know the user to wait for the correct client cert. -func (c *CockroachDBContainer) WaitUntilReady(ctx context.Context, target wait.StrategyTarget) error { - return wait.ForAll( - wait.ForFile(fileCACert).WithMatcher(func(r io.Reader) error { - buf, err := io.ReadAll(r) - if err != nil { - return fmt.Errorf("read CA cert: %w", err) - } - - if !c.certPool.AppendCertsFromPEM(buf) { - return errors.New("invalid CA cert") - } - - return nil - }), - wait.ForFile(certsDir+"/client."+c.user+".crt").WithMatcher(func(r io.Reader) error { - var err error - if c.clientCert, err = io.ReadAll(r); err != nil { - return fmt.Errorf("read client cert: %w", err) - } - - return nil - }), - wait.ForFile(certsDir+"/client."+c.user+".key").WithMatcher(func(r io.Reader) error { - var err error - if c.clientKey, err = io.ReadAll(r); err != nil { - return fmt.Errorf("read client key: %w", err) - } - - cert, err := tls.X509KeyPair(c.clientCert, c.clientKey) - if err != nil { - return fmt.Errorf("x509 key pair: %w", err) - } - - c.tlsConfig = &tls.Config{ - RootCAs: c.certPool, - Certificates: []tls.Certificate{cert}, - ServerName: "127.0.0.1", - } - - return nil - }), - ).WaitUntilReady(ctx, target) + for _, arg := range req.Cmd { + if arg == insecureFlag { + c.insecure = true + break + } + } } // Deprecated: use Run instead. @@ -258,10 +280,12 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize // [local cluster in docker]: https://www.cockroachlabs.com/docs/stable/start-a-local-cluster-in-docker-linux func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CockroachDBContainer, error) { ctr := &CockroachDBContainer{ - database: defaultDatabase, - user: defaultUser, - password: defaultPassword, - certPool: x509.NewCertPool(), + options: options{ + database: defaultDatabase, + user: defaultUser, + password: defaultPassword, + certPool: x509.NewCertPool(), + }, } req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ @@ -285,10 +309,9 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom memStorageFlag + defaultStoreSize, }, WaitingFor: wait.ForAll( - // TODO: check this works without any init files. - wait.ForLog("end running init files from /docker-entrypoint-initdb.d"), + wait.ForFile(cockroachDir+"/init_success"), wait.ForHTTP("/health").WithPort(defaultAdminPort), - ctr, + ctr, // Wait for the TLS files to be available if needed. wait.ForSQL(defaultSQLPort, "pgx/v5", func(host string, port nat.Port) string { connStr, err := ctr.connString(host, port) if err != nil { @@ -312,8 +335,8 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } } - // Extract the options from the request. - ctr.options(&req) + // Extract the options from the request so they can used by wait strategies and connection methods. + ctr.setOptions(&req) var err error ctr.Container, err = testcontainers.GenericContainer(ctx, req) diff --git a/modules/cockroachdb/cockroachdb_test.go b/modules/cockroachdb/cockroachdb_test.go index 91043529f9..d2f28d70cc 100644 --- a/modules/cockroachdb/cockroachdb_test.go +++ b/modules/cockroachdb/cockroachdb_test.go @@ -2,217 +2,85 @@ package cockroachdb_test import ( "context" + "database/sql" "testing" - "time" "github.com/jackc/pgx/v5" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cockroachdb" - "github.com/testcontainers/testcontainers-go/wait" ) -func TestCockroach_Insecure(t *testing.T) { - suite.Run(t, &AuthNSuite{}) -} +const testImage = "cockroachdb/cockroach:latest-v23.1" -func TestCockroach_NotRoot(t *testing.T) { - suite.Run(t, &AuthNSuite{ - opts: []testcontainers.ContainerCustomizer{ - cockroachdb.WithUser("test"), - }, - }) +func TestRun(t *testing.T) { + testContainer(t) } -func TestCockroach_Password(t *testing.T) { - suite.Run(t, &AuthNSuite{ - opts: []testcontainers.ContainerCustomizer{ - cockroachdb.WithUser("foo"), - cockroachdb.WithPassword("bar"), - }, - }) +func TestRun_WithAllOptions(t *testing.T) { + testContainer(t, + cockroachdb.WithDatabase("testDatabase"), + cockroachdb.WithStoreSize("50%"), + cockroachdb.WithUser("testUser"), + cockroachdb.WithPassword("testPassword"), + cockroachdb.WithNoClusterDefaults(), + cockroachdb.WithInitScripts("testdata/__init.sql"), + // WithInsecure is not present as it is incompatible with WithPassword. + ) } -func TestCockroach_TLS(t *testing.T) { - tlsCfg, err := cockroachdb.NewTLSConfig() - require.NoError(t, err) - - suite.Run(t, &AuthNSuite{ - opts: []testcontainers.ContainerCustomizer{ - tlsCfg, - }, - }) +func TestRun_WithInsecureAndPassword(t *testing.T) { + _, err := cockroachdb.Run(context.Background(), testImage, + cockroachdb.WithPassword("testPassword"), + cockroachdb.WithInsecure(), + ) + require.Error(t, err) + + // Check order does not matter. + _, err = cockroachdb.Run(context.Background(), testImage, + cockroachdb.WithInsecure(), + cockroachdb.WithPassword("testPassword"), + ) + require.Error(t, err) } -// TODO: remove this tests, its just a simple example for speed up the development. -func TestTLS(t *testing.T) { - tlsCfg, err := cockroachdb.NewTLSConfig() - require.NoError(t, err) - +// testContainer runs a CockroachDB container and validates its functionality. +func testContainer(t *testing.T, opts ...testcontainers.ContainerCustomizer) { ctx := context.Background() - - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", tlsCfg) + ctr, err := cockroachdb.Run(ctx, testImage, opts...) testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) require.NotNil(t, ctr) -} -func TestNoTLS(t *testing.T) { - ctx := context.Background() - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1") - testcontainers.CleanupContainer(t, ctr) + // Check a raw connection with a ping. + cfg, err := ctr.ConnectionConfig(ctx) require.NoError(t, err) - require.NotNil(t, ctr) -} -func TestUser(t *testing.T) { - ctx := context.Background() - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", cockroachdb.WithUser("test")) - testcontainers.CleanupContainer(t, ctr) + conn, err := pgx.ConnectConfig(ctx, cfg) require.NoError(t, err) - require.NotNil(t, ctr) -} - -type AuthNSuite struct { - suite.Suite - opts []testcontainers.ContainerCustomizer -} - -func (suite *AuthNSuite) TestConnectionString() { - ctx := context.Background() - - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().NoError(err) -} - -func (suite *AuthNSuite) TestPing() { - ctx := context.Background() - - inputs := []struct { - name string - opts []testcontainers.ContainerCustomizer - }{ - { - name: "defaults", - // opts: suite.opts - }, - { - name: "database", - opts: []testcontainers.ContainerCustomizer{ - cockroachdb.WithDatabase("test"), - }, - }, - } - - for _, input := range inputs { - suite.Run(input.name, func() { - opts := suite.opts - opts = append(opts, input.opts...) - - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().NoError(err) - - conn, err := conn(ctx, ctr) - suite.Require().NoError(err) - defer conn.Close(ctx) - - err = conn.Ping(ctx) - suite.Require().NoError(err) - }) - } -} - -func (suite *AuthNSuite) TestQuery() { - ctx := context.Background() - - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().NoError(err) - - conn, err := conn(ctx, ctr) - suite.Require().NoError(err) - defer conn.Close(ctx) - - _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") - suite.Require().NoError(err) - - _, err = conn.Exec(ctx, "INSERT INTO test (id) VALUES (523123)") - suite.Require().NoError(err) - - var id int - err = conn.QueryRow(ctx, "SELECT id FROM test").Scan(&id) - suite.Require().NoError(err) - suite.Equal(523123, id) -} - -// TestWithWaitStrategyAndDeadline covers a previous regression, container creation needs to fail to cover that path. -func (suite *AuthNSuite) TestWithWaitStrategyAndDeadline() { - nodeStartUpCompleted := "node startup completed" - - suite.Run("Expected Failure To Run", func() { - ctx := context.Background() - - // This will never match a log statement - suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Millisecond*250, wait.ForLog("Won't Exist In Logs"))) - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().ErrorIs(err, context.DeadlineExceeded) - }) - - suite.Run("Expected Failure To Run But Would Succeed ", func() { - ctx := context.Background() - - // This will timeout as we didn't give enough time for intialization, but would have succeeded otherwise - suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Millisecond*20, wait.ForLog(nodeStartUpCompleted))) - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().ErrorIs(err, context.DeadlineExceeded) + t.Cleanup(func() { + require.NoError(t, conn.Close(ctx)) }) - suite.Run("Succeeds And Executes Commands", func() { - ctx := context.Background() - - // This will succeed - suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Second*60, wait.ForLog(nodeStartUpCompleted))) - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().NoError(err) - - conn, err := conn(ctx, ctr) - suite.Require().NoError(err) - defer conn.Close(ctx) - - _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") - suite.Require().NoError(err) - }) - - suite.Run("Succeeds And Executes Commands Waiting on HTTP Endpoint", func() { - ctx := context.Background() + err = conn.Ping(ctx) + require.NoError(t, err) - // This will succeed - suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Second*60, wait.ForHTTP("/health").WithPort("8080/tcp"))) - ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - testcontainers.CleanupContainer(suite.T(), ctr) - suite.Require().NoError(err) + // Check an SQL connection with a queries. + addr, err := ctr.ConnectionString(ctx) + require.NoError(t, err) - conn, err := conn(ctx, ctr) - suite.Require().NoError(err) - defer conn.Close(ctx) + db, err := sql.Open("pgx/v5", addr) + require.NoError(t, err) - _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") - suite.Require().NoError(err) - }) -} + _, err = db.ExecContext(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") + require.NoError(t, err) -func conn(ctx context.Context, container *cockroachdb.CockroachDBContainer) (*pgx.Conn, error) { - cfg, err := container.ConnectionConfig(ctx) - if err != nil { - return nil, err - } + _, err = db.ExecContext(ctx, "INSERT INTO test (id) VALUES (523123)") + require.NoError(t, err) - return pgx.ConnectConfig(ctx, cfg) + var id int + err = db.QueryRowContext(ctx, "SELECT id FROM test").Scan(&id) + require.NoError(t, err) + require.Equal(t, 523123, id) } diff --git a/modules/cockroachdb/examples_test.go b/modules/cockroachdb/examples_test.go index a182f56aca..a1259c218b 100644 --- a/modules/cockroachdb/examples_test.go +++ b/modules/cockroachdb/examples_test.go @@ -62,7 +62,7 @@ func ExampleRun() { // true } -func ExampleRun_withOptions() { +func ExampleRun_withInitOptions() { ctx := context.Background() cockroachdbContainer, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", @@ -126,6 +126,6 @@ func ExampleRun_withOptions() { // Output: // true // 00:00:05 - // 01:00:01 + // 00:00:50 // true } diff --git a/modules/cockroachdb/go.mod b/modules/cockroachdb/go.mod index fbc0fd6f7a..cf31a35616 100644 --- a/modules/cockroachdb/go.mod +++ b/modules/cockroachdb/go.mod @@ -41,7 +41,6 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mdelapenya/tlscert v0.1.0 github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index e8661eb69a..3877e20a9a 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -69,8 +69,6 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdelapenya/tlscert v0.1.0 h1:YTpF579PYUX475eOL+6zyEO3ngLTOUWck78NBuJVXaM= -github.com/mdelapenya/tlscert v0.1.0/go.mod h1:wrbyM/DwbFCeCeqdPX/8c6hNOqQgbf0rUDErE1uD+64= 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= diff --git a/modules/cockroachdb/options.go b/modules/cockroachdb/options.go index f1ca20e8db..5d3cc14c01 100644 --- a/modules/cockroachdb/options.go +++ b/modules/cockroachdb/options.go @@ -1,49 +1,57 @@ package cockroachdb import ( - "fmt" + "errors" "path/filepath" "strings" "github.com/testcontainers/testcontainers-go" ) +// errInsecureWithPassword is returned when trying to use insecure mode with a password. +var errInsecureWithPassword = errors.New("insecure mode cannot be used with a password") + // customizer is an interface for customizing a CockroachDB container. type customizer interface { customize(*CockroachDBContainer) error } -// WithDatabase sets the name of the database to use. -// This will be ignored if data exists in the `/cockroach/cockroach-data` directory within the container. +// WithDatabase sets the name of the database to create and use. +// This will be converted to lowercase as CockroachDB forces the database to be lowercase. +// The database creation will be skipped if data exists in the `/cockroach/cockroach-data` directory within the container. func WithDatabase(database string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { + // CockroachDB forces the database to be lowercase. + database = strings.ToLower(database) req.Env[envDatabase] = database return nil } } -// WithUser creates & sets the user to connect as. -// This will be ignored if data exists in the `/cockroach/cockroach-data` directory within the container. +// WithUser sets the name of the user to create and connect as. +// This will be converted to lowercase as CockroachDB forces the user to be lowercase. +// The user creation will be skipped if data exists in the `/cockroach/cockroach-data` directory within the container. func WithUser(user string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { - if user != defaultUser && req.Env[envOptionTLS] == "true" { - return fmt.Errorf("unsupported user %q with TLS, use %q", user, defaultUser) - } - + // CockroachDB forces the user to be lowercase. + user = strings.ToLower(user) req.Env[envUser] = user return nil } } -// WithPassword sets the password when using password authentication. -// This will be ignored if data exists in the `/cockroach/cockroach-data` directory within the container. +// WithPassword sets the password of the user to create and connect as. +// The user creation will be skipped if data exists in the `/cockroach/cockroach-data` directory within the container. func WithPassword(password string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { - req.Env[envPassword] = password - if password != "" { - req.Cmd = append(req.Cmd, "--accept-sql-without-tls") + for _, arg := range req.Cmd { + if arg == insecureFlag { + return errInsecureWithPassword + } } + req.Env[envPassword] = password + return nil } } @@ -95,7 +103,7 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { for i, script := range scripts { files[i] = testcontainers.ContainerFile{ HostFilePath: script, - ContainerFilePath: initDBPath + filepath.Base(script), + ContainerFilePath: initDBPath + "/" + filepath.Base(script), FileMode: 0o644, } } @@ -104,3 +112,16 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { return nil } } + +// WithInsecure enables insecure mode and disables TLS. +func WithInsecure() testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + if req.Env[envPassword] != "" { + return errInsecureWithPassword + } + + req.Cmd = append(req.Cmd, insecureFlag) + + return nil + } +} diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index bc6a849456..b9c4e9fdf2 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -102,6 +102,7 @@ func ExampleRun_withSSL() { defer os.RemoveAll(certDirs) // generates the CA certificate and the certificate + // exampleSelfSignedCert { caCert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "ca", Host: "localhost,127.0.0.1", @@ -112,7 +113,9 @@ func ExampleRun_withSSL() { log.Print("failed to generate CA certificate") return } + // } + // exampleSignSelfSignedCert { cert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "client", Host: "localhost,127.0.0.1", @@ -124,6 +127,7 @@ func ExampleRun_withSSL() { log.Print("failed to generate certificate") return } + // } sslSettings := rabbitmq.SSLSettings{ CACertFile: caCert.CertPath,