Skip to content

Commit

Permalink
Merge branch 'main' into v1
Browse files Browse the repository at this point in the history
* main:
  feat: add grafana-lgtm module (#2660)
  Added valkey module (#2639)
  fix: container.Endpoint and wait.FortHTTP to use lowest internal port (#2641)
  chore: test cleanups (#2657)
  docs: fix compilation of examples (#2656)
  feat: add custom container registry substitutor (#2647)
  fix: couchbase containers intermittently hang on startup (#2650)
  chore(deps): bump Ryuk to 0.8.1 (#2648)
  fix: retry on label error (#2644)
  perf: optimise docker authentication config lookup (#2646)
  • Loading branch information
mdelapenya committed Aug 5, 2024
2 parents 9df76db + 4c5f1bd commit 094d0b1
Show file tree
Hide file tree
Showing 40 changed files with 4,268 additions and 121 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, azurite, 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, vearch, weaviate]
module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, dolt, elasticsearch, gcloud, grafana-lgtm, 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, valkey, vault, vearch, weaviate]
uses: ./.github/workflows/ci-test-go.yml
with:
go-version: ${{ matrix.go-version }}
Expand Down
8 changes: 8 additions & 0 deletions .vscode/.testcontainers-go.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"name": "module / gcloud",
"path": "../modules/gcloud"
},
{
"name": "module / grafana-lgtm",
"path": "../modules/grafana-lgtm"
},
{
"name": "module / inbucket",
"path": "../modules/inbucket"
Expand Down Expand Up @@ -169,6 +173,10 @@
"name": "module / surrealdb",
"path": "../modules/surrealdb"
},
{
"name": "module / valkey",
"path": "../modules/valkey"
},
{
"name": "module / vault",
"path": "../modules/vault"
Expand Down
79 changes: 52 additions & 27 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"net/url"
"os"
"sync"

"github.com/cpuguy83/dockercfg"
"github.com/docker/docker/api/types/registry"
Expand Down Expand Up @@ -53,52 +54,76 @@ func defaultRegistry(ctx context.Context) string {
return info.IndexServerAddress
}

// authConfig represents the details of the auth config for a registry.
type authConfig struct {
key string
cfg registry.AuthConfig
}

// getDockerAuthConfigs returns a map with the auth configs from the docker config file
// using the registry as the key
func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
var getDockerAuthConfigs = sync.OnceValues(func() (map[string]registry.AuthConfig, error) {
cfg, err := getDockerConfig()
if err != nil {
return nil, err
}

cfgs := map[string]registry.AuthConfig{}
results := make(chan authConfig, len(cfg.AuthConfigs)+len(cfg.CredentialHelpers))
var wg sync.WaitGroup
wg.Add(len(cfg.AuthConfigs) + len(cfg.CredentialHelpers))
for k, v := range cfg.AuthConfigs {
ac := registry.AuthConfig{
Auth: v.Auth,
Email: v.Email,
IdentityToken: v.IdentityToken,
Password: v.Password,
RegistryToken: v.RegistryToken,
ServerAddress: v.ServerAddress,
Username: v.Username,
}
go func(k string, v dockercfg.AuthConfig) {
defer wg.Done()

ac := registry.AuthConfig{
Auth: v.Auth,
Email: v.Email,
IdentityToken: v.IdentityToken,
Password: v.Password,
RegistryToken: v.RegistryToken,
ServerAddress: v.ServerAddress,
Username: v.Username,
}

if v.Username == "" && v.Password == "" {
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
}

if v.Auth == "" {
ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
}
results <- authConfig{key: k, cfg: ac}
}(k, v)
}

// in the case where the auth field in the .docker/conf.json is empty, and the user has credential helpers registered
// the auth comes from there
for k := range cfg.CredentialHelpers {
go func(k string) {
defer wg.Done()

if v.Username == "" && v.Password == "" {
ac := registry.AuthConfig{}
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
}

if v.Auth == "" {
ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
}

cfgs[k] = ac
results <- authConfig{key: k, cfg: ac}
}(k)
}

// in the case where the auth field in the .docker/conf.json is empty, and the user has credential helpers registered
// the auth comes from there
for k := range cfg.CredentialHelpers {
ac := registry.AuthConfig{}
u, p, _ := dockercfg.GetRegistryCredentials(k)
ac.Username = u
ac.Password = p
go func() {
wg.Wait()
close(results)
}()

cfgs[k] = ac
for ac := range results {
cfgs[ac.key] = ac.cfg
}

return cfgs, nil
}
})

// getDockerConfig returns the docker config file. It will internally check, in this particular order:
// 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config
Expand Down
15 changes: 7 additions & 8 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,16 +278,15 @@ func (c *DockerContainer) Endpoint(ctx context.Context, proto string) (string, e
return "", err
}

ports := inspect.NetworkSettings.Ports

// get first port
var firstPort nat.Port
for p := range ports {
firstPort = p
break
// Get lowest numbered bound port.
var lowestPort nat.Port
for port := range inspect.NetworkSettings.Ports {
if lowestPort == "" || port.Int() < lowestPort.Int() {
lowestPort = port
}
}

return c.PortEndpoint(ctx, firstPort, proto)
return c.PortEndpoint(ctx, lowestPort, proto)
}

// Exec executes a command in the current container.
Expand Down
70 changes: 36 additions & 34 deletions docs/features/creating_container.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,50 +157,52 @@ import (
"log"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)

const (
reusableContainerName = "my_test_reusable_container"
)

ctx := context.Background()

n1, err := testcontainers.Run(ctx, testcontainers.Request{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
Name: reusableContainerName,
Started: true,
})
if err != nil {
log.Fatal(err)
}
defer n1.Terminate(ctx)
func main() {
ctx := context.Background()

copiedFileName := "hello_copy.sh"
err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700)
n1, err := testcontainers.Run(ctx, testcontainers.Request{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
Name: reusableContainerName,
Started: true,
})
if err != nil {
log.Fatal(err)
}
defer n1.Terminate(ctx)

if err != nil {
log.Fatal(err)
}
copiedFileName := "hello_copy.sh"
err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700)

n2, err := testcontainers.Run(ctx, testcontainers.Request{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
Name: reusableContainerName,
Started: true,
Reuse: true,
})
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}

c, _, err := n2.Exec(ctx, []string{"bash", copiedFileName})
if err != nil {
log.Fatal(err)
}
fmt.Println(c)
n2, err := testcontainers.Run(ctx, testcontainers.Request{
Image: "nginx:1.17.6",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForListeningPort("80/tcp"),
Name: reusableContainerName,
Started: true,
Reuse: true,
})
if err != nil {
log.Fatal(err)
}

c, _, err := n2.Exec(ctx, []string{"bash", copiedFileName})
if err != nil {
log.Fatal(err)
}
fmt.Println(c)
```
## Parallel running
Expand Down
10 changes: 5 additions & 5 deletions docs/features/wait/host_port.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The host-port wait strategy will check if the container is listening to a specific port and allows to set the following conditions:

- a port exposed by the container. The port and protocol to be used, which is represented by a string containing the port number and protocol in the format "80/tcp".
- alternatively, wait for the first exposed port in the container.
- alternatively, wait for the lowest exposed port in the container.
- the startup timeout to be used, default is 60 seconds.
- the poll interval to be used, default is 100 milliseconds.

Expand All @@ -19,9 +19,9 @@ req := ContainerRequest{
}
```

## First exposed port in the container
## Lowest exposed port in the container

The wait strategy will use the first exposed port from the container configuration.
The wait strategy will use the lowest exposed port from the container configuration.

```golang
req := ContainerRequest{
Expand All @@ -30,12 +30,12 @@ req := ContainerRequest{
}
```

Said that, it could be the case that the container request included ports to be exposed. Therefore using `wait.ForExposedPort` will wait for the first exposed port in the request, because the container configuration retrieved from Docker will already include them.
Said that, it could be the case that the container request included ports to be exposed. Therefore using `wait.ForExposedPort` will wait for the lowest exposed port in the request, because the container configuration retrieved from Docker will already include them.

```golang
req := ContainerRequest{
Image: "docker.io/nginx:alpine",
ExposedPorts: []string{"80/tcp", "9080/tcp"},
WaitingFor: wait.ForExposedPort(),
}
```
```
2 changes: 1 addition & 1 deletion docs/features/wait/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The HTTP wait strategy will check the result of an HTTP(S) request against the container and allows to set the following conditions:

- the port to be used. If no port is passed, it will use the first exposed port in the image.
- the port to be used. If no port is passed, it will use the lowest exposed port in the image.
- the path to be used.
- the HTTP method to be used.
- the HTTP request body to be sent.
Expand Down
Loading

0 comments on commit 094d0b1

Please sign in to comment.