Skip to content

Commit

Permalink
Merge branch 'main' into support-buildkit
Browse files Browse the repository at this point in the history
* main:
  support Dolt (testcontainers#2177)
  chore: create TLS certs in a consistent manner (testcontainers#2478)
  chore(deps): bump idna from 3.6 to 3.7 (testcontainers#2480)
  • Loading branch information
mdelapenya committed Apr 15, 2024
2 parents cd77ebb + b5f1e13 commit d67ece5
Show file tree
Hide file tree
Showing 35 changed files with 1,143 additions and 299 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
matrix:
go-version: [1.21.x, 1.x]
platform: [ubuntu-latest]
module: [artemis, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, elasticsearch, gcloud, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, vault, weaviate]
module: [artemis, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, dolt, elasticsearch, gcloud, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, vault, weaviate]
uses: ./.github/workflows/ci-test-go.yml
with:
go-version: ${{ matrix.go-version }}
Expand Down
4 changes: 4 additions & 0 deletions .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
"name": "module / couchbase",
"path": "../modules/couchbase"
},
{
"name": "module / dolt",
"path": "../modules/dolt"
},
{
"name": "module / elasticsearch",
"path": "../modules/elasticsearch"
Expand Down
7 changes: 4 additions & 3 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion container.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (c *ContainerRequest) GetContext() (io.Reader, error) {

if dockerIgnoreExists {
// only add .dockerignore if it exists
includes = append(includes, filepath.Join(".dockerignore"))
includes = append(includes, ".dockerignore")
}

includes = append(includes, c.GetDockerfile())
Expand Down
4 changes: 3 additions & 1 deletion docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,9 @@ func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byt

func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func(tw io.Writer) error, fileContentSize int64, containerFilePath string, fileMode int64) error {
buffer, err := tarFile(containerFilePath, fileContent, fileContentSize, fileMode)
if err != nil {
return err
}

err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{})
if err != nil {
Expand Down Expand Up @@ -1531,7 +1534,6 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl
Attachable: true,
Labels: core.DefaultLabels(core.SessionID()),
})

if err != nil {
return "", err
}
Expand Down
5 changes: 3 additions & 2 deletions docker_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/docker/docker/pkg/stdcopy"
"github.com/stretchr/testify/require"

tcexec "github.com/testcontainers/testcontainers-go/exec"
)

Expand Down Expand Up @@ -133,6 +134,6 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) {
require.NotNil(t, stdout)
require.NotNil(t, stderr)

require.Equal(t, stdout.String(), "stdout\n")
require.Equal(t, stderr.String(), "stderr\n")
require.Equal(t, "stdout\n", stdout.String())
require.Equal(t, "stderr\n", stderr.String())
}
17 changes: 17 additions & 0 deletions docs/features/tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TLS certificates

Interacting with services that require TLS certificates is a common issue when working with containers. You can create one or more on-the-fly certificates in order to communicate with your services.

_Testcontainers for Go_ uses a library to generate certificates on-the-fly. This library is called [tlscert](https://github.com/mdelapenya/tlscert).

### Examples

In the following example we are going to start an HTTP server with a self-signed certificate.
It exposes one single handler that will return a simple message when accessed.
The example will also create a client that will connect to the server using the generated certificate,
demonstrating how to use the generated certificate to communicate with a service.

<!--codeinclude-->
[Create a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSelfSignedCert
[Sign a self-signed certificate](../../modules/cockroachdb/certs.go) inside_block:exampleSignSelfSignedCert
<!--/codeinclude-->
81 changes: 81 additions & 0 deletions docs/modules/dolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Dolt

Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>

## Introduction

The Testcontainers module for Dolt.

## Adding this module to your project dependencies

Please run the following command to add the Dolt module to your Go dependencies:

```
go get github.com/testcontainers/testcontainers-go/modules/dolt
```

## Usage example

<!--codeinclude-->
[Creating a Dolt container](../../modules/dolt/examples_test.go) inside_block:runDoltContainer
<!--/codeinclude-->

## Module reference

The Dolt module exposes one entrypoint function to create the Dolt container, and this function receives two parameters:

```golang
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error)
```

- `context.Context`, the Go context.
- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.

### Container Options

When starting the Dolt container, you can pass options in a variadic way to configure it.

#### Image

If you need to set a different Dolt Docker image, you can use `testcontainers.WithImage` with a valid Docker image
for Dolt. E.g. `testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4")`.

{% include "../features/common_functional_options.md" %}

#### Set username, password and database name

If you need to set a different database, and its credentials, you can use `WithUsername`, `WithPassword`, `WithDatabase`
options.

!!!info
The default values for the username is `root`, for password is `test` and for the default database name is `test`.

#### Init Scripts

If you would like to perform DDL or DML operations in the Dolt container, add one or more `*.sql`, `*.sql.gz`, or `*.sh`
scripts to the container request, using the `WithScripts(scriptPaths ...string)`. Those files will be copied under `/docker-entrypoint-initdb.d`.

#### Clone from remotes

If you would like to clone data from a remote into the Dolt container, add an `*.sh`
scripts to the container request, using the `WithScripts(scriptPaths ...string)`. Additionally, use `WithDoltCloneRemoteUrl(url string)` to specify
the remote to clone, and use `WithDoltCredsPublicKey(key string)` along with `WithCredsFile(credsFile string)` to authorize the Dolt container to clone from the remote.

<!--codeinclude-->
[Example of Clone script](../../modules/dolt/testdata/clone-db.sh)
<!--/codeinclude-->

#### Custom configuration

If you need to set a custom configuration, you can use `WithConfigFile` option to pass the path to a custom configuration file.

### Container Methods

#### ConnectionString

This method returns the connection string to connect to the Dolt container, using the default `3306` port.
It's possible to pass extra parameters to the connection string, e.g. `tls=skip-verify` or `application_name=myapp`, in a variadic way.
<!--codeinclude-->
[Get connection string](../../modules/dolt/dolt_test.go) inside_block:connectionString
<!--/codeinclude-->
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ nav:
- features/files_and_mounts.md
- features/creating_networks.md
- features/networking.md
- features/tls.md
- features/garbage_collector.md
- features/build_from_dockerfile.md
- features/docker_auth.md
Expand All @@ -70,6 +71,7 @@ nav:
- modules/cockroachdb.md
- modules/consul.md
- modules/couchbase.md
- modules/dolt.md
- modules/elasticsearch.md
- modules/gcloud.md
- modules/inbucket.md
Expand Down
175 changes: 42 additions & 133 deletions modules/cockroachdb/certs.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package cockroachdb

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"fmt"
"net"
"time"

"github.com/mdelapenya/tlscert"
)

type TLSConfig struct {
Expand All @@ -21,138 +19,49 @@ type TLSConfig struct {

// NewTLSConfig creates a new TLSConfig capable of running CockroachDB & connecting over TLS.
func NewTLSConfig() (*TLSConfig, error) {
caCert, caKey, err := generateCA()
if err != nil {
return nil, err
// 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, fmt.Errorf("failed to generate CA certificate")
}

nodeCert, nodeKey, err := generateNode(caCert, caKey)
if err != nil {
return nil, err
// }

// 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, fmt.Errorf("failed to generate node certificate")
}

clientCert, clientKey, err := generateClient(caCert, caKey)
if err != nil {
return nil, err
// }

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, fmt.Errorf("failed to generate client certificate")
}

return &TLSConfig{
CACert: caCert,
NodeCert: nodeCert,
NodeKey: nodeKey,
ClientCert: clientCert,
ClientKey: clientKey,
CACert: caCert.Cert,
NodeCert: nodeCert.Bytes,
NodeKey: nodeCert.KeyBytes,
ClientCert: clientCert.Bytes,
ClientKey: clientCert.KeyBytes,
}, nil
}

func generateCA() (*x509.Certificate, *rsa.PrivateKey, error) {
template := x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
CommonName: "Cockroach Test CA",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
}

caPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

caBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, caPrivKey.Public(), caPrivKey)
if err != nil {
return nil, nil, err
}

caCert, err := x509.ParseCertificate(caBytes)
if err != nil {
return nil, nil, err
}

return caCert, caPrivKey, nil
}

func generateNode(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) {
template := x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
CommonName: "node",
},
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{
net.IPv4(127, 0, 0, 1),
net.IPv6loopback,
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}

certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey)
if err != nil {
return nil, nil, err
}

cert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
certKey := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})

return cert, certKey, nil
}

func generateClient(caCert *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) {
template := x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
CommonName: defaultUser,
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}

certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

certBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certPrivKey.Public(), caKey)
if err != nil {
return nil, nil, err
}

cert := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
certKey := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})

return cert, certKey, nil
}
1 change: 1 addition & 0 deletions modules/cockroachdb/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ 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
Expand Down
Loading

0 comments on commit d67ece5

Please sign in to comment.