diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 04930115d6..5952d31d52 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/go { "name": "Go", - "image": "mcr.microsoft.com/devcontainers/go:0-1.21-bullseye", + "image": "mcr.microsoft.com/devcontainers/go:1.22-bookworm", // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 1fe773fb46..2e5efff2b1 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -26,3 +26,25 @@ categories: - 'test flakiness' - title: đŸ“Ļ Dependency updates label: 'dependencies' +autolabeler: + - label: 'breaking change' + title: + - '/^[a-z]+(\(.+\))?!\:/' + - label: 'security' + title: + - '/^security(\(.+\))?!?\:/' + - label: 'feature' + title: + - '/^feat(\(.+\))?!?\:/' + - label: 'bug' + title: + - '/^(fix)(\(.+\))?!?\:/' + - label: 'documentation' + title: + - '/^docs(\(.+\))?!?\:/' + - label: 'chore' + title: + - '/^chore(\(.+\))?!?\:/' + - label: 'dependencies' + title: + - '/^deps(\(.+\))?!?\:/' diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index 36f1e6de4f..82be78435f 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -55,29 +55,26 @@ jobs: steps: - name: Setup rootless Docker if: ${{ inputs.rootless-docker }} - uses: ScribeMD/rootless-docker@6bd157a512c2fafa4e0243a8aa87d964eb890886 # v0.2.2 - - - name: Remove Docker root socket - if: ${{ inputs.rootless-docker }} - run: sudo rm -rf /var/run/docker.sock + uses: docker/setup-docker-action@01efb57f882e3b1a22e7cf3501dbe51287b0ecb4 # v4 + with: + rootless: true - name: Check out code into the Go module directory - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - name: Set up Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5 with: go-version: '${{ inputs.go-version }}' cache-dependency-path: '${{ inputs.project-directory }}/go.sum' id: go - name: golangci-lint - # TODO: Remove each example/module once it passes the golangci-lint - if: ${{ inputs.platform == 'ubuntu-latest' && inputs.go-version == '1.20.x' }} - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3 + if: ${{ inputs.platform == 'ubuntu-latest' }} + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.55.2 + version: v1.61.0 # Optional: working directory, useful for monorepos working-directory: ${{ inputs.project-directory }} # Optional: golangci-lint command line arguments. @@ -86,13 +83,25 @@ jobs: # takes precedence over all other caching options. skip-cache: true + - name: generate + if: ${{ inputs.platform == 'ubuntu-latest' }} + working-directory: ./${{ inputs.project-directory }} + shell: bash + run: | + make generate + git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] + - name: modVerify working-directory: ./${{ inputs.project-directory }} run: go mod verify - name: modTidy + if: ${{ inputs.platform == 'ubuntu-latest' }} working-directory: ./${{ inputs.project-directory }} - run: make tools-tidy + shell: bash + run: | + make tidy + git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] - name: ensure compilation working-directory: ./${{ inputs.project-directory }} @@ -108,11 +117,18 @@ jobs: timeout-minutes: 30 run: make test-unit + - name: Set sonar artifact name + # For the core library, where the project directory is '.', we'll use "core" as artifact name. + # For the modules, we'll remove the slashes, keeping the name of the module + if: ${{ github.ref_name == 'main' && github.repository_owner == 'testcontainers' && inputs.platform == 'ubuntu-latest' && inputs.run-tests && !inputs.rootless-docker && !inputs.ryuk-disabled }} + run: | + echo "ARTIFACT_NAME=$(basename ${{ inputs.project-directory == '.' && 'core' || inputs.project-directory }})-${{ inputs.go-version }}-${{ inputs.platform }}" >> $GITHUB_ENV + - name: Upload SonarCloud files - if: ${{ github.ref_name == 'main' && github.repository_owner == 'testcontainers' && inputs.run-tests && !inputs.rootless-docker }} - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 + if: ${{ github.ref_name == 'main' && github.repository_owner == 'testcontainers' && inputs.platform == 'ubuntu-latest' && inputs.run-tests && !inputs.rootless-docker && !inputs.ryuk-disabled }} + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: sonarcloud + name: sonarcloud-${{ env.ARTIFACT_NAME }} path: | ./sonar-project.properties ${{ inputs.project-directory }}/TEST-unit.xml @@ -123,7 +139,7 @@ jobs: ./scripts/check_environment.sh - name: Test Summary - uses: test-summary/action@fee35d7df20790255fe6aa92cf0f6d28092ecf2f # v2 + uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 with: paths: "**/${{ inputs.project-directory }}/TEST-unit*.xml" if: always() diff --git a/.github/workflows/ci-windows-trigger.yml b/.github/workflows/ci-windows-trigger.yml index 696549c4f0..540ab221db 100644 --- a/.github/workflows/ci-windows-trigger.yml +++ b/.github/workflows/ci-windows-trigger.yml @@ -11,11 +11,11 @@ jobs: windows-test-command-trigger: permissions: pull-requests: write # for peter-evans/slash-command-dispatch to create PR reaction - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Trigger windows-test command - uses: peter-evans/slash-command-dispatch@f996d7b7aae9059759ac55e978cff76d91853301 # v3.0.2 + uses: peter-evans/slash-command-dispatch@13bc09769d122a64f75aa5037256f6f2d78be8c4 # v4.0.0 with: token: ${{ secrets.WINDOWS_WORKERS_TOKEN }} # The command to trigger the pipeline: e.g. /windows-test diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 9f868aa8f0..65a8cf573d 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -6,10 +6,7 @@ on: jobs: test-windows: - # At the moment, we are running a self-hosted runner on Windows 2022. - runs-on: [self-hosted, Windows, X64] - strategy: - fail-fast: false + runs-on: [self-hosted, Windows, X64, desktop-windows-intel] timeout-minutes: 30 steps: - name: Create pending status @@ -27,14 +24,14 @@ jobs: }) - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} ref: ${{ github.event.client_payload.pull_request.head.ref }} - name: Set up Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5 + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5 with: go-version-file: go.mod id: go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97b8c4d91c..10fb7f4949 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: test: strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest, macos-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -44,7 +44,7 @@ jobs: name: "Test with reaper off" strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] uses: ./.github/workflows/ci-test-go.yml with: go-version: ${{ matrix.go-version }} @@ -61,7 +61,7 @@ jobs: name: "Test with Rootless Docker" strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -76,7 +76,7 @@ jobs: test-module-generator: strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -92,9 +92,9 @@ jobs: needs: test strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest] - 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] + module: [artemis, azurite, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, databend, dolt, dynamodb, elasticsearch, etcd, gcloud, grafana-lgtm, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, meilisearch, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, valkey, vault, vearch, weaviate, yugabytedb] uses: ./.github/workflows/ci-test-go.yml with: go-version: ${{ matrix.go-version }} @@ -112,7 +112,7 @@ jobs: module: [nginx, toxiproxy] uses: ./.github/workflows/ci-test-go.yml with: - go-version: "1.21.x" + go-version: "1.22.x" fail-fast: true platform: 'ubuntu-latest' project-directory: examples/${{ matrix.module }} @@ -129,14 +129,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: # Disabling shallow clone is recommended for improving relevancy of reporting fetch-depth: 0 - - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: sonarcloud + pattern: sonarcloud-* + merge-multiple: true - name: Analyze with SonarCloud uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8919663257..15f56f2eb5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,11 +49,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -67,7 +67,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -80,6 +80,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/conventions.yml b/.github/workflows/conventions.yml new file mode 100644 index 0000000000..ef858a42b6 --- /dev/null +++ b/.github/workflows/conventions.yml @@ -0,0 +1,49 @@ +name: "Enforce conventions" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + +permissions: + pull-requests: read + +jobs: + lint-pr: + name: Validate PR title follows Conventional Commits + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # We may not need a scope on every commit (i.e. repo-level change). + # + # feat!: read config consistenly + # feat(redis): support for clustering + # chore(redis): update tests + # fix(redis): trim connection string + # ^ ^ ^ + # | | |__ Subject + # | |_______ Scope + # |____________ Type: it can end with a ! to denote a breaking change. + requireScope: false + # Scope should be lowercase. + disallowScopes: | + [A-Z]+ + # ensures the subject doesn't start with an uppercase character. + subjectPattern: ^(?![A-Z]).+$ + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + doesn't start with an uppercase character. + types: | + security + fix + feat + docs + chore + deps diff --git a/.github/workflows/docker-moby-latest.yml b/.github/workflows/docker-moby-latest.yml new file mode 100644 index 0000000000..dc06fb49e7 --- /dev/null +++ b/.github/workflows/docker-moby-latest.yml @@ -0,0 +1,77 @@ +name: Tests against Latest Docker Moby + +on: + schedule: + # nightly build, at 23:59 CEST + - cron: '59 23 * * *' + +jobs: + test_latest_moby: + strategy: + matrix: + rootless-docker: [true, false] + containerd-integration: [true, false] + + name: "Core tests using latest moby/moby" + runs-on: 'ubuntu-latest' + continue-on-error: true + steps: + - name: Set the Docker Install type + run: | + echo "docker_install_type=${{ matrix.rootless-docker == true && 'Rootless' || 'Rootful' }}" >> "$GITHUB_ENV" + echo "containerd_integration=${{ matrix.containerd-integration == true && 'containerd' || '' }}" >> "$GITHUB_ENV" + + - name: Check out code into the Go module directory + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + + - name: Set up Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + id: go + + - name: modVerify + run: go mod verify + + - name: modTidy + run: go mod tidy + + - name: Install Nightly Docker + uses: docker/setup-docker-action@master + with: + rootless: ${{ matrix.rootless-docker }} + version: type=image,tag=master + daemon-config: | + { + "debug": true, + "features": { + "containerd-snapshotter": ${{ matrix.containerd-integration }} + } + } + + - name: go test + timeout-minutes: 30 + run: make test-unit + + - name: Create slack payload file + if: failure() + run: | + cat < ./payload-slack-content.json + { + "tc_project": "testcontainers-go", + "tc_docker_install_type": "${docker_install_type}", + "tc_containerd_integration": "${containerd_integration}", + "tc_github_action_url": "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}", + "tc_github_action_status": "FAILED", + "tc_slack_channel_id": "${{ secrets.SLACK_DOCKER_LATEST_CHANNEL_ID }}" + } + + - name: Notify to Slack on failures + if: failure() + id: slack + uses: slackapi/slack-github-action@v1.26.0 + with: + payload-file-path: "./payload-slack-content.json" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCKER_LATEST_WEBHOOK }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 29c2b72ee0..43cb9ad9d2 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -5,6 +5,10 @@ on: push: branches: - main + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] permissions: contents: read @@ -16,6 +20,8 @@ jobs: pull-requests: write # for release-drafter/release-drafter to add label to PR runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5.19.0 + - uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v5.19.0 + with: + disable-autolabeler: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 73c657d3c4..51b8a535c5 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -20,12 +20,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -43,7 +43,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: SARIF file path: results.sarif @@ -51,6 +51,6 @@ jobs: # required for Code scanning alerts - name: "Upload SARIF results to code scanning" - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index b6ba580a02..e529356359 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,9 @@ src/pip-delete-this-directory.txt TEST-*.xml -**/go.work \ No newline at end of file +tcvenv + +**/go.work + +# VS Code settings +.vscode diff --git a/.golangci.yml b/.golangci.yml index 1791b9caac..d708f003e3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,15 +1,21 @@ linters: enable: + - errcheck - errorlint - gci - gocritic - gofumpt - misspell - - nonamedreturns + - nolintlint + - nakedret + - perfsprint - testifylint - - errcheck + - thelper + - usestdlibvars linters-settings: + nakedret: + max-func-lines: 0 errorlint: # Check whether fmt.Errorf uses the %w verb for formatting errors. # See the https://github.com/polyfloyd/go-errorlint for caveats. @@ -29,16 +35,6 @@ linters-settings: disable: - float-compare - go-require - enable: - - bool-compare - - compares - - empty - - error-is-as - - error-nil - - expected-actual - - len - - require-error - - suite-dont-use-pkg - - suite-extra-assert-call + enable-all: true run: timeout: 5m diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000000..2f96829f21 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,11 @@ +quiet: True +disable-version-string: True +with-expecter: True +mockname: "mock{{.InterfaceName}}" +filename: "{{ .InterfaceName | lower }}_mock_test.go" +outpkg: "{{.PackageName}}_test" +dir: "{{.InterfaceDir}}" +packages: + github.com/testcontainers/testcontainers-go/wait: + interfaces: + StrategyTarget: diff --git a/.vscode/.testcontainers-go.code-workspace b/.vscode/.testcontainers-go.code-workspace index 627ecbb03a..bc7e7aead9 100644 --- a/.vscode/.testcontainers-go.code-workspace +++ b/.vscode/.testcontainers-go.code-workspace @@ -17,6 +17,10 @@ "name": "module / artemis", "path": "../modules/artemis" }, + { + "name": "module / azurite", + "path": "../modules/azurite" + }, { "name": "module / cassandra", "path": "../modules/cassandra" @@ -45,18 +49,34 @@ "name": "module / couchbase", "path": "../modules/couchbase" }, + { + "name": "module / databend", + "path": "../modules/databend" + }, { "name": "module / dolt", "path": "../modules/dolt" }, + { + "name": "module / dynamodb", + "path": "../modules/dynamodb" + }, { "name": "module / elasticsearch", "path": "../modules/elasticsearch" }, + { + "name": "module / etcd", + "path": "../modules/etcd" + }, { "name": "module / gcloud", "path": "../modules/gcloud" }, + { + "name": "module / grafana-lgtm", + "path": "../modules/grafana-lgtm" + }, { "name": "module / inbucket", "path": "../modules/inbucket" @@ -85,6 +105,10 @@ "name": "module / mariadb", "path": "../modules/mariadb" }, + { + "name": "module / meilisearch", + "path": "../modules/meilisearch" + }, { "name": "module / milvus", "path": "../modules/milvus" @@ -165,14 +189,26 @@ "name": "module / surrealdb", "path": "../modules/surrealdb" }, + { + "name": "module / valkey", + "path": "../modules/valkey" + }, { "name": "module / vault", "path": "../modules/vault" }, + { + "name": "module / vearch", + "path": "../modules/vearch" + }, { "name": "module / weaviate", "path": "../modules/weaviate" }, + { + "name": "module / yugabytedb", + "path": "../modules/yugabytedb" + }, { "name": "modulegen", "path": "../modulegen" diff --git a/Makefile b/Makefile index 2674087e18..9c5a968ee7 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,44 @@ include ./commons-test.mk +.PHONY: lint-all +lint-all: + $(MAKE) lint + $(MAKE) -C modulegen lint + $(MAKE) -C examples lint-examples + $(MAKE) -C modules lint-modules + .PHONY: test-all test-all: tools test-tools test-unit .PHONY: test-examples test-examples: @echo "Running example tests..." - make -C examples test + $(MAKE) -C examples test .PHONY: tidy-all tidy-all: - make -C examples tidy-examples - make -C modules tidy-modules + $(MAKE) tidy + $(MAKE) -C examples tidy-examples + $(MAKE) -C modules tidy-modules + +## -------------------------------------- + +TCENV=tcvenv +PYTHONBIN=./$(TCENV)/bin + +tcvenv: tcvenv/touchfile + +tcvenv/touchfile: + @echo "Creating docs $(TCENV)..." + test -d $(TCENV) || python3 -m venv $(TCENV) + @echo "Installing requirements..." + . $(PYTHONBIN)/activate; pip install -Ur requirements.txt + touch $(TCENV)/touchfile + +clean-docs: + @echo "Destroying docs $(TCENV)..." + rm -rf $(TCENV) + +.PHONY: serve-docs +serve-docs: tcvenv + . $(PYTHONBIN)/activate; $(PYTHONBIN)/mkdocs serve diff --git a/Pipfile b/Pipfile index ffa08b9757..2648278724 100644 --- a/Pipfile +++ b/Pipfile @@ -8,9 +8,9 @@ verify_ssl = true [packages] mkdocs = "==1.5.3" mkdocs-codeinclude-plugin = "==0.2.1" -mkdocs-include-markdown-plugin = "==6.0.4" -mkdocs-material = "==9.5.13" -mkdocs-markdownextradata-plugin = "==0.2.5" +mkdocs-include-markdown-plugin = "==6.2.2" +mkdocs-material = "==9.5.18" +mkdocs-markdownextradata-plugin = "==0.2.6" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 9faa900fdc..9a2f6d24c8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7e2177af09f35451787a76b01cf4552aa401ce8f0cc9739c329d98526c54a3ec" + "sha256": "0411eac13d1b06b42671b8a654fb269eb0c329d9a3d41f669ccf7b653ef8ad32" }, "pipfile-spec": 6, "requires": { @@ -26,19 +26,20 @@ }, "bracex": { "hashes": [ - "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb", - "sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418" + "sha256:0725da5045e8d37ea9592ab3614d8b561e22c3c5fde3964699be672e072ab611", + "sha256:d2fcf4b606a82ac325471affe1706dd9bbaa3536c91ef86a31f6b766f3dad1d0" ], "markers": "python_version >= '3.8'", - "version": "==2.4" + "version": "==2.5" }, "certifi": { "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], + "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==2024.2.2" + "version": "==2024.7.4" }, "charset-normalizer": { "hashes": [ @@ -164,33 +165,32 @@ "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], - "index": "pypi", "markers": "python_version >= '3.5'", "version": "==3.7" }, "importlib-metadata": { "hashes": [ - "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792", - "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100" + "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", + "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5" ], "markers": "python_version < '3.10'", - "version": "==7.0.2" + "version": "==8.4.0" }, "jinja2": { "hashes": [ - "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", - "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" ], "markers": "python_version >= '3.7'", - "version": "==3.1.3" + "version": "==3.1.4" }, "markdown": { "hashes": [ - "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd", - "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8" + "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", + "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803" ], "markers": "python_version >= '3.8'", - "version": "==3.5.2" + "version": "==3.7" }, "markupsafe": { "hashes": [ @@ -286,29 +286,30 @@ }, "mkdocs-include-markdown-plugin": { "hashes": [ - "sha256:523c9c3a1d6a517386dc11bf60b0c0c564af1071bb6de8d213106d54f752dcc1", - "sha256:e7b8b5ecc41d6a3e16969cff3725ec3a391b68e9dfe1a4b4e36a8508becda835" + "sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7", + "sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==6.0.4" + "version": "==6.2.2" }, "mkdocs-markdownextradata-plugin": { "hashes": [ - "sha256:9c562e8fe375647d5692d11dfe369a7bdd50302174d35995fce2aeca58036ec6" + "sha256:34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242", + "sha256:4aed9b43b8bec65b02598387426ca4809099ea5f5aa78bf114f3296fd46686b5" ], "index": "pypi", - "markers": "python_version not in '3.0, 3.1, 3.2, 3.3' and python_full_version >= '2.7.9'", - "version": "==0.2.5" + "markers": "python_version >= '3.6'", + "version": "==0.2.6" }, "mkdocs-material": { "hashes": [ - "sha256:5cbe17fee4e3b4980c8420a04cc762d8dc052ef1e10532abd4fce88e5ea9ce6a", - "sha256:d8e4caae576312a88fd2609b81cf43d233cdbe36860d67a68702b018b425bd87" + "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4", + "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==9.5.13" + "version": "==9.5.18" }, "mkdocs-material-extensions": { "hashes": [ @@ -320,11 +321,11 @@ }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "markers": "python_version >= '3.7'", - "version": "==23.2" + "markers": "python_version >= '3.8'", + "version": "==24.1" }, "paginate": { "hashes": [ @@ -342,11 +343,11 @@ }, "platformdirs": { "hashes": [ - "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", - "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", + "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" ], "markers": "python_version >= '3.8'", - "version": "==4.2.0" + "version": "==4.2.2" }, "pygments": { "hashes": [ @@ -358,11 +359,11 @@ }, "pymdown-extensions": { "hashes": [ - "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584", - "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4" + "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940", + "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb" ], "markers": "python_version >= '3.8'", - "version": "==10.7.1" + "version": "==10.8.1" }, "python-dateutil": { "hashes": [ @@ -382,60 +383,62 @@ }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "pyyaml-env-tag": { "hashes": [ @@ -447,110 +450,97 @@ }, "regex": { "hashes": [ - "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5", - "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770", - "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc", - "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105", - "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d", - "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b", - "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9", - "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630", - "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6", - "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c", - "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482", - "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6", - "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a", - "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80", - "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5", - "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1", - "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f", - "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf", - "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb", - "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2", - "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347", - "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20", - "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060", - "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5", - "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73", - "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f", - "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d", - "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3", - "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae", - "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4", - "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2", - "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457", - "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c", - "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4", - "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87", - "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0", - "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704", - "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f", - "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f", - "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b", - "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5", - "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923", - "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715", - "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c", - "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca", - "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1", - "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756", - "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360", - "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc", - "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445", - "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e", - "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4", - "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a", - "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8", - "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53", - "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697", - "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf", - "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a", - "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415", - "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f", - "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9", - "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400", - "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d", - "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392", - "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb", - "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd", - "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861", - "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232", - "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95", - "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7", - "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39", - "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887", - "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5", - "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39", - "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb", - "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586", - "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97", - "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423", - "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69", - "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7", - "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1", - "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7", - "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5", - "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8", - "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91", - "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590", - "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe", - "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c", - "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64", - "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd", - "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa", - "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31", - "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988" + "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc", + "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5", + "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf", + "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94", + "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397", + "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82", + "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4", + "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae", + "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d", + "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db", + "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1", + "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b", + "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b", + "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666", + "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6", + "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c", + "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6", + "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c", + "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd", + "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636", + "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6", + "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962", + "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26", + "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e", + "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1", + "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b", + "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3", + "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a", + "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6", + "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257", + "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185", + "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e", + "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247", + "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31", + "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f", + "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec", + "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3", + "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b", + "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f", + "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150", + "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02", + "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17", + "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc", + "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4", + "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796", + "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f", + "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a", + "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d", + "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833", + "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f", + "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc", + "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d", + "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c", + "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10", + "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0", + "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb", + "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947", + "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae", + "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a", + "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f", + "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7", + "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925", + "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630", + "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61", + "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e", + "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58", + "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0", + "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8", + "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1", + "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1", + "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a", + "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662", + "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea", + "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1", + "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013", + "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90", + "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2", + "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e", + "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb" ], - "markers": "python_version >= '3.7'", - "version": "==2023.12.25" + "markers": "python_version >= '3.8'", + "version": "==2024.4.28" }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5", + "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8" ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.0" }, "six": { "hashes": [ @@ -562,62 +552,69 @@ }, "urllib3": { "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], + "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.2.1" + "version": "==2.2.2" }, "watchdog": { "hashes": [ - "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257", - "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca", - "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b", - "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85", - "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b", - "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19", - "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50", - "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92", - "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269", - "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f", - "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c", - "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b", - "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87", - "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b", - "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b", - "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8", - "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c", - "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3", - "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7", - "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605", - "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935", - "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b", - "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927", - "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101", - "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07", - "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec", - "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4", - "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245", - "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d" + "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4", + "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19", + "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a", + "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa", + "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a", + "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a", + "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1", + "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc", + "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9", + "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930", + "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73", + "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b", + "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83", + "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7", + "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef", + "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1", + "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040", + "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b", + "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270", + "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c", + "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d", + "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8", + "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508", + "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b", + "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503", + "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757", + "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b", + "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29", + "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c", + "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22", + "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578", + "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e", + "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee", + "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7", + "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3" ], "markers": "python_version >= '3.8'", - "version": "==4.0.0" + "version": "==4.0.2" }, "wcmatch": { "hashes": [ - "sha256:14554e409b142edeefab901dc68ad570b30a72a8ab9a79106c5d5e9a6d241bd5", - "sha256:86c17572d0f75cbf3bcb1a18f3bf2f9e72b39a9c08c9b4a74e991e1882a8efb3" + "sha256:567d66b11ad74384954c8af86f607857c3bdf93682349ad32066231abd556c92", + "sha256:af25922e2b6dbd1550fa37a4c8de7dd558d6c1bb330c641de9b907b9776cb3c4" ], "markers": "python_version >= '3.8'", - "version": "==8.5" + "version": "==9.0" }, "zipp": { "hashes": [ - "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" + "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", + "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b" ], "markers": "python_version >= '3.8'", - "version": "==3.17.0" + "version": "==3.20.1" } }, "develop": {} diff --git a/README.md b/README.md index cf7e0fc2f9..ea21c63871 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,14 @@ # Testcontainers -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=141451032&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=EastUs) - -**Builds** - [![Main pipeline](https://github.com/testcontainers/testcontainers-go/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/testcontainers/testcontainers-go/actions/workflows/ci.yml) - -**Documentation** - [![GoDoc Reference](https://pkg.go.dev/badge/github.com/testcontainers/testcontainers-go.svg)](https://pkg.go.dev/github.com/testcontainers/testcontainers-go) - -**Social** - -[![Slack](https://img.shields.io/badge/Slack-4A154B?logo=slack)](https://testcontainers.slack.com/) - -**Code quality** - [![Go Report Card](https://goreportcard.com/badge/github.com/testcontainers/testcontainers-go)](https://goreportcard.com/report/github.com/testcontainers/testcontainers-go) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=testcontainers_testcontainers-go&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=testcontainers_testcontainers-go) +[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/testcontainers/testcontainers-go/blob/main/LICENSE) -**License** +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=141451032&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=EastUs) -[![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/testcontainers/testcontainers-go/blob/main/LICENSE) +[![Join our Slack](https://img.shields.io/badge/Slack-4A154B?logo=slack)](https://testcontainers.slack.com/) _Testcontainers for Go_ is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers diff --git a/cleanup.go b/cleanup.go new file mode 100644 index 0000000000..e2d52440b9 --- /dev/null +++ b/cleanup.go @@ -0,0 +1,107 @@ +package testcontainers + +import ( + "context" + "errors" + "fmt" + "reflect" + "time" +) + +// terminateOptions is a type that holds the options for terminating a container. +type terminateOptions struct { + ctx context.Context + timeout *time.Duration + volumes []string +} + +// TerminateOption is a type that represents an option for terminating a container. +type TerminateOption func(*terminateOptions) + +// StopContext returns a TerminateOption that sets the context. +// Default: context.Background(). +func StopContext(ctx context.Context) TerminateOption { + return func(c *terminateOptions) { + c.ctx = ctx + } +} + +// StopTimeout returns a TerminateOption that sets the timeout. +// Default: See [Container.Stop]. +func StopTimeout(timeout time.Duration) TerminateOption { + return func(c *terminateOptions) { + c.timeout = &timeout + } +} + +// RemoveVolumes returns a TerminateOption that sets additional volumes to remove. +// This is useful when the container creates named volumes that should be removed +// which are not removed by default. +// Default: nil. +func RemoveVolumes(volumes ...string) TerminateOption { + return func(c *terminateOptions) { + c.volumes = volumes + } +} + +// TerminateContainer calls [Container.Terminate] on the container if it is not nil. +// +// This should be called as a defer directly after [GenericContainer](...) +// or a modules Run(...) to ensure the container is terminated when the +// function ends. +func TerminateContainer(container Container, options ...TerminateOption) error { + if isNil(container) { + return nil + } + + c := &terminateOptions{ + ctx: context.Background(), + } + + for _, opt := range options { + opt(c) + } + + // TODO: Add a timeout when terminate supports it. + err := container.Terminate(c.ctx) + if !isCleanupSafe(err) { + return fmt.Errorf("terminate: %w", err) + } + + // Remove additional volumes if any. + if len(c.volumes) == 0 { + return nil + } + + client, err := NewDockerClientWithOpts(c.ctx) + if err != nil { + return fmt.Errorf("docker client: %w", err) + } + + defer client.Close() + + // Best effort to remove all volumes. + var errs []error + for _, volume := range c.volumes { + if errRemove := client.VolumeRemove(c.ctx, volume, true); errRemove != nil { + errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) + } + } + + return errors.Join(errs...) +} + +// isNil returns true if val is nil or an nil instance false otherwise. +func isNil(val any) bool { + if val == nil { + return true + } + + valueOf := reflect.ValueOf(val) + switch valueOf.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: + return valueOf.IsNil() + default: + return false + } +} diff --git a/commons-test.mk b/commons-test.mk index e99d62a224..91ed6a1244 100644 --- a/commons-test.mk +++ b/commons-test.mk @@ -6,18 +6,22 @@ define go_install endef $(GOBIN)/golangci-lint: - $(call go_install,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2) + $(call go_install,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0) $(GOBIN)/gotestsum: $(call go_install,gotest.tools/gotestsum@latest) +$(GOBIN)/mockery: + $(call go_install,github.com/vektra/mockery/v2@v2.45) + .PHONY: install -install: $(GOBIN)/golangci-lint $(GOBIN)/gotestsum +install: $(GOBIN)/golangci-lint $(GOBIN)/gotestsum $(GOBIN)/mockery .PHONY: clean clean: rm $(GOBIN)/golangci-lint rm $(GOBIN)/gotestsum + rm $(GOBIN)/mockery .PHONY: dependencies-scan dependencies-scan: @@ -26,7 +30,11 @@ dependencies-scan: .PHONY: lint lint: $(GOBIN)/golangci-lint - golangci-lint run --out-format=github-actions --path-prefix=. --verbose -c $(ROOT_DIR)/.golangci.yml --fix + golangci-lint run --out-format=colored-line-number --path-prefix=. --verbose -c $(ROOT_DIR)/.golangci.yml --fix + +.PHONY: generate +generate: $(GOBIN)/mockery + go generate ./... .PHONY: test-% test-%: $(GOBIN)/gotestsum @@ -37,8 +45,10 @@ test-%: $(GOBIN)/gotestsum --packages="./..." \ --junitfile TEST-unit.xml \ -- \ + -v \ -coverprofile=coverage.out \ - -timeout=30m + -timeout=30m \ + -race .PHONY: tools tools: @@ -47,6 +57,9 @@ tools: .PHONY: test-tools test-tools: $(GOBIN)/gotestsum -.PHONY: tools-tidy -tools-tidy: +.PHONY: tidy +tidy: go mod tidy + +.PHONY: pre-commit +pre-commit: generate tidy lint diff --git a/config_test.go b/config_test.go deleted file mode 100644 index 516b05218e..0000000000 --- a/config_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package testcontainers_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/internal/config" -) - -// unset environment variables to avoid side effects -// execute this function before each test -func resetTestEnv(t *testing.T) { - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "") - t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "") -} - -func TestReadConfig(t *testing.T) { - resetTestEnv(t) - t.Cleanup(func() { - config.Reset() - }) - - t.Run("Config is read just once", func(t *testing.T) { - t.Setenv("HOME", "") - t.Setenv("USERPROFILE", "") // Windows support - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") - - cfg := testcontainers.ReadConfig() - - expected := testcontainers.TestcontainersConfig{ - RyukDisabled: true, - Config: config.Config{ - RyukDisabled: true, - }, - } - - assert.Equal(t, expected, cfg) - - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "false") - cfg = testcontainers.ReadConfig() - assert.Equal(t, expected, cfg) - }) -} diff --git a/container.go b/container.go index 2f24f4c38f..5ee0aac881 100644 --- a/container.go +++ b/container.go @@ -1,6 +1,7 @@ package testcontainers import ( + "archive/tar" "context" "errors" "fmt" @@ -10,6 +11,7 @@ import ( "strings" "time" + "github.com/cpuguy83/dockercfg" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" @@ -35,22 +37,26 @@ type DeprecatedContainer interface { // Container allows getting info about and controlling a single container instance type Container interface { - GetContainerID() string // get the container id from the provider - Endpoint(context.Context, string) (string, error) // get proto://ip:port string for the first exposed port - PortEndpoint(context.Context, nat.Port, string) (string, error) // get proto://ip:port string for the given exposed port - Host(context.Context) (string, error) // get host where the container port is exposed - MappedPort(context.Context, nat.Port) (nat.Port, error) // get externally mapped port for a container port - Ports(context.Context) (nat.PortMap, error) // get all exposed ports - SessionID() string // get session id - IsRunning() bool - Start(context.Context) error // start the container - Stop(context.Context, *time.Duration) error // stop the container - Terminate(context.Context) error // terminate the container + GetContainerID() string // get the container id from the provider + Endpoint(context.Context, string) (string, error) // get proto://ip:port string for the lowest exposed port + PortEndpoint(ctx context.Context, port nat.Port, proto string) (string, error) // get proto://ip:port string for the given exposed port + Host(context.Context) (string, error) // get host where the container port is exposed + Inspect(context.Context) (*types.ContainerJSON, error) // get container info + MappedPort(context.Context, nat.Port) (nat.Port, error) // get externally mapped port for a container port + Ports(context.Context) (nat.PortMap, error) // Deprecated: Use c.Inspect(ctx).NetworkSettings.Ports instead + SessionID() string // get session id + IsRunning() bool // IsRunning returns true if the container is running, false otherwise. + Start(context.Context) error // start the container + Stop(context.Context, *time.Duration) error // stop the container + + // Terminate stops and removes the container and its image if it was built and not flagged as kept. + Terminate(ctx context.Context) error + Logs(context.Context) (io.ReadCloser, error) // Get logs of the container FollowOutput(LogConsumer) // Deprecated: it will be removed in the next major release StartLogProducer(context.Context, ...LogProductionOption) error // Deprecated: Use the ContainerRequest instead StopLogProducer() error // Deprecated: it will be removed in the next major release - Name(context.Context) (string, error) // get container name + Name(context.Context) (string, error) // Deprecated: Use c.Inspect(ctx).Name instead State(context.Context) (*types.ContainerState, error) // returns container's running state Networks(context.Context) ([]string, error) // get container networks NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network @@ -68,7 +74,7 @@ type Container interface { type ImageBuildInfo interface { BuildOptions() (types.ImageBuildOptions, error) // converts the ImageBuildInfo to a types.ImageBuildOptions GetContext() (io.Reader, error) // the path to the build context - GetDockerfile() string // the relative path to the Dockerfile, including the fileitself + GetDockerfile() string // the relative path to the Dockerfile, including the file itself GetRepo() string // get repo label for image GetTag() string // get tag label for image ShouldPrintBuildLog() bool // allow build log to be printed to stdout @@ -81,7 +87,7 @@ type ImageBuildInfo interface { // rather than using a pre-built one type FromDockerfile struct { Context string // the path to the context of the docker build - ContextArchive io.Reader // the tar archive file to send to docker that contains the build context + ContextArchive io.ReadSeeker // the tar archive file to send to docker that contains the build context Dockerfile string // the path from the context to the Dockerfile for the image, defaults to "Dockerfile" Repo string // the repo label for image, defaults to UUID Tag string // the tag label for image, defaults to UUID @@ -161,6 +167,15 @@ type ContainerRequest struct { LogConsumerCfg *LogConsumerConfig // define the configuration for the log producer and its log consumers to follow the logs } +// sessionID returns the session ID for the container request. +func (c *ContainerRequest) sessionID() string { + if sessionID := c.Labels[core.LabelSessionID]; sessionID != "" { + return sessionID + } + + return core.SessionID() +} + // containerOptions functional options for a container type containerOptions struct { ImageName string @@ -208,6 +223,7 @@ func (c *ContainerRequest) Validate() error { } // GetContext retrieve the build context for the request +// Must be closed when no longer needed. func (c *ContainerRequest) GetContext() (io.Reader, error) { var includes []string = []string{"."} @@ -270,60 +286,120 @@ func (c *ContainerRequest) GetBuildArgs() map[string]*string { return c.FromDockerfile.BuildArgs } -// GetDockerfile returns the Dockerfile from the ContainerRequest, defaults to "Dockerfile" +// GetDockerfile returns the Dockerfile from the ContainerRequest, defaults to "Dockerfile". +// Sets FromDockerfile.Dockerfile to the default if blank. func (c *ContainerRequest) GetDockerfile() string { - f := c.FromDockerfile.Dockerfile - if f == "" { - return "Dockerfile" + if c.FromDockerfile.Dockerfile == "" { + c.FromDockerfile.Dockerfile = "Dockerfile" } - return f + return c.FromDockerfile.Dockerfile } -// GetRepo returns the Repo label for image from the ContainerRequest, defaults to UUID +// GetRepo returns the Repo label for image from the ContainerRequest, defaults to UUID. +// Sets FromDockerfile.Repo to the default value if blank. func (c *ContainerRequest) GetRepo() string { - r := c.FromDockerfile.Repo - if r == "" { - return uuid.NewString() + if c.FromDockerfile.Repo == "" { + c.FromDockerfile.Repo = uuid.NewString() } - return strings.ToLower(r) + return strings.ToLower(c.FromDockerfile.Repo) } -// GetTag returns the Tag label for image from the ContainerRequest, defaults to UUID +// GetTag returns the Tag label for image from the ContainerRequest, defaults to UUID. +// Sets FromDockerfile.Tag to the default value if blank. func (c *ContainerRequest) GetTag() string { - t := c.FromDockerfile.Tag - if t == "" { - return uuid.NewString() + if c.FromDockerfile.Tag == "" { + c.FromDockerfile.Tag = uuid.NewString() } - return strings.ToLower(t) + return strings.ToLower(c.FromDockerfile.Tag) } -// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release -// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry +// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release. +// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry. +// Panics if an error occurs. func (c *ContainerRequest) GetAuthConfigs() map[string]registry.AuthConfig { - return getAuthConfigsFromDockerfile(c) + auth, err := getAuthConfigsFromDockerfile(c) + if err != nil { + panic(fmt.Sprintf("failed to get auth configs from Dockerfile: %v", err)) + } + return auth +} + +// dockerFileImages returns the images from the request Dockerfile. +func (c *ContainerRequest) dockerFileImages() ([]string, error) { + if c.ContextArchive == nil { + // Source is a directory, we can read the Dockerfile directly. + images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs()) + if err != nil { + return nil, fmt.Errorf("extract images from Dockerfile: %w", err) + } + + return images, nil + } + + // Source is an archive, we need to read it to get the Dockerfile. + dockerFile := c.GetDockerfile() + tr := tar.NewReader(c.FromDockerfile.ContextArchive) + + for { + hdr, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + return nil, fmt.Errorf("Dockerfile %q not found in context archive", dockerFile) + } + + return nil, fmt.Errorf("reading tar archive: %w", err) + } + + if hdr.Name != dockerFile { + continue + } + + images, err := core.ExtractImagesFromReader(tr, c.GetBuildArgs()) + if err != nil { + return nil, fmt.Errorf("extract images from Dockerfile: %w", err) + } + + // Reset the archive to the beginning. + if _, err := c.ContextArchive.Seek(0, io.SeekStart); err != nil { + return nil, fmt.Errorf("seek context archive to start: %w", err) + } + + return images, nil + } } // getAuthConfigsFromDockerfile returns the auth configs to be able to pull from an authenticated docker registry -func getAuthConfigsFromDockerfile(c *ContainerRequest) map[string]registry.AuthConfig { - images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs()) +func getAuthConfigsFromDockerfile(c *ContainerRequest) (map[string]registry.AuthConfig, error) { + images, err := c.dockerFileImages() + if err != nil { + return nil, fmt.Errorf("docker file images: %w", err) + } + + // Get the auth configs once for all images as it can be a time-consuming operation. + configs, err := getDockerAuthConfigs() if err != nil { - return map[string]registry.AuthConfig{} + return nil, err } authConfigs := map[string]registry.AuthConfig{} for _, image := range images { - registry, authConfig, err := DockerImageAuth(context.Background(), image) + registry, authConfig, err := dockerImageAuth(context.Background(), image, configs) if err != nil { + if !errors.Is(err, dockercfg.ErrCredentialsNotFound) { + return nil, fmt.Errorf("docker image auth %q: %w", image, err) + } + + // Credentials not found no config to add. continue } authConfigs[registry] = authConfig } - return authConfigs + return authConfigs, nil } func (c *ContainerRequest) ShouldBuildImage() bool { @@ -355,14 +431,11 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) { buildOptions.BuildArgs = c.GetBuildArgs() buildOptions.Dockerfile = c.GetDockerfile() - buildContext, err := c.GetContext() + // Make sure the auth configs from the Dockerfile are set right after the user-defined build options. + authsFromDockerfile, err := getAuthConfigsFromDockerfile(c) if err != nil { - return buildOptions, err + return types.ImageBuildOptions{}, fmt.Errorf("auth configs from Dockerfile: %w", err) } - buildOptions.Context = buildContext - - // Make sure the auth configs from the Dockerfile are set right after the user-defined build options. - authsFromDockerfile := getAuthConfigsFromDockerfile(c) if buildOptions.AuthConfigs == nil { buildOptions.AuthConfigs = map[string]registry.AuthConfig{} @@ -374,6 +447,20 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) { // make sure the first tag is the one defined in the ContainerRequest tag := fmt.Sprintf("%s:%s", c.GetRepo(), c.GetTag()) + + // apply substitutors to the built image + for _, is := range c.ImageSubstitutors { + modifiedTag, err := is.Substitute(tag) + if err != nil { + return types.ImageBuildOptions{}, fmt.Errorf("failed to substitute image %s with %s: %w", tag, is.Description(), err) + } + + if modifiedTag != tag { + Logger.Printf("✍đŸŧ Replacing image with %s. From: %s to %s\n", is.Description(), tag, modifiedTag) + tag = modifiedTag + } + } + if len(buildOptions.Tags) > 0 { // prepend the tag buildOptions.Tags = append([]string{tag}, buildOptions.Tags...) @@ -381,6 +468,25 @@ func (c *ContainerRequest) BuildOptions() (types.ImageBuildOptions, error) { buildOptions.Tags = []string{tag} } + if !c.ShouldKeepBuiltImage() { + dst := GenericLabels() + if err = core.MergeCustomLabels(dst, c.Labels); err != nil { + return types.ImageBuildOptions{}, err + } + if err = core.MergeCustomLabels(dst, buildOptions.Labels); err != nil { + return types.ImageBuildOptions{}, err + } + buildOptions.Labels = dst + } + + // Do this as late as possible to ensure we don't leak the context on error/panic. + buildContext, err := c.GetContext() + if err != nil { + return types.ImageBuildOptions{}, err + } + + buildOptions.Context = buildContext + return buildOptions, nil } @@ -423,10 +529,10 @@ func (c *ContainerRequest) validateMounts() error { c.HostConfigModifier(&hostConfig) - if hostConfig.Binds != nil && len(hostConfig.Binds) > 0 { + if len(hostConfig.Binds) > 0 { for _, bind := range hostConfig.Binds { parts := strings.Split(bind, ":") - if len(parts) != 2 { + if len(parts) != 2 && len(parts) != 3 { return fmt.Errorf("%w: %s", ErrInvalidBindMount, bind) } targetPath := parts[1] diff --git a/container_file_test.go b/container_file_test.go index 31273c9966..12d2240604 100644 --- a/container_file_test.go +++ b/container_file_test.go @@ -3,23 +3,22 @@ package testcontainers import ( - "errors" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" ) func TestContainerFileValidation(t *testing.T) { type ContainerFileValidationTestCase struct { Name string - ExpectedError error + ExpectedError string File ContainerFile } f, err := os.Open(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) testTable := []ContainerFileValidationTestCase{ { @@ -38,7 +37,7 @@ func TestContainerFileValidation(t *testing.T) { }, { Name: "invalid container file", - ExpectedError: errors.New("either HostFilePath or Reader must be specified"), + ExpectedError: "either HostFilePath or Reader must be specified", File: ContainerFile{ HostFilePath: "", Reader: nil, @@ -47,7 +46,7 @@ func TestContainerFileValidation(t *testing.T) { }, { Name: "invalid container file", - ExpectedError: errors.New("ContainerFilePath must be specified"), + ExpectedError: "ContainerFilePath must be specified", File: ContainerFile{ HostFilePath: "/path/to/host", ContainerFilePath: "", @@ -58,15 +57,10 @@ func TestContainerFileValidation(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { err := testCase.File.validate() - switch { - case err == nil && testCase.ExpectedError == nil: - return - case err == nil && testCase.ExpectedError != nil: - t.Errorf("did not receive expected error: %s", testCase.ExpectedError.Error()) - case err != nil && testCase.ExpectedError == nil: - t.Errorf("received unexpected error: %s", err.Error()) - case err.Error() != testCase.ExpectedError.Error(): - t.Errorf("errors mismatch: %s != %s", err.Error(), testCase.ExpectedError.Error()) + if testCase.ExpectedError != "" { + require.EqualError(t, err, testCase.ExpectedError) + } else { + require.NoError(t, err) } }) } diff --git a/container_ignore_test.go b/container_ignore_test.go index ca89db4d89..505b9edd6d 100644 --- a/container_ignore_test.go +++ b/container_ignore_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParseDockerIgnore(t *testing.T) { @@ -37,7 +38,7 @@ func TestParseDockerIgnore(t *testing.T) { for _, testCase := range testCases { exists, excluded, err := parseDockerIgnore(testCase.filePath) assert.Equal(t, testCase.exists, exists) - assert.Equal(t, testCase.expectedErr, err) + require.ErrorIs(t, testCase.expectedErr, err) assert.Equal(t, testCase.expectedExcluded, excluded) } } diff --git a/container_test.go b/container_test.go index 1d25c4d661..742a97436e 100644 --- a/container_test.go +++ b/container_test.go @@ -8,10 +8,10 @@ import ( "fmt" "io" "log" - "strings" "testing" "time" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -23,14 +23,14 @@ import ( func Test_ContainerValidation(t *testing.T) { type ContainerValidationTestCase struct { Name string - ExpectedError error + ExpectedError string ContainerRequest testcontainers.ContainerRequest } testTable := []ContainerValidationTestCase{ { Name: "cannot set both context and image", - ExpectedError: errors.New("you cannot specify both an Image and Context in a ContainerRequest"), + ExpectedError: "you cannot specify both an Image and Context in a ContainerRequest", ContainerRequest: testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ Context: ".", @@ -39,15 +39,13 @@ func Test_ContainerValidation(t *testing.T) { }, }, { - Name: "can set image without context", - ExpectedError: nil, + Name: "can set image without context", ContainerRequest: testcontainers.ContainerRequest{ Image: "redis:latest", }, }, { - Name: "can set context without image", - ExpectedError: nil, + Name: "can set context without image", ContainerRequest: testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ Context: ".", @@ -55,8 +53,7 @@ func Test_ContainerValidation(t *testing.T) { }, }, { - Name: "Can mount same source to multiple targets", - ExpectedError: nil, + Name: "Can mount same source to multiple targets", ContainerRequest: testcontainers.ContainerRequest{ Image: "redis:latest", HostConfigModifier: func(hc *container.HostConfig) { @@ -66,7 +63,7 @@ func Test_ContainerValidation(t *testing.T) { }, { Name: "Cannot mount multiple sources to same target", - ExpectedError: errors.New("duplicate mount target detected: /data"), + ExpectedError: "duplicate mount target detected: /data", ContainerRequest: testcontainers.ContainerRequest{ Image: "redis:latest", HostConfigModifier: func(hc *container.HostConfig) { @@ -76,11 +73,33 @@ func Test_ContainerValidation(t *testing.T) { }, { Name: "Invalid bind mount", - ExpectedError: errors.New("invalid bind mount: /data:/data:/data"), + ExpectedError: "invalid bind mount: /data:/data:a:b", ContainerRequest: testcontainers.ContainerRequest{ Image: "redis:latest", HostConfigModifier: func(hc *container.HostConfig) { - hc.Binds = []string{"/data:/data:/data"} + hc.Binds = []string{"/data:/data:a:b"} + }, + }, + }, + { + Name: "bind-options/provided", + ContainerRequest: testcontainers.ContainerRequest{ + Image: "redis:latest", + HostConfigModifier: func(hc *container.HostConfig) { + hc.Binds = []string{ + "/a:/a:nocopy", + "/b:/b:ro", + "/c:/c:rw", + "/d:/d:z", + "/e:/e:Z", + "/f:/f:shared", + "/g:/g:rshared", + "/h:/h:slave", + "/i:/i:rslave", + "/j:/j:private", + "/k:/k:rprivate", + "/l:/l:ro,z,shared", + } }, }, }, @@ -89,15 +108,10 @@ func Test_ContainerValidation(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { err := testCase.ContainerRequest.Validate() - switch { - case err == nil && testCase.ExpectedError == nil: - return - case err == nil && testCase.ExpectedError != nil: - t.Errorf("did not receive expected error: %s", testCase.ExpectedError.Error()) - case err != nil && testCase.ExpectedError == nil: - t.Errorf("received unexpected error: %s", err.Error()) - case err.Error() != testCase.ExpectedError.Error(): - t.Errorf("errors mismatch: %s != %s", err.Error(), testCase.ExpectedError.Error()) + if testCase.ExpectedError != "" { + require.EqualError(t, err, testCase.ExpectedError) + } else { + require.NoError(t, err) } }) } @@ -137,9 +151,7 @@ func Test_GetDockerfile(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.name, func(t *testing.T) { n := testCase.ContainerRequest.GetDockerfile() - if n != testCase.ExpectedDockerfileName { - t.Fatalf("expected Dockerfile name: %s, received: %s", testCase.ExpectedDockerfileName, n) - } + require.Equalf(t, n, testCase.ExpectedDockerfileName, "expected Dockerfile name: %s, received: %s", testCase.ExpectedDockerfileName, n) }) } } @@ -148,17 +160,17 @@ func Test_BuildImageWithContexts(t *testing.T) { type TestCase struct { Name string ContextPath string - ContextArchive func() (io.Reader, error) + ContextArchive func() (io.ReadSeeker, error) ExpectedEchoOutput string Dockerfile string - ExpectedError error + ExpectedError string } testCases := []TestCase{ { Name: "test build from context archive", // fromDockerfileWithContextArchive { - ContextArchive: func() (io.Reader, error) { + ContextArchive: func() (io.ReadSeeker, error) { var buf bytes.Buffer tarWriter := tar.NewWriter(&buf) files := []struct { @@ -167,7 +179,7 @@ func Test_BuildImageWithContexts(t *testing.T) { }{ { Name: "Dockerfile", - Contents: `FROM docker.io/alpine + Contents: `FROM alpine CMD ["echo", "this is from the archive"]`, }, } @@ -203,7 +215,7 @@ func Test_BuildImageWithContexts(t *testing.T) { }, { Name: "test build from context archive and be able to use files in it", - ContextArchive: func() (io.Reader, error) { + ContextArchive: func() (io.ReadSeeker, error) { var buf bytes.Buffer tarWriter := tar.NewWriter(&buf) files := []struct { @@ -216,7 +228,7 @@ func Test_BuildImageWithContexts(t *testing.T) { }, { Name: "Dockerfile", - Contents: `FROM docker.io/alpine + Contents: `FROM alpine WORKDIR /app COPY . . CMD ["sh", "./say_hi.sh"]`, @@ -252,21 +264,21 @@ func Test_BuildImageWithContexts(t *testing.T) { ExpectedEchoOutput: "hi this is from the say_hi.sh file!", }, { - Name: "test buildling from a context on the filesystem", + Name: "test building from a context on the filesystem", ContextPath: "./testdata", Dockerfile: "echo.Dockerfile", ExpectedEchoOutput: "this is from the echo test Dockerfile", - ContextArchive: func() (io.Reader, error) { + ContextArchive: func() (io.ReadSeeker, error) { return nil, nil }, }, { Name: "it should error if neither a context nor a context archive are specified", ContextPath: "", - ContextArchive: func() (io.Reader, error) { + ContextArchive: func() (io.ReadSeeker, error) { return nil, nil }, - ExpectedError: errors.New("you must specify either a build context or an image: failed to create container"), + ExpectedError: "create container: you must specify either a build context or an image", }, } @@ -276,9 +288,8 @@ func Test_BuildImageWithContexts(t *testing.T) { t.Parallel() ctx := context.Background() a, err := testCase.ContextArchive() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ ContextArchive: a, @@ -292,25 +303,81 @@ func Test_BuildImageWithContexts(t *testing.T) { ContainerRequest: req, Started: true, }) - switch { - case testCase.ExpectedError != nil && err != nil: - if testCase.ExpectedError.Error() != err.Error() { - t.Fatalf("unexpected error: %s, was expecting %s", err.Error(), testCase.ExpectedError.Error()) - } - case err != nil: - t.Fatal(err) - default: - terminateContainerOnEnd(t, ctx, c) + testcontainers.CleanupContainer(t, c) + + if testCase.ExpectedError != "" { + require.EqualError(t, err, testCase.ExpectedError) + return } + + require.NoError(t, err) }) } } +func TestCustomLabelsImage(t *testing.T) { + const ( + myLabelName = "org.my.label" + myLabelValue = "my-label-value" + ) + + ctx := context.Background() + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "alpine:latest", + Labels: map[string]string{myLabelName: myLabelValue}, + }, + } + + ctr, err := testcontainers.GenericContainer(ctx, req) + + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, ctr.Terminate(ctx)) }) + + ctrJSON, err := ctr.Inspect(ctx) + require.NoError(t, err) + assert.Equal(t, myLabelValue, ctrJSON.Config.Labels[myLabelName]) +} + +func TestCustomLabelsBuildOptionsModifier(t *testing.T) { + const ( + myLabelName = "org.my.label" + myLabelValue = "my-label-value" + myBuildOptionLabel = "org.my.bo.label" + myBuildOptionValue = "my-bo-label-value" + ) + + ctx := context.Background() + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: "./testdata", + Dockerfile: "Dockerfile", + BuildOptionsModifier: func(opts *types.ImageBuildOptions) { + opts.Labels = map[string]string{ + myBuildOptionLabel: myBuildOptionValue, + } + }, + }, + Labels: map[string]string{myLabelName: myLabelValue}, + }, + } + + ctr, err := testcontainers.GenericContainer(ctx, req) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + ctrJSON, err := ctr.Inspect(ctx) + require.NoError(t, err) + require.Equal(t, myLabelValue, ctrJSON.Config.Labels[myLabelName]) + require.Equal(t, myBuildOptionValue, ctrJSON.Config.Labels[myBuildOptionLabel]) +} + func Test_GetLogsFromFailedContainer(t *testing.T) { ctx := context.Background() // directDockerHubReference { req := testcontainers.ContainerRequest{ - Image: "docker.io/alpine", + Image: "alpine", Cmd: []string{"echo", "-n", "I was not expecting this"}, WaitingFor: wait.ForLog("I was expecting this").WithStartupTimeout(5 * time.Second), } @@ -320,39 +387,28 @@ func Test_GetLogsFromFailedContainer(t *testing.T) { ContainerRequest: req, Started: true, }) - - if err != nil && err.Error() != "failed to start container: container exited with code 0" { - t.Fatal(err) - } else if err == nil { - terminateContainerOnEnd(t, ctx, c) - t.Fatal("was expecting error starting container") - } + testcontainers.CleanupContainer(t, c) + require.ErrorContains(t, err, "container exited with code 0") logs, logErr := c.Logs(ctx) - if logErr != nil { - t.Fatal(logErr) - } + require.NoError(t, logErr) b, err := io.ReadAll(logs) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) log := string(b) - if strings.Contains(log, "I was not expecting this") == false { - t.Fatalf("could not find expected log in %s", log) - } + require.Contains(t, log, "I was not expecting this") } // dockerImageSubstitutor { type dockerImageSubstitutor struct{} func (s dockerImageSubstitutor) Description() string { - return "DockerImageSubstitutor (prepends docker.io)" + return "DockerImageSubstitutor (prepends registry.hub.docker.com)" } func (s dockerImageSubstitutor) Substitute(image string) (string, error) { - return "docker.io/" + image, nil + return "registry.hub.docker.com/library/" + image, nil } // } @@ -411,7 +467,7 @@ func TestImageSubstitutors(t *testing.T) { name: "Prepend namespace", image: "alpine", substitutors: []testcontainers.ImageSubstitutor{dockerImageSubstitutor{}}, - expectedImage: "docker.io/alpine", + expectedImage: "registry.hub.docker.com/library/alpine", }, { name: "Substitution with error", @@ -430,25 +486,21 @@ func TestImageSubstitutors(t *testing.T) { ImageSubstitutors: test.substitutors, } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, ctr) if test.expectedError != nil { require.ErrorIs(t, err, test.expectedError) return } - if err != nil { - t.Fatal(err) - } - defer func() { - terminateContainerOnEnd(t, ctx, container) - }() + require.NoError(t, err) // enforce the concrete type, as GenericContainer returns an interface, // which will be changed in future implementations of the library - dockerContainer := container.(*testcontainers.DockerContainer) + dockerContainer := ctr.(*testcontainers.DockerContainer) assert.Equal(t, test.expectedImage, dockerContainer.Image) }) } @@ -468,21 +520,17 @@ func TestShouldStartContainersInParallel(t *testing.T) { ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatalf("could not start container: %v", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + // mappedPort { - port, err := container.MappedPort(ctx, nginxDefaultPort) + port, err := ctr.MappedPort(ctx, nginxDefaultPort) // } - if err != nil { - t.Fatalf("could not get mapped port: %v", err) - } - - terminateContainerOnEnd(t, ctx, container) + require.NoError(t, err) t.Logf("Parallel container [iteration_%d] listening on %d\n", i, port.Int()) }) @@ -493,30 +541,30 @@ func ExampleGenericContainer_withSubstitutors() { ctx := context.Background() // applyImageSubstitutors { - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "alpine:latest", ImageSubstitutors: []testcontainers.ImageSubstitutor{dockerImageSubstitutor{}}, }, Started: true, }) - // } - if err != nil { - log.Fatalf("could not start container: %v", err) - } - defer func() { - err := container.Terminate(ctx) - if err != nil { - log.Fatalf("could not terminate container: %v", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + // } + if err != nil { + log.Printf("could not start container: %v", err) + return + } + // enforce the concrete type, as GenericContainer returns an interface, // which will be changed in future implementations of the library - dockerContainer := container.(*testcontainers.DockerContainer) + dockerContainer := ctr.(*testcontainers.DockerContainer) fmt.Println(dockerContainer.Image) - // Output: docker.io/alpine:latest + // Output: registry.hub.docker.com/library/alpine:latest } diff --git a/docker.go b/docker.go index e42767cedc..296fe6743c 100644 --- a/docker.go +++ b/docker.go @@ -3,32 +3,32 @@ package testcontainers import ( "archive/tar" "bufio" - "bytes" "context" "encoding/base64" - "encoding/binary" "encoding/json" "errors" "fmt" "io" "io/fs" + "net" "net/url" "os" "path/filepath" "regexp" - "strings" "sync" "time" "github.com/cenkalti/backoff/v4" - "github.com/containerd/containerd/platforms" + "github.com/containerd/platforms" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-connections/nat" "github.com/moby/term" specs "github.com/opencontainers/image-spec/specs-go/v1" @@ -47,45 +47,52 @@ const ( Podman = "podman" ReaperDefault = "reaper_default" // Default network name when bridge is not available packagePath = "github.com/testcontainers/testcontainers-go" - - logStoppedForOutOfSyncMessage = "Stopping log consumer: Headers out of sync" ) -var createContainerFailDueToNameConflictRegex = regexp.MustCompile("Conflict. The container name .* is already in use by container .*") +var ( + // createContainerFailDueToNameConflictRegex is a regular expression that matches the container is already in use error. + createContainerFailDueToNameConflictRegex = regexp.MustCompile("Conflict. The container name .* is already in use by container .*") + + // minLogProductionTimeout is the minimum log production timeout. + minLogProductionTimeout = time.Duration(5 * time.Second) + + // maxLogProductionTimeout is the maximum log production timeout. + maxLogProductionTimeout = time.Duration(60 * time.Second) + + // errLogProductionStop is the cause for stopping log production. + errLogProductionStop = errors.New("log production stopped") +) // DockerContainer represents a container started using Docker type DockerContainer struct { // Container ID from Docker - ID string - WaitingFor wait.Strategy - Image string + ID string + WaitingFor wait.Strategy + Image string + exposedPorts []string // a reference to the container's requested exposed ports. It allows checking they are ready before any wait strategy isRunning bool imageWasBuilt bool // keepBuiltImage makes Terminate not remove the image if imageWasBuilt. - keepBuiltImage bool - provider *DockerProvider - sessionID string - terminationSignal chan bool - consumers []LogConsumer - raw *types.ContainerJSON - logProductionError chan error + keepBuiltImage bool + provider *DockerProvider + sessionID string + terminationSignal chan bool + consumers []LogConsumer // TODO: Remove locking and wait group once the deprecated StartLogProducer and // StopLogProducer have been removed and hence logging can only be started and // stopped once. - // logProductionWaitGroup is used to signal when the log production has stopped. - // This allows stopLogProduction to safely set logProductionStop to nil. - logProductionWaitGroup sync.WaitGroup - - // logProductionMutex protects logProductionStop channel so it can be started again. - logProductionMutex sync.Mutex - logProductionStop chan struct{} + // logProductionCancel is used to signal the log production to stop. + logProductionCancel context.CancelCauseFunc + logProductionCtx context.Context logProductionTimeout *time.Duration logger Logging lifecycleHooks []ContainerLifecycleHooks + + healthStatus string // container health status, will default to healthStatusNone if no healthcheck is present } // SetLogger sets the logger for the container @@ -111,22 +118,23 @@ func (c *DockerContainer) IsRunning() bool { return c.isRunning } -// Endpoint gets proto://host:port string for the first exposed port +// Endpoint gets proto://host:port string for the lowest numbered exposed port // Will returns just host:port if proto is "" func (c *DockerContainer) Endpoint(ctx context.Context, proto string) (string, error) { - ports, err := c.Ports(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return "", err } - // 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) } // PortEndpoint gets proto://host:port string for the given exposed port @@ -144,7 +152,7 @@ func (c *DockerContainer) PortEndpoint(ctx context.Context, port nat.Port, proto protoFull := "" if proto != "" { - protoFull = fmt.Sprintf("%s://", proto) + protoFull = proto + "://" } return fmt.Sprintf("%s%s:%s", protoFull, host, outerPort.Port()), nil @@ -152,7 +160,7 @@ func (c *DockerContainer) PortEndpoint(ctx context.Context, port nat.Port, proto // Host gets host (ip or name) of the docker daemon where the container port is exposed // Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel -// You can use the "TC_HOST" env variable to set this yourself +// You can use the "TESTCONTAINERS_HOST_OVERRIDE" env variable to set this yourself func (c *DockerContainer) Host(ctx context.Context) (string, error) { host, err := c.provider.DaemonHost(ctx) if err != nil { @@ -161,19 +169,27 @@ func (c *DockerContainer) Host(ctx context.Context) (string, error) { return host, nil } +// Inspect gets the raw container info +func (c *DockerContainer) Inspect(ctx context.Context) (*types.ContainerJSON, error) { + jsonRaw, err := c.inspectRawContainer(ctx) + if err != nil { + return nil, err + } + + return jsonRaw, nil +} + // MappedPort gets externally mapped port for a container port func (c *DockerContainer) MappedPort(ctx context.Context, port nat.Port) (nat.Port, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { - return "", err + return "", fmt.Errorf("inspect: %w", err) } if inspect.ContainerJSONBase.HostConfig.NetworkMode == "host" { return port, nil } - ports, err := c.Ports(ctx) - if err != nil { - return "", err - } + + ports := inspect.NetworkSettings.Ports for k, p := range ports { if k.Port() != port.Port() { @@ -188,12 +204,13 @@ func (c *DockerContainer) MappedPort(ctx context.Context, port nat.Port) (nat.Po return nat.NewPort(k.Proto(), p[0].HostPort) } - return "", errors.New("port not found") + return "", errdefs.NotFound(fmt.Errorf("port %q not found", port)) } +// Deprecated: use c.Inspect(ctx).NetworkSettings.Ports instead. // Ports gets the exposed ports for the container. func (c *DockerContainer) Ports(ctx context.Context) (nat.PortMap, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return nil, err } @@ -209,42 +226,51 @@ func (c *DockerContainer) SessionID() string { func (c *DockerContainer) Start(ctx context.Context) error { err := c.startingHook(ctx) if err != nil { - return err + return fmt.Errorf("starting hook: %w", err) } if err := c.provider.client.ContainerStart(ctx, c.ID, container.StartOptions{}); err != nil { - return err + return fmt.Errorf("container start: %w", err) } defer c.provider.Close() err = c.startedHook(ctx) if err != nil { - return err + return fmt.Errorf("started hook: %w", err) } c.isRunning = true err = c.readiedHook(ctx) if err != nil { - return err + return fmt.Errorf("readied hook: %w", err) } return nil } -// Stop will stop an already started container +// Stop stops the container. // -// In case the container fails to stop -// gracefully within a time frame specified by the timeout argument, -// it is forcefully terminated (killed). +// In case the container fails to stop gracefully within a time frame specified +// by the timeout argument, it is forcefully terminated (killed). // // If the timeout is nil, the container's StopTimeout value is used, if set, // otherwise the engine default. A negative timeout value can be specified, // meaning no timeout, i.e. no forceful termination is performed. +// +// All hooks are called in the following order: +// - [ContainerLifecycleHooks.PreStops] +// - [ContainerLifecycleHooks.PostStops] +// +// If the container is already stopped, the method is a no-op. func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) error { + // Note we can't check isRunning here because we allow external creation + // without exposing the ability to fully initialize the container state. + // See: https://github.com/testcontainers/testcontainers-go/issues/2667 + // TODO: Add a check for isRunning when the above issue is resolved. err := c.stoppingHook(ctx) if err != nil { - return err + return fmt.Errorf("stopping hook: %w", err) } var options container.StopOptions @@ -255,30 +281,48 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro } if err := c.provider.client.ContainerStop(ctx, c.ID, options); err != nil { - return err + return fmt.Errorf("container stop: %w", err) } + defer c.provider.Close() c.isRunning = false err = c.stoppedHook(ctx) if err != nil { - return err + return fmt.Errorf("stopped hook: %w", err) } return nil } -// Terminate is used to kill the container. It is usually triggered by as defer function. +// Terminate calls stops and then removes the container including its volumes. +// If its image was built it and all child images are also removed unless +// the [FromDockerfile.KeepImage] on the [ContainerRequest] was set to true. +// +// The following hooks are called in order: +// - [ContainerLifecycleHooks.PreTerminates] +// - [ContainerLifecycleHooks.PostTerminates] func (c *DockerContainer) Terminate(ctx context.Context) error { + // ContainerRemove hardcodes stop timeout to 3 seconds which is too short + // to ensure that child containers are stopped so we manually call stop. + // TODO: make this configurable via a functional option. + timeout := 10 * time.Second + err := c.Stop(ctx, &timeout) + if err != nil && !isCleanupSafe(err) { + return fmt.Errorf("stop: %w", err) + } + select { - // close reaper if it was created + // Close reaper connection if it was attached. case c.terminationSignal <- true: default: } defer c.provider.client.Close() + // TODO: Handle errors from ContainerRemove more correctly, e.g. should we + // run the terminated hook? errs := []error{ c.terminatingHook(ctx), c.provider.client.ContainerRemove(ctx, c.GetContainerID(), container.RemoveOptions{ @@ -289,7 +333,7 @@ func (c *DockerContainer) Terminate(ctx context.Context) error { } if c.imageWasBuilt && !c.keepBuiltImage { - _, err := c.provider.client.ImageRemove(ctx, c.Image, types.ImageRemoveOptions{ + _, err := c.provider.client.ImageRemove(ctx, c.Image, image.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -298,6 +342,7 @@ func (c *DockerContainer) Terminate(ctx context.Context) error { c.sessionID = "" c.isRunning = false + return errors.Join(errs...) } @@ -309,17 +354,6 @@ func (c *DockerContainer) inspectRawContainer(ctx context.Context) (*types.Conta return nil, err } - c.raw = &inspect - return c.raw, nil -} - -func (c *DockerContainer) inspectContainer(ctx context.Context) (*types.ContainerJSON, error) { - defer c.provider.Close() - inspect, err := c.provider.client.ContainerInspect(ctx, c.ID) - if err != nil { - return nil, err - } - return &inspect, nil } @@ -388,22 +422,20 @@ func (c *DockerContainer) followOutput(consumer LogConsumer) { c.consumers = append(c.consumers, consumer) } +// Deprecated: use c.Inspect(ctx).Name instead. // Name gets the name of the container. func (c *DockerContainer) Name(ctx context.Context) (string, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return "", err } return inspect.Name, nil } -// State returns container's running state +// State returns container's running state. func (c *DockerContainer) State(ctx context.Context) (*types.ContainerState, error) { inspect, err := c.inspectRawContainer(ctx) if err != nil { - if c.raw != nil { - return c.raw.State, err - } return nil, err } return inspect.State, nil @@ -411,7 +443,7 @@ func (c *DockerContainer) State(ctx context.Context) (*types.ContainerState, err // Networks gets the names of the networks the container is attached to. func (c *DockerContainer) Networks(ctx context.Context) ([]string, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return []string{}, err } @@ -429,7 +461,7 @@ func (c *DockerContainer) Networks(ctx context.Context) ([]string, error) { // ContainerIP gets the IP address of the primary network within the container. func (c *DockerContainer) ContainerIP(ctx context.Context) (string, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return "", err } @@ -452,7 +484,7 @@ func (c *DockerContainer) ContainerIP(ctx context.Context) (string, error) { func (c *DockerContainer) ContainerIPs(ctx context.Context) ([]string, error) { ips := make([]string, 0) - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return nil, err } @@ -467,7 +499,7 @@ func (c *DockerContainer) ContainerIPs(ctx context.Context) ([]string, error) { // NetworkAliases gets the aliases of the container for the networks it is attached to. func (c *DockerContainer) NetworkAliases(ctx context.Context) (map[string][]string, error) { - inspect, err := c.inspectContainer(ctx) + inspect, err := c.Inspect(ctx) if err != nil { return map[string][]string{}, err } @@ -503,12 +535,12 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string, options ...tce response, err := cli.ContainerExecCreate(ctx, c.ID, processOptions.ExecConfig) if err != nil { - return 0, nil, err + return 0, nil, fmt.Errorf("container exec create: %w", err) } - hijack, err := cli.ContainerExecAttach(ctx, response.ID, types.ExecStartCheck{}) + hijack, err := cli.ContainerExecAttach(ctx, response.ID, container.ExecAttachOptions{}) if err != nil { - return 0, nil, err + return 0, nil, fmt.Errorf("container exec attach: %w", err) } processOptions.Reader = hijack.Reader @@ -523,7 +555,7 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string, options ...tce for { execResp, err := cli.ContainerExecInspect(ctx, response.ID) if err != nil { - return 0, nil, err + return 0, nil, fmt.Errorf("container exec inspect: %w", err) } if !execResp.Running { @@ -595,7 +627,7 @@ func (c *DockerContainer) CopyDirToContainer(ctx context.Context, hostDirPath st // create the directory under its parent parent := filepath.Dir(containerParentPath) - err = c.provider.client.CopyToContainer(ctx, c.ID, parent, buff, types.CopyToContainerOptions{}) + err = c.provider.client.CopyToContainer(ctx, c.ID, parent, buff, container.CopyToContainerOptions{}) if err != nil { return err } @@ -653,7 +685,7 @@ func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func( return err } - err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, types.CopyToContainerOptions{}) + err = c.provider.client.CopyToContainer(ctx, c.ID, "/", buffer, container.CopyToContainerOptions{}) if err != nil { return err } @@ -662,6 +694,29 @@ func (c *DockerContainer) copyToContainer(ctx context.Context, fileContent func( return nil } +// logConsumerWriter is a writer that writes to a LogConsumer. +type logConsumerWriter struct { + log Log + consumers []LogConsumer +} + +// newLogConsumerWriter creates a new logConsumerWriter for logType that sends messages to all consumers. +func newLogConsumerWriter(logType string, consumers []LogConsumer) *logConsumerWriter { + return &logConsumerWriter{ + log: Log{LogType: logType}, + consumers: consumers, + } +} + +// Write writes the p content to all consumers. +func (lw logConsumerWriter) Write(p []byte) (int, error) { + lw.log.Content = p + for _, consumer := range lw.consumers { + consumer.Accept(lw.log) + } + return len(p), nil +} + type LogProductionOption func(*DockerContainer) // WithLogProductionTimeout is a functional option that sets the timeout for the log production. @@ -679,131 +734,107 @@ func (c *DockerContainer) StartLogProducer(ctx context.Context, opts ...LogProdu // startLogProduction will start a concurrent process that will continuously read logs // from the container and will send them to each added LogConsumer. +// // Default log production timeout is 5s. It is used to set the context timeout -// which means that each log-reading loop will last at least the specified timeout -// and that it cannot be cancelled earlier. +// which means that each log-reading loop will last at up to the specified timeout. +// // Use functional option WithLogProductionTimeout() to override default timeout. If it's // lower than 5s and greater than 60s it will be set to 5s or 60s respectively. func (c *DockerContainer) startLogProduction(ctx context.Context, opts ...LogProductionOption) error { - { - c.logProductionMutex.Lock() - defer c.logProductionMutex.Unlock() - - if c.logProductionStop != nil { - return errors.New("log production already started") - } - - c.logProductionStop = make(chan struct{}) - c.logProductionWaitGroup.Add(1) - } - for _, opt := range opts { opt(c) } - minLogProductionTimeout := time.Duration(5 * time.Second) - maxLogProductionTimeout := time.Duration(60 * time.Second) - - if c.logProductionTimeout == nil { + // Validate the log production timeout. + switch { + case c.logProductionTimeout == nil: c.logProductionTimeout = &minLogProductionTimeout - } - - if *c.logProductionTimeout < minLogProductionTimeout { + case *c.logProductionTimeout < minLogProductionTimeout: c.logProductionTimeout = &minLogProductionTimeout - } - - if *c.logProductionTimeout > maxLogProductionTimeout { + case *c.logProductionTimeout > maxLogProductionTimeout: c.logProductionTimeout = &maxLogProductionTimeout } - c.logProductionError = make(chan error, 1) + // Setup the log writers. + stdout := newLogConsumerWriter(StdoutLog, c.consumers) + stderr := newLogConsumerWriter(StderrLog, c.consumers) - go func() { - defer func() { - close(c.logProductionError) - c.logProductionWaitGroup.Done() - }() + // Setup the log production context which will be used to stop the log production. + c.logProductionCtx, c.logProductionCancel = context.WithCancelCause(ctx) - since := "" - // if the socket is closed we will make additional logs request with updated Since timestamp - BEGIN: - options := container.LogsOptions{ - ShowStdout: true, - ShowStderr: true, - Follow: true, - Since: since, - } + // We capture context cancel function to avoid data race with multiple + // calls to startLogProduction. + go func(cancel context.CancelCauseFunc) { + // Ensure the context is cancelled when log productions completes + // so that GetLogProductionErrorChannel functions correctly. + defer cancel(nil) - ctx, cancel := context.WithTimeout(ctx, *c.logProductionTimeout) - defer cancel() + c.logProducer(stdout, stderr) + }(c.logProductionCancel) - r, err := c.provider.client.ContainerLogs(ctx, c.GetContainerID(), options) - if err != nil { - c.logProductionError <- err - return - } - defer c.provider.Close() + return nil +} - for { - select { - case <-c.logProductionStop: - c.logProductionError <- r.Close() - return - default: - h := make([]byte, 8) - _, err := io.ReadFull(r, h) - if err != nil { - // proper type matching requires https://go-review.googlesource.com/c/go/+/250357/ (go 1.16) - if strings.Contains(err.Error(), "use of closed network connection") { - now := time.Now() - since = fmt.Sprintf("%d.%09d", now.Unix(), int64(now.Nanosecond())) - goto BEGIN - } - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - // Probably safe to continue here - continue - } - _, _ = fmt.Fprintf(os.Stderr, "container log error: %+v. %s", err, logStoppedForOutOfSyncMessage) - // if we would continue here, the next header-read will result into random data... - return - } +// logProducer read logs from the container and writes them to stdout, stderr until either: +// - logProductionCtx is done +// - A fatal error occurs +// - No more logs are available +func (c *DockerContainer) logProducer(stdout, stderr io.Writer) { + // Clean up idle client connections. + defer c.provider.Close() - count := binary.BigEndian.Uint32(h[4:]) - if count == 0 { - continue - } - logType := h[0] - if logType > 2 { - _, _ = fmt.Fprintf(os.Stderr, "received invalid log type: %d", logType) - // sometimes docker returns logType = 3 which is an undocumented log type, so treat it as stdout - logType = 1 - } + // Setup the log options, start from the beginning. + options := &container.LogsOptions{ + ShowStdout: true, + ShowStderr: true, + Follow: true, + } - // a map of the log type --> int representation in the header, notice the first is blank, this is stdin, but the go docker client doesn't allow following that in logs - logTypes := []string{"", StdoutLog, StderrLog} + // Use a separate method so that timeout cancel function is + // called correctly. + for c.copyLogsTimeout(stdout, stderr, options) { + } +} - b := make([]byte, count) - _, err = io.ReadFull(r, b) - if err != nil { - // TODO: add-logger: use logger to log out this error - _, _ = fmt.Fprintf(os.Stderr, "error occurred reading log with known length %s", err.Error()) - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - // Probably safe to continue here - continue - } - // we can not continue here as the next read most likely will not be the next header - _, _ = fmt.Fprintln(os.Stderr, logStoppedForOutOfSyncMessage) - return - } - for _, c := range c.consumers { - c.Accept(Log{ - LogType: logTypes[logType], - Content: b, - }) - } - } - } - }() +// copyLogsTimeout copies logs from the container to stdout and stderr with a timeout. +// It returns true if the log production should be retried, false otherwise. +func (c *DockerContainer) copyLogsTimeout(stdout, stderr io.Writer, options *container.LogsOptions) bool { + timeoutCtx, cancel := context.WithTimeout(c.logProductionCtx, *c.logProductionTimeout) + defer cancel() + + err := c.copyLogs(timeoutCtx, stdout, stderr, *options) + switch { + case err == nil: + // No more logs available. + return false + case c.logProductionCtx.Err() != nil: + // Log production was stopped or caller context is done. + return false + case timeoutCtx.Err() != nil, errors.Is(err, net.ErrClosed): + // Timeout or client connection closed, retry. + default: + // Unexpected error, retry. + Logger.Printf("Unexpected error reading logs: %v", err) + } + + // Retry from the last log received. + now := time.Now() + options.Since = fmt.Sprintf("%d.%09d", now.Unix(), int64(now.Nanosecond())) + + return true +} + +// copyLogs copies logs from the container to stdout and stderr. +func (c *DockerContainer) copyLogs(ctx context.Context, stdout, stderr io.Writer, options container.LogsOptions) error { + rc, err := c.provider.client.ContainerLogs(ctx, c.GetContainerID(), options) + if err != nil { + return fmt.Errorf("container logs: %w", err) + } + defer rc.Close() + + if _, err = stdcopy.StdCopy(stdout, stderr, rc); err != nil { + return fmt.Errorf("stdcopy: %w", err) + } return nil } @@ -816,24 +847,71 @@ func (c *DockerContainer) StopLogProducer() error { // stopLogProduction will stop the concurrent process that is reading logs // and sending them to each added LogConsumer func (c *DockerContainer) stopLogProduction() error { - // TODO: Remove locking and wait group once StartLogProducer and StopLogProducer - // have been removed and hence logging can only be started / stopped once. - c.logProductionMutex.Lock() - defer c.logProductionMutex.Unlock() - if c.logProductionStop != nil { - close(c.logProductionStop) - c.logProductionWaitGroup.Wait() - // Set c.logProductionStop to nil so that it can be started again. - c.logProductionStop = nil - return <-c.logProductionError + if c.logProductionCancel == nil { + return nil } + + // Signal the log production to stop. + c.logProductionCancel(errLogProductionStop) + + if err := context.Cause(c.logProductionCtx); err != nil { + switch { + case errors.Is(err, errLogProductionStop): + // Log production was stopped. + return nil + case errors.Is(err, context.DeadlineExceeded), + errors.Is(err, context.Canceled): + // Parent context is done. + return nil + default: + return err + } + } + return nil } // GetLogProductionErrorChannel exposes the only way for the consumer // to be able to listen to errors and react to them. func (c *DockerContainer) GetLogProductionErrorChannel() <-chan error { - return c.logProductionError + if c.logProductionCtx == nil { + return nil + } + + errCh := make(chan error, 1) + go func(ctx context.Context) { + <-ctx.Done() + errCh <- context.Cause(ctx) + close(errCh) + }(c.logProductionCtx) + + return errCh +} + +// connectReaper connects the reaper to the container if it is needed. +func (c *DockerContainer) connectReaper(ctx context.Context) error { + if c.provider.config.RyukDisabled || isReaperImage(c.Image) { + // Reaper is disabled or we are the reaper container. + return nil + } + + reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, c.provider.host), core.SessionID(), c.provider) + if err != nil { + return fmt.Errorf("reaper: %w", err) + } + + if c.terminationSignal, err = reaper.Connect(); err != nil { + return fmt.Errorf("reaper connect: %w", err) + } + + return nil +} + +// cleanupTermSignal triggers the termination signal if it was created and an error occurred. +func (c *DockerContainer) cleanupTermSignal(err error) { + if c.terminationSignal != nil && err != nil { + c.terminationSignal <- true + } } // DockerNetwork represents a network started using Docker @@ -868,7 +946,8 @@ type DockerProvider struct { client client.APIClient host string hostCache string - config TestcontainersConfig + config config.Config + mtx sync.Mutex } // Client gets the docker client used by the provider @@ -894,80 +973,79 @@ var _ ContainerProvider = (*DockerProvider)(nil) // BuildImage will build and image from context and Dockerfile, then return the tag func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (string, error) { - buildOptions, err := img.BuildOptions() - - var buildError error - var resp types.ImageBuildResponse - err = backoff.Retry(func() error { - resp, err = p.client.ImageBuild(ctx, buildOptions.Context, buildOptions) - if err != nil { - buildError = errors.Join(buildError, err) - if isPermanentClientError(err) { - return backoff.Permanent(err) + var buildOptions types.ImageBuildOptions + resp, err := backoff.RetryNotifyWithData( + func() (types.ImageBuildResponse, error) { + var err error + buildOptions, err = img.BuildOptions() + if err != nil { + return types.ImageBuildResponse{}, backoff.Permanent(fmt.Errorf("build options: %w", err)) } - Logger.Printf("Failed to build image: %s, will retry", err) - return err - } - defer p.Close() + defer tryClose(buildOptions.Context) // release resources in any case - return nil - }, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) + resp, err := p.client.ImageBuild(ctx, buildOptions.Context, buildOptions) + if err != nil { + if isPermanentClientError(err) { + return types.ImageBuildResponse{}, backoff.Permanent(fmt.Errorf("build image: %w", err)) + } + return types.ImageBuildResponse{}, err + } + defer p.Close() + + return resp, nil + }, + backoff.WithContext(backoff.NewExponentialBackOff(), ctx), + func(err error, duration time.Duration) { + p.Logger.Printf("Failed to build image: %s, will retry", err) + }, + ) if err != nil { - return "", errors.Join(buildError, err) + return "", err // Error is already wrapped. } + defer resp.Body.Close() + output := io.Discard if img.ShouldPrintBuildLog() { - termFd, isTerm := term.GetFdInfo(os.Stderr) - err = jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stderr, termFd, isTerm, nil) - if err != nil { - return "", err - } + output = os.Stderr } - // need to read the response from Docker, I think otherwise the image - // might not finish building before continuing to execute here - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(resp.Body) - if err != nil { - return "", err + // Always process the output, even if it is not printed + // to ensure that errors during the build process are + // correctly handled. + termFd, isTerm := term.GetFdInfo(output) + if err = jsonmessage.DisplayJSONMessagesStream(resp.Body, output, termFd, isTerm, nil); err != nil { + return "", fmt.Errorf("build image: %w", err) } - _ = resp.Body.Close() - // the first tag is the one we want return buildOptions.Tags[0], nil } -// CreateContainer fulfills a request for a container without starting it -func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerRequest) (Container, error) { - var err error - +// CreateContainer fulfils a request for a container without starting it +func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerRequest) (con Container, err error) { // defer the close of the Docker client connection the soonest defer p.Close() - // Make sure that bridge network exists - // In case it is disabled we will create reaper_default network - if p.DefaultNetwork == "" { - p.DefaultNetwork, err = p.getDefaultNetwork(ctx, p.client) - if err != nil { - return nil, err - } + var defaultNetwork string + defaultNetwork, err = p.ensureDefaultNetwork(ctx) + if err != nil { + return nil, fmt.Errorf("ensure default network: %w", err) } // If default network is not bridge make sure it is attached to the request // as container won't be attached to it automatically // in case of Podman the bridge network is called 'podman' as 'bridge' would conflict - if p.DefaultNetwork != p.defaultBridgeNetworkName { + if defaultNetwork != p.defaultBridgeNetworkName { isAttached := false for _, net := range req.Networks { - if net == p.DefaultNetwork { + if net == defaultNetwork { isAttached = true break } } if !isAttached { - req.Networks = append(req.Networks, p.DefaultNetwork) + req.Networks = append(req.Networks, defaultNetwork) } } @@ -982,56 +1060,51 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque req.Labels = make(map[string]string) } - tcConfig := p.Config().Config - - var termSignal chan bool - // the reaper does not need to start a reaper for itself - isReaperContainer := strings.HasSuffix(imageName, config.ReaperDefaultImage) - if !tcConfig.RyukDisabled && !isReaperContainer { - r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), core.SessionID(), p) - if err != nil { - return nil, fmt.Errorf("%w: creating reaper failed", err) - } - termSignal, err = r.Connect() - if err != nil { - return nil, fmt.Errorf("%w: connecting to reaper failed", err) - } - } - - // Cleanup on error, otherwise set termSignal to nil before successful return. - defer func() { - if termSignal != nil { - termSignal <- true - } - }() - if err = req.Validate(); err != nil { return nil, err } // always append the hub substitutor after the user-defined ones - req.ImageSubstitutors = append(req.ImageSubstitutors, newPrependHubRegistry(tcConfig.HubImageNamePrefix)) + req.ImageSubstitutors = append(req.ImageSubstitutors, newPrependHubRegistry(p.config.HubImageNamePrefix)) - for _, is := range req.ImageSubstitutors { - modifiedTag, err := is.Substitute(imageName) - if err != nil { - return nil, fmt.Errorf("failed to substitute image %s with %s: %w", imageName, is.Description(), err) - } + var platform *specs.Platform - if modifiedTag != imageName { - p.Logger.Printf("✍đŸŧ Replacing image with %s. From: %s to %s\n", is.Description(), imageName, modifiedTag) - imageName = modifiedTag - } + defaultHooks := []ContainerLifecycleHooks{ + DefaultLoggingHook(p.Logger), } - var platform *specs.Platform + origLifecycleHooks := req.LifecycleHooks + req.LifecycleHooks = []ContainerLifecycleHooks{ + combineContainerHooks(defaultHooks, req.LifecycleHooks), + } if req.ShouldBuildImage() { + if err = req.buildingHook(ctx); err != nil { + return nil, err + } + imageName, err = p.BuildImage(ctx, &req) if err != nil { return nil, err } + + req.Image = imageName + if err = req.builtHook(ctx); err != nil { + return nil, err + } } else { + for _, is := range req.ImageSubstitutors { + modifiedTag, err := is.Substitute(imageName) + if err != nil { + return nil, fmt.Errorf("failed to substitute image %s with %s: %w", imageName, is.Description(), err) + } + + if modifiedTag != imageName { + p.Logger.Printf("✍đŸŧ Replacing image with %s. From: %s to %s\n", is.Description(), imageName, modifiedTag) + imageName = modifiedTag + } + } + if req.ImagePlatform != "" { p, err := platforms.Parse(req.ImagePlatform) if err != nil { @@ -1045,7 +1118,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque if req.AlwaysPullImage { shouldPullImage = true // If requested always attempt to pull image } else { - image, _, err := p.client.ImageInspectWithRaw(ctx, imageName) + img, _, err := p.client.ImageInspectWithRaw(ctx, imageName) if err != nil { if client.IsErrNotFound(err) { shouldPullImage = true @@ -1053,13 +1126,13 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque return nil, err } } - if platform != nil && (image.Architecture != platform.Architecture || image.Os != platform.OS) { + if platform != nil && (img.Architecture != platform.Architecture || img.Os != platform.OS) { shouldPullImage = true } } if shouldPullImage { - pullOpt := types.ImagePullOptions{ + pullOpt := image.PullOptions{ Platform: req.ImagePlatform, // may be empty } if err := p.attemptToPullImage(ctx, imageName, pullOpt); err != nil { @@ -1068,11 +1141,10 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque } } - if !isReaperContainer { - // add the labels that the reaper will use to terminate the container to the request - for k, v := range core.DefaultLabels(core.SessionID()) { - req.Labels[k] = v - } + if !isReaperImage(imageName) { + // Add the labels that identify this as a testcontainers container and + // allow the reaper to terminate it if requested. + AddGenericLabels(req.Labels) } dockerInput := &container.Config{ @@ -1095,13 +1167,12 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque networkingConfig := &network.NetworkingConfig{} // default hooks include logger hook and pre-create hook - defaultHooks := []ContainerLifecycleHooks{ - DefaultLoggingHook(p.Logger), - defaultPreCreateHook(ctx, p, req, dockerInput, hostConfig, networkingConfig), + defaultHooks = append(defaultHooks, + defaultPreCreateHook(p, dockerInput, hostConfig, networkingConfig), defaultCopyFileToContainerHook(req.Files), defaultLogConsumersHook(req.LogConsumerCfg), defaultReadinessHook(), - } + ) // in the case the container needs to access a local port // we need to forward the local port to the container @@ -1111,13 +1182,28 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque // forward the host ports to the container ports. sshdForwardPortsHook, err := exposeHostPorts(ctx, &req, req.HostAccessPorts...) if err != nil { - return nil, fmt.Errorf("failed to expose host ports: %w", err) + return nil, fmt.Errorf("expose host ports: %w", err) } + defer func() { + if err != nil && con == nil { + // Container setup failed so ensure we clean up the sshd container too. + ctr := &DockerContainer{ + provider: p, + logger: p.Logger, + lifecycleHooks: []ContainerLifecycleHooks{sshdForwardPortsHook}, + } + err = errors.Join(ctr.terminatingHook(ctx)) + } + }() + defaultHooks = append(defaultHooks, sshdForwardPortsHook) } - req.LifecycleHooks = []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)} + // Combine with the original LifecycleHooks to avoid duplicate logging hooks. + req.LifecycleHooks = []ContainerLifecycleHooks{ + combineContainerHooks(defaultHooks, origLifecycleHooks), + } err = req.creatingHook(ctx) if err != nil { @@ -1126,7 +1212,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque resp, err := p.client.ContainerCreate(ctx, dockerInput, hostConfig, networkingConfig, platform, req.Name) if err != nil { - return nil, err + return nil, fmt.Errorf("container create: %w", err) } // #248: If there is more than one network specified in the request attach newly created container to them one by one @@ -1141,34 +1227,41 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque } err = p.client.NetworkConnect(ctx, nw.ID, resp.ID, &endpointSetting) if err != nil { - return nil, err + return nil, fmt.Errorf("network connect: %w", err) } } } } - c := &DockerContainer{ - ID: resp.ID, - WaitingFor: req.WaitingFor, - Image: imageName, - imageWasBuilt: req.ShouldBuildImage(), - keepBuiltImage: req.ShouldKeepBuiltImage(), - sessionID: core.SessionID(), - provider: p, - terminationSignal: termSignal, - logger: p.Logger, - lifecycleHooks: req.LifecycleHooks, + // This should match the fields set in ContainerFromDockerResponse. + ctr := &DockerContainer{ + ID: resp.ID, + WaitingFor: req.WaitingFor, + Image: imageName, + imageWasBuilt: req.ShouldBuildImage(), + keepBuiltImage: req.ShouldKeepBuiltImage(), + sessionID: req.sessionID(), + exposedPorts: req.ExposedPorts, + provider: p, + logger: p.Logger, + lifecycleHooks: req.LifecycleHooks, } - err = c.createdHook(ctx) - if err != nil { - return nil, err + if err = ctr.connectReaper(ctx); err != nil { + return ctr, err // No wrap as it would stutter. } - // Disable cleanup on success - termSignal = nil + // Wrapped so the returned error is passed to the cleanup function. + defer func(ctr *DockerContainer) { + ctr.cleanupTermSignal(err) + }(ctr) - return c, nil + if err = ctr.createdHook(ctx); err != nil { + // Return the container to allow caller to clean up. + return ctr, fmt.Errorf("created hook: %w", err) + } + + return ctr, nil } func (p *DockerProvider) findContainerByName(ctx context.Context, name string) (*types.Container, error) { @@ -1180,7 +1273,7 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) ( filter := filters.NewArgs(filters.Arg("name", fmt.Sprintf("^%s$", name))) containers, err := p.client.ContainerList(ctx, container.ListOptions{Filters: filter}) if err != nil { - return nil, err + return nil, fmt.Errorf("container list: %w", err) } defer p.Close() @@ -1191,26 +1284,32 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) ( } func (p *DockerProvider) waitContainerCreation(ctx context.Context, name string) (*types.Container, error) { - var container *types.Container - return container, backoff.Retry(func() error { - c, err := p.findContainerByName(ctx, name) - if err != nil { - if !errdefs.IsNotFound(err) && isPermanentClientError(err) { - return backoff.Permanent(err) + return backoff.RetryNotifyWithData( + func() (*types.Container, error) { + c, err := p.findContainerByName(ctx, name) + if err != nil { + if !errdefs.IsNotFound(err) && isPermanentClientError(err) { + return nil, backoff.Permanent(err) + } + return nil, err } - return err - } - if c == nil { - return fmt.Errorf("container %s not found", name) - } - - container = c - return nil - }, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) + if c == nil { + return nil, errdefs.NotFound(fmt.Errorf("container %s not found", name)) + } + return c, nil + }, + backoff.WithContext(backoff.NewExponentialBackOff(), ctx), + func(err error, duration time.Duration) { + if errdefs.IsNotFound(err) { + return + } + p.Logger.Printf("Waiting for container. Got an error: %v; Retrying in %d seconds", err, duration/time.Second) + }, + ) } -func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req ContainerRequest) (Container, error) { +func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req ContainerRequest) (con Container, err error) { c, err := p.findContainerByName(ctx, req.Name) if err != nil { return nil, err @@ -1229,20 +1328,26 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain } } - sessionID := core.SessionID() - - tcConfig := p.Config().Config + sessionID := req.sessionID() var termSignal chan bool - if !tcConfig.RyukDisabled { - r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p) + if !p.config.RyukDisabled { + r, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p) if err != nil { - return nil, fmt.Errorf("%w: creating reaper failed", err) + return nil, fmt.Errorf("reaper: %w", err) } - termSignal, err = r.Connect() + + termSignal, err := r.Connect() if err != nil { - return nil, fmt.Errorf("%w: connecting to reaper failed", err) + return nil, fmt.Errorf("reaper connect: %w", err) } + + // Cleanup on error. + defer func() { + if err != nil { + termSignal <- true + } + }() } // default hooks include logger hook and pre-create hook @@ -1257,6 +1362,7 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain WaitingFor: req.WaitingFor, Image: c.Image, sessionID: sessionID, + exposedPorts: req.ExposedPorts, provider: p, terminationSignal: termSignal, logger: p.Logger, @@ -1280,34 +1386,39 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain // attemptToPullImage tries to pull the image while respecting the ctx cancellations. // Besides, if the image cannot be pulled due to ErrorNotFound then no need to retry but terminate immediately. -func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pullOpt types.ImagePullOptions) error { +func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pullOpt image.PullOptions) error { registry, imageAuth, err := DockerImageAuth(ctx, tag) if err != nil { - p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is:%s", registry, tag, err) + p.Logger.Printf("Failed to get image auth for %s. Setting empty credentials for the image: %s. Error is: %s", registry, tag, err) } else { // see https://github.com/docker/docs/blob/e8e1204f914767128814dca0ea008644709c117f/engine/api/sdk/examples.md?plain=1#L649-L657 encodedJSON, err := json.Marshal(imageAuth) if err != nil { - p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is:%s", tag, err) + p.Logger.Printf("Failed to marshal image auth. Setting empty credentials for the image: %s. Error is: %s", tag, err) } else { pullOpt.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON) } } var pull io.ReadCloser - err = backoff.Retry(func() error { - pull, err = p.client.ImagePull(ctx, tag, pullOpt) - if err != nil { - if isPermanentClientError(err) { - return backoff.Permanent(err) + err = backoff.RetryNotify( + func() error { + pull, err = p.client.ImagePull(ctx, tag, pullOpt) + if err != nil { + if isPermanentClientError(err) { + return backoff.Permanent(err) + } + return err } - Logger.Printf("Failed to pull image: %s, will retry", err) - return err - } - defer p.Close() - - return nil - }, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) + defer p.Close() + + return nil + }, + backoff.WithContext(backoff.NewExponentialBackOff(), ctx), + func(err error, duration time.Duration) { + p.Logger.Printf("Failed to pull image: %s, will retry", err) + }, + ) if err != nil { return err } @@ -1344,37 +1455,47 @@ func (p *DockerProvider) RunContainer(ctx context.Context, req ContainerRequest) // Config provides the TestcontainersConfig read from $HOME/.testcontainers.properties or // the environment variables func (p *DockerProvider) Config() TestcontainersConfig { - return p.config + return TestcontainersConfig{ + Host: p.config.Host, + TLSVerify: p.config.TLSVerify, + CertPath: p.config.CertPath, + RyukDisabled: p.config.RyukDisabled, + RyukPrivileged: p.config.RyukPrivileged, + Config: p.config, + } } // DaemonHost gets the host or ip of the Docker daemon where ports are exposed on // Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel -// You can use the "TC_HOST" env variable to set this yourself +// You can use the "TESTCONTAINERS_HOST_OVERRIDE" env variable to set this yourself func (p *DockerProvider) DaemonHost(ctx context.Context) (string, error) { - return daemonHost(ctx, p) + p.mtx.Lock() + defer p.mtx.Unlock() + + return p.daemonHostLocked(ctx) } -func daemonHost(ctx context.Context, p *DockerProvider) (string, error) { +func (p *DockerProvider) daemonHostLocked(ctx context.Context) (string, error) { if p.hostCache != "" { return p.hostCache, nil } - host, exists := os.LookupEnv("TC_HOST") + host, exists := os.LookupEnv("TESTCONTAINERS_HOST_OVERRIDE") if exists { p.hostCache = host return p.hostCache, nil } // infer from Docker host - url, err := url.Parse(p.client.DaemonHost()) + daemonURL, err := url.Parse(p.client.DaemonHost()) if err != nil { return "", err } defer p.Close() - switch url.Scheme { + switch daemonURL.Scheme { case "http", "https", "tcp": - p.hostCache = url.Hostname() + p.hostCache = daemonURL.Hostname() case "unix", "npipe": if core.InAContainer() { ip, err := p.GetGatewayIP(ctx) @@ -1397,65 +1518,55 @@ func daemonHost(ctx context.Context, p *DockerProvider) (string, error) { // Deprecated: use network.New instead // CreateNetwork returns the object representing a new network identified by its name -func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) (Network, error) { - var err error - +func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) (net Network, err error) { // defer the close of the Docker client connection the soonest defer p.Close() - // Make sure that bridge network exists - // In case it is disabled we will create reaper_default network - if p.DefaultNetwork == "" { - if p.DefaultNetwork, err = p.getDefaultNetwork(ctx, p.client); err != nil { - return nil, err - } + if _, err = p.ensureDefaultNetwork(ctx); err != nil { + return nil, fmt.Errorf("ensure default network: %w", err) } if req.Labels == nil { req.Labels = make(map[string]string) } - tcConfig := p.Config().Config - - nc := types.NetworkCreate{ - Driver: req.Driver, - CheckDuplicate: req.CheckDuplicate, - Internal: req.Internal, - EnableIPv6: req.EnableIPv6, - Attachable: req.Attachable, - Labels: req.Labels, - IPAM: req.IPAM, + nc := network.CreateOptions{ + Driver: req.Driver, + Internal: req.Internal, + EnableIPv6: req.EnableIPv6, + Attachable: req.Attachable, + Labels: req.Labels, + IPAM: req.IPAM, } - sessionID := core.SessionID() + sessionID := req.sessionID() var termSignal chan bool - if !tcConfig.RyukDisabled { - r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p) + if !p.config.RyukDisabled { + r, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p) if err != nil { - return nil, fmt.Errorf("%w: creating network reaper failed", err) + return nil, fmt.Errorf("reaper: %w", err) } - termSignal, err = r.Connect() + + termSignal, err := r.Connect() if err != nil { - return nil, fmt.Errorf("%w: connecting to network reaper failed", err) + return nil, fmt.Errorf("reaper connect: %w", err) } - } - // add the labels that the reaper will use to terminate the network to the request - for k, v := range core.DefaultLabels(sessionID) { - req.Labels[k] = v + // Cleanup on error. + defer func() { + if err != nil { + termSignal <- true + } + }() } - // Cleanup on error, otherwise set termSignal to nil before successful return. - defer func() { - if termSignal != nil { - termSignal <- true - } - }() + // add the labels that the reaper will use to terminate the network to the request + core.AddDefaultLabels(sessionID, req.Labels) response, err := p.client.NetworkCreate(ctx, req.Name, nc) if err != nil { - return &DockerNetwork{}, err + return &DockerNetwork{}, fmt.Errorf("create network: %w", err) } n := &DockerNetwork{ @@ -1466,19 +1577,16 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) provider: p, } - // Disable cleanup on success - termSignal = nil - return n, nil } // GetNetwork returns the object representing the network identified by its name -func (p *DockerProvider) GetNetwork(ctx context.Context, req NetworkRequest) (types.NetworkResource, error) { - networkResource, err := p.client.NetworkInspect(ctx, req.Name, types.NetworkInspectOptions{ +func (p *DockerProvider) GetNetwork(ctx context.Context, req NetworkRequest) (network.Inspect, error) { + networkResource, err := p.client.NetworkInspect(ctx, req.Name, network.InspectOptions{ Verbose: true, }) if err != nil { - return types.NetworkResource{}, err + return network.Inspect{}, err } return networkResource, err @@ -1486,22 +1594,20 @@ func (p *DockerProvider) GetNetwork(ctx context.Context, req NetworkRequest) (ty func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) { // Use a default network as defined in the DockerProvider - if p.DefaultNetwork == "" { - var err error - p.DefaultNetwork, err = p.getDefaultNetwork(ctx, p.client) - if err != nil { - return "", err - } + defaultNetwork, err := p.ensureDefaultNetwork(ctx) + if err != nil { + return "", fmt.Errorf("ensure default network: %w", err) } - nw, err := p.GetNetwork(ctx, NetworkRequest{Name: p.DefaultNetwork}) + + nw, err := p.GetNetwork(ctx, NetworkRequest{Name: defaultNetwork}) if err != nil { return "", err } var ip string - for _, config := range nw.IPAM.Config { - if config.Gateway != "" { - ip = config.Gateway + for _, cfg := range nw.IPAM.Config { + if cfg.Gateway != "" { + ip = cfg.Gateway break } } @@ -1512,76 +1618,102 @@ func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) { return ip, nil } -func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APIClient) (string, error) { - // Get list of available networks - networkResources, err := cli.NetworkList(ctx, types.NetworkListOptions{}) - if err != nil { - return "", err - } +// ensureDefaultNetwork ensures that defaultNetwork is set and creates +// it if it does not exist, returning its value. +// It is safe to call this method concurrently. +func (p *DockerProvider) ensureDefaultNetwork(ctx context.Context) (string, error) { + p.mtx.Lock() + defer p.mtx.Unlock() - reaperNetwork := ReaperDefault + if p.defaultNetwork != "" { + // Already set. + return p.defaultNetwork, nil + } - reaperNetworkExists := false + networkResources, err := p.client.NetworkList(ctx, network.ListOptions{}) + if err != nil { + return "", fmt.Errorf("network list: %w", err) + } + // TODO: remove once we have docker context support via #2810 + // Prefer the default bridge network if it exists. + // This makes the results stable as network list order is not guaranteed. for _, net := range networkResources { - if net.Name == p.defaultBridgeNetworkName { - return p.defaultBridgeNetworkName, nil + switch net.Name { + case p.defaultBridgeNetworkName: + p.defaultNetwork = p.defaultBridgeNetworkName + return p.defaultNetwork, nil + case ReaperDefault: + p.defaultNetwork = ReaperDefault } + } - if net.Name == reaperNetwork { - reaperNetworkExists = true - } + if p.defaultNetwork != "" { + return p.defaultNetwork, nil } - // Create a bridge network for the container communications - if !reaperNetworkExists { - _, err = cli.NetworkCreate(ctx, reaperNetwork, types.NetworkCreate{ - Driver: Bridge, - Attachable: true, - Labels: core.DefaultLabels(core.SessionID()), - }) - if err != nil { - return "", err - } + // Create a bridge network for the container communications. + _, err = p.client.NetworkCreate(ctx, ReaperDefault, network.CreateOptions{ + Driver: Bridge, + Attachable: true, + Labels: GenericLabels(), + }) + // If the network already exists, we can ignore the error as that can + // happen if we are running multiple tests in parallel and we only + // need to ensure that the network exists. + if err != nil && !errdefs.IsConflict(err) { + return "", fmt.Errorf("network create: %w", err) } - return reaperNetwork, nil + p.defaultNetwork = ReaperDefault + + return p.defaultNetwork, nil } -// containerFromDockerResponse builds a Docker container struct from the response of the Docker API -func containerFromDockerResponse(ctx context.Context, response types.Container) (*DockerContainer, error) { - provider, err := NewDockerProvider() - if err != nil { - return nil, err +// ContainerFromType builds a Docker container struct from the response of the Docker API +func (p *DockerProvider) ContainerFromType(ctx context.Context, response types.Container) (ctr *DockerContainer, err error) { + exposedPorts := make([]string, len(response.Ports)) + for i, port := range response.Ports { + exposedPorts[i] = fmt.Sprintf("%d/%s", port.PublicPort, port.Type) } - container := DockerContainer{} - - container.ID = response.ID - container.WaitingFor = nil - container.Image = response.Image - container.imageWasBuilt = false - - container.logger = provider.Logger - container.lifecycleHooks = []ContainerLifecycleHooks{ - DefaultLoggingHook(container.logger), + // This should match the fields set in CreateContainer. + ctr = &DockerContainer{ + ID: response.ID, + Image: response.Image, + imageWasBuilt: false, + sessionID: response.Labels[core.LabelSessionID], + isRunning: response.State == "running", + exposedPorts: exposedPorts, + provider: p, + logger: p.Logger, + lifecycleHooks: []ContainerLifecycleHooks{ + DefaultLoggingHook(p.Logger), + }, } - container.provider = provider - container.sessionID = core.SessionID() - container.consumers = []LogConsumer{} - container.isRunning = response.State == "running" + if err = ctr.connectReaper(ctx); err != nil { + return nil, err + } - // the termination signal should be obtained from the reaper - container.terminationSignal = nil + // Wrapped so the returned error is passed to the cleanup function. + defer func(ctr *DockerContainer) { + ctr.cleanupTermSignal(err) + }(ctr) // populate the raw representation of the container - _, err = container.inspectRawContainer(ctx) + jsonRaw, err := ctr.inspectRawContainer(ctx) if err != nil { - return nil, err + // Return the container to allow caller to clean up. + return ctr, fmt.Errorf("inspect raw container: %w", err) + } + + // the health status of the container, if any + if health := jsonRaw.State.Health; health != nil { + ctr.healthStatus = health.Status } - return &container, nil + return ctr, nil } // ListImages list images from the provider. If an image has multiple Tags, each tag is reported @@ -1589,7 +1721,7 @@ func containerFromDockerResponse(ctx context.Context, response types.Container) func (p *DockerProvider) ListImages(ctx context.Context) ([]ImageInfo, error) { images := []ImageInfo{} - imageList, err := p.client.ImageList(ctx, types.ImageListOptions{}) + imageList, err := p.client.ImageList(ctx, image.ListOptions{}) if err != nil { return images, fmt.Errorf("listing images %w", err) } @@ -1631,8 +1763,8 @@ func (p *DockerProvider) SaveImages(ctx context.Context, output string, images . } // PullImage pulls image from registry -func (p *DockerProvider) PullImage(ctx context.Context, image string) error { - return p.attemptToPullImage(ctx, image, types.ImagePullOptions{}) +func (p *DockerProvider) PullImage(ctx context.Context, img string) error { + return p.attemptToPullImage(ctx, img, image.PullOptions{}) } var permanentClientErrors = []func(error) bool{ @@ -1641,6 +1773,7 @@ var permanentClientErrors = []func(error) bool{ errdefs.IsUnauthorized, errdefs.IsForbidden, errdefs.IsNotImplemented, + errdefs.IsSystem, } func isPermanentClientError(err error) bool { @@ -1651,3 +1784,10 @@ func isPermanentClientError(err error) bool { } return false } + +func tryClose(r io.Reader) { + rc, ok := r.(io.Closer) + if ok { + _ = rc.Close() + } +} diff --git a/docker_auth.go b/docker_auth.go index 04b8527ccb..58b3ef2637 100644 --- a/docker_auth.go +++ b/docker_auth.go @@ -2,10 +2,15 @@ package testcontainers import ( "context" + "crypto/md5" "encoding/base64" + "encoding/hex" "encoding/json" + "errors" + "fmt" "net/url" "os" + "sync" "github.com/cpuguy83/dockercfg" "github.com/docker/docker/api/types/registry" @@ -16,19 +21,28 @@ import ( // defaultRegistryFn is variable overwritten in tests to check for behaviour with different default values. var defaultRegistryFn = defaultRegistry +// getRegistryCredentials is a variable overwritten in tests to mock the dockercfg.GetRegistryCredentials function. +var getRegistryCredentials = dockercfg.GetRegistryCredentials + // DockerImageAuth returns the auth config for the given Docker image, extracting first its Docker registry. // Finally, it will use the credential helpers to extract the information from the docker config file // for that registry, if it exists. func DockerImageAuth(ctx context.Context, image string) (string, registry.AuthConfig, error) { - defaultRegistry := defaultRegistryFn(ctx) - reg := core.ExtractRegistry(image, defaultRegistry) - - cfgs, err := getDockerAuthConfigs() + configs, err := getDockerAuthConfigs() if err != nil { + reg := core.ExtractRegistry(image, defaultRegistryFn(ctx)) return reg, registry.AuthConfig{}, err } - if cfg, ok := getRegistryAuth(reg, cfgs); ok { + return dockerImageAuth(ctx, image, configs) +} + +// dockerImageAuth returns the auth config for the given Docker image. +func dockerImageAuth(ctx context.Context, image string, configs map[string]registry.AuthConfig) (string, registry.AuthConfig, error) { + defaultRegistry := defaultRegistryFn(ctx) + reg := core.ExtractRegistry(image, defaultRegistry) + + if cfg, ok := getRegistryAuth(reg, configs); ok { return reg, cfg, nil } @@ -79,48 +93,167 @@ func defaultRegistry(ctx context.Context) string { return info.IndexServerAddress } +// authConfigResult is a result looking up auth details for key. +type authConfigResult struct { + key string + cfg registry.AuthConfig + err error +} + +// credentialsCache is a cache for registry credentials. +type credentialsCache struct { + entries map[string]credentials + mtx sync.RWMutex +} + +// credentials represents the username and password for a registry. +type credentials struct { + username string + password string +} + +var creds = &credentialsCache{entries: map[string]credentials{}} + +// AuthConfig updates the details in authConfig for the given hostname +// as determined by the details in configKey. +func (c *credentialsCache) AuthConfig(hostname, configKey string, authConfig *registry.AuthConfig) error { + u, p, err := creds.get(hostname, configKey) + if err != nil { + return err + } + + if u != "" { + authConfig.Username = u + authConfig.Password = p + } else { + authConfig.IdentityToken = p + } + + return nil +} + +// get returns the username and password for the given hostname +// as determined by the details in configPath. +// If the username is empty, the password is an identity token. +func (c *credentialsCache) get(hostname, configKey string) (string, string, error) { + key := configKey + ":" + hostname + c.mtx.RLock() + entry, ok := c.entries[key] + c.mtx.RUnlock() + + if ok { + return entry.username, entry.password, nil + } + + // No entry found, request and cache. + user, password, err := getRegistryCredentials(hostname) + if err != nil { + return "", "", fmt.Errorf("getting credentials for %s: %w", hostname, err) + } + + c.mtx.Lock() + c.entries[key] = credentials{username: user, password: password} + c.mtx.Unlock() + + return user, password, nil +} + +// configKey returns a key to use for caching credentials based on +// the contents of the currently active config. +func configKey(cfg *dockercfg.Config) (string, error) { + h := md5.New() + if err := json.NewEncoder(h).Encode(cfg); err != nil { + return "", fmt.Errorf("encode config: %w", err) + } + + return hex.EncodeToString(h.Sum(nil)), nil +} + // 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) { cfg, err := getDockerConfig() + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return map[string]registry.AuthConfig{}, nil + } + + return nil, err + } + + key, err := configKey(cfg) if err != nil { return nil, err } - cfgs := map[string]registry.AuthConfig{} + size := len(cfg.AuthConfigs) + len(cfg.CredentialHelpers) + cfgs := make(map[string]registry.AuthConfig, size) + results := make(chan authConfigResult, size) + var wg sync.WaitGroup + wg.Add(size) 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() - if v.Username == "" && v.Password == "" { - u, p, _ := dockercfg.GetRegistryCredentials(k) - ac.Username = u - ac.Password = p - } + 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.Auth == "" { - ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password)) - } + switch { + case ac.Username == "" && ac.Password == "": + // Look up credentials from the credential store. + if err := creds.AuthConfig(k, key, &ac); err != nil { + results <- authConfigResult{err: err} + return + } + case ac.Auth == "": + // Create auth from the username and password encoding. + ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password)) + } - cfgs[k] = ac + results <- authConfigResult{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 + // 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(k string) { + defer wg.Done() + + var ac registry.AuthConfig + if err := creds.AuthConfig(k, key, &ac); err != nil { + results <- authConfigResult{err: err} + return + } - cfgs[k] = ac + results <- authConfigResult{key: k, cfg: ac} + }(k) + } + + go func() { + wg.Wait() + close(results) + }() + + var errs []error + for result := range results { + if result.err != nil { + errs = append(errs, result.err) + continue + } + + cfgs[result.key] = result.cfg + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) } return cfgs, nil @@ -130,20 +263,20 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) { // 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config // 2. the DOCKER_CONFIG environment variable, as the path to the config file // 3. else it will load the default config file, which is ~/.docker/config.json -func getDockerConfig() (dockercfg.Config, error) { - dockerAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") - if dockerAuthConfig != "" { - cfg := dockercfg.Config{} - err := json.Unmarshal([]byte(dockerAuthConfig), &cfg) - if err == nil { - return cfg, nil +func getDockerConfig() (*dockercfg.Config, error) { + if env := os.Getenv("DOCKER_AUTH_CONFIG"); env != "" { + var cfg dockercfg.Config + if err := json.Unmarshal([]byte(env), &cfg); err != nil { + return nil, fmt.Errorf("unmarshal DOCKER_AUTH_CONFIG: %w", err) } + + return &cfg, nil } cfg, err := dockercfg.LoadDefaultConfig() if err != nil { - return cfg, err + return nil, fmt.Errorf("load default config: %w", err) } - return cfg, nil + return &cfg, nil } diff --git a/docker_auth_test.go b/docker_auth_test.go index 33602a4347..5d397d53c8 100644 --- a/docker_auth_test.go +++ b/docker_auth_test.go @@ -2,161 +2,137 @@ package testcontainers import ( "context" + _ "embed" + "encoding/base64" "fmt" + "net" "os" "path/filepath" "testing" "github.com/cpuguy83/dockercfg" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/wait" ) -const exampleAuth = "https://example-auth.com" - -var testDockerConfigDirPath = filepath.Join("testdata", ".docker") - -var indexDockerIO = core.IndexDockerIO - -func TestGetDockerConfig(t *testing.T) { - const expectedErrorMessage = "Expected to find %s in auth configs" - - // Verify that the default docker config file exists before any test in this suite runs. - // Then, we can safely run the tests that rely on it. - defaultCfg, err := dockercfg.LoadDefaultConfig() - require.NoError(t, err) - require.NotEmpty(t, defaultCfg) +const ( + exampleAuth = "https://example-auth.com" + privateRegistry = "https://my.private.registry" + exampleRegistry = "https://example.com" +) - t.Run("without DOCKER_CONFIG env var retrieves default", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", "") +func Test_getDockerConfig(t *testing.T) { + expectedConfig := &dockercfg.Config{ + AuthConfigs: map[string]dockercfg.AuthConfig{ + core.IndexDockerIO: {}, + exampleRegistry: {}, + privateRegistry: {}, + }, + CredentialsStore: "desktop", + } + t.Run("HOME/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata") cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) + require.Equal(t, expectedConfig, cfg) + }) + + t.Run("HOME/not-found", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") - assert.Equal(t, defaultCfg, cfg) + cfg, err := getDockerConfig() + require.ErrorIs(t, err, os.ErrNotExist) + require.Nil(t, cfg) }) - t.Run("with DOCKER_CONFIG env var pointing to a non-existing file raises error", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join(testDockerConfigDirPath, "non-existing")) + t.Run("HOME/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "invalid-config") cfg, err := getDockerConfig() - require.Error(t, err) - require.Empty(t, cfg) + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) - t.Run("with DOCKER_CONFIG env var", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + t.Run("DOCKER_AUTH_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig) cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Len(t, cfg.AuthConfigs, 3) + require.Equal(t, expectedConfig, cfg) + }) - authCfgs := cfg.AuthConfigs + t.Run("DOCKER_AUTH_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_AUTH_CONFIG", `{"auths": []}`) - if _, ok := authCfgs[indexDockerIO]; !ok { - t.Errorf(expectedErrorMessage, indexDockerIO) - } - if _, ok := authCfgs["https://example.com"]; !ok { - t.Errorf(expectedErrorMessage, "https://example.com") - } - if _, ok := authCfgs["https://my.private.registry"]; !ok { - t.Errorf(expectedErrorMessage, "https://my.private.registry") - } + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) - t.Run("DOCKER_AUTH_CONFIG env var takes precedence", func(t *testing.T) { - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "`+exampleAuth+`": {} - }, - "credsStore": "desktop" - }`) - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + t.Run("DOCKER_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", ".docker")) cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Len(t, cfg.AuthConfigs, 1) + require.Equal(t, expectedConfig, cfg) + }) - authCfgs := cfg.AuthConfigs + t.Run("DOCKER_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", "invalid-config", ".docker")) - if _, ok := authCfgs[indexDockerIO]; ok { - t.Errorf("Not expected to find %s in auth configs", indexDockerIO) - } - if _, ok := authCfgs[exampleAuth]; !ok { - t.Errorf(expectedErrorMessage, exampleAuth) - } + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) +} +func TestDockerImageAuth(t *testing.T) { t.Run("retrieve auth with DOCKER_AUTH_CONFIG env var", func(t *testing.T) { - base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret - - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + username, password := "gopher", "secret" + creds := setAuthConfig(t, exampleAuth, username, password) registry, cfg, err := DockerImageAuth(context.Background(), exampleAuth+"/my/image:latest") require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Equal(t, exampleAuth, registry) - assert.Equal(t, "gopher", cfg.Username) - assert.Equal(t, "secret", cfg.Password) - assert.Equal(t, base64, cfg.Auth) + require.Equal(t, exampleAuth, registry) + require.Equal(t, username, cfg.Username) + require.Equal(t, password, cfg.Password) + require.Equal(t, creds, cfg.Auth) }) t.Run("match registry authentication by host", func(t *testing.T) { - base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret imageReg := "example-auth.com" imagePath := "/my/image:latest" - - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + base64 := setAuthConfig(t, exampleAuth, "gopher", "secret") registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Equal(t, imageReg, registry) - assert.Equal(t, "gopher", cfg.Username) - assert.Equal(t, "secret", cfg.Password) - assert.Equal(t, base64, cfg.Auth) + require.Equal(t, imageReg, registry) + require.Equal(t, "gopher", cfg.Username) + require.Equal(t, "secret", cfg.Password) + require.Equal(t, base64, cfg.Auth) }) t.Run("fail to match registry authentication due to invalid host", func(t *testing.T) { - base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret imageReg := "example-auth.com" imagePath := "/my/image:latest" invalidRegistryURL := "://invalid-host" - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "`+invalidRegistryURL+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + setAuthConfig(t, invalidRegistryURL, "gopher", "secret") registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) require.Empty(t, cfg) - - assert.Equal(t, imageReg, registry) + require.Equal(t, imageReg, registry) }) t.Run("fail to match registry authentication by host with empty URL scheme creds and missing default", func(t *testing.T) { @@ -168,22 +144,15 @@ func TestGetDockerConfig(t *testing.T) { return "" } - base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret imageReg := "" imagePath := "image:latest" - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "example-auth.com": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + setAuthConfig(t, "example-auth.com", "gopher", "secret") registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) require.Empty(t, cfg) - - assert.Equal(t, imageReg, registry) + require.Equal(t, imageReg, registry) }) } @@ -198,13 +167,14 @@ func TestBuildContainerFromDockerfile(t *testing.T) { WaitingFor: wait.ForLog("Ready to accept connections"), } - redisC, err := prepareRedisImage(ctx, req, t) + redisC, err := prepareRedisImage(ctx, req) + CleanupContainer(t, redisC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, redisC) } // removeImageFromLocalCache removes the image from the local cache -func removeImageFromLocalCache(t *testing.T, image string) { +func removeImageFromLocalCache(t *testing.T, img string) { + t.Helper() ctx := context.Background() testcontainersClient, err := NewDockerClientWithOpts(ctx, client.WithVersion(daemonMaxVersion)) @@ -213,26 +183,20 @@ func removeImageFromLocalCache(t *testing.T, image string) { } defer testcontainersClient.Close() - _, err = testcontainersClient.ImageRemove(ctx, image, types.ImageRemoveOptions{ + _, err = testcontainersClient.ImageRemove(ctx, img, image.RemoveOptions{ Force: true, PruneChildren: true, }) - if err != nil { - t.Logf("could not remove image %s: %v\n", image, err) + if err != nil && !client.IsErrNotFound(err) { + t.Logf("could not remove image %s: %v\n", img, err) } } func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) { - // using the same credentials as in the Docker Registry - base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:5001": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + registryHost := prepareLocalRegistryWithAuth(t) - prepareLocalRegistryWithAuth(t) + // using the same credentials as in the Docker Registry + setAuthConfig(t, registryHost, "testuser", "testpassword") ctx := context.Background() @@ -240,28 +204,26 @@ func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) { FromDockerfile: FromDockerfile{ Context: "./testdata", Dockerfile: "auth.Dockerfile", + BuildArgs: map[string]*string{ + "REGISTRY_HOST": ®istryHost, + }, + Repo: "localhost", }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), } - redisC, err := prepareRedisImage(ctx, req, t) + redisC, err := prepareRedisImage(ctx, req) + CleanupContainer(t, redisC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, redisC) } func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *testing.T) { - // using different credentials than in the Docker Registry - base64 := "Zm9vOmJhcg==" // foo:bar - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:5001": { "username": "foo", "password": "bar", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + registryHost := prepareLocalRegistryWithAuth(t) - prepareLocalRegistryWithAuth(t) + // using different credentials than in the Docker Registry + setAuthConfig(t, registryHost, "foo", "bar") ctx := context.Background() @@ -269,32 +231,29 @@ func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *test FromDockerfile: FromDockerfile{ Context: "./testdata", Dockerfile: "auth.Dockerfile", + BuildArgs: map[string]*string{ + "REGISTRY_HOST": ®istryHost, + }, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), } - redisC, err := prepareRedisImage(ctx, req, t) + redisC, err := prepareRedisImage(ctx, req) + CleanupContainer(t, redisC) require.Error(t, err) - terminateContainerOnEnd(t, ctx, redisC) } func TestCreateContainerFromPrivateRegistry(t *testing.T) { - // using the same credentials as in the Docker Registry - base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:5001": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" } - }, - "credsStore": "desktop" - }`) + registryHost := prepareLocalRegistryWithAuth(t) - prepareLocalRegistryWithAuth(t) + // using the same credentials as in the Docker Registry + setAuthConfig(t, registryHost, "testuser", "testpassword") ctx := context.Background() req := ContainerRequest{ - Image: "localhost:5001/redis:5.0-alpine", + Image: registryHost + "/redis:5.0-alpine", AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), @@ -304,18 +263,19 @@ func TestCreateContainerFromPrivateRegistry(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, redisContainer) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, redisContainer) } -func prepareLocalRegistryWithAuth(t *testing.T) { +func prepareLocalRegistryWithAuth(t *testing.T) string { + t.Helper() ctx := context.Background() wd, err := os.Getwd() require.NoError(t, err) // copyDirectoryToContainer { req := ContainerRequest{ Image: "registry:2", - ExposedPorts: []string{"5001:5000/tcp"}, + ExposedPorts: []string{"5000/tcp"}, Env: map[string]string{ "REGISTRY_AUTH": "htpasswd", "REGISTRY_AUTH_HTPASSWD_REALM": "Registry", @@ -324,15 +284,15 @@ func prepareLocalRegistryWithAuth(t *testing.T) { }, Files: []ContainerFile{ { - HostFilePath: fmt.Sprintf("%s/testdata/auth", wd), + HostFilePath: wd + "/testdata/auth", ContainerFilePath: "/auth", }, { - HostFilePath: fmt.Sprintf("%s/testdata/data", wd), + HostFilePath: wd + "/testdata/data", ContainerFilePath: "/data", }, }, - WaitingFor: wait.ForExposedPort(), + WaitingFor: wait.ForHTTP("/").WithPort("5000/tcp"), } // } @@ -343,27 +303,216 @@ func prepareLocalRegistryWithAuth(t *testing.T) { } registryC, err := GenericContainer(ctx, genContainerReq) + CleanupContainer(t, registryC) require.NoError(t, err) + mappedPort, err := registryC.MappedPort(ctx, "5000/tcp") + require.NoError(t, err) + + ip := localAddress(t) + mp := mappedPort.Port() + addr := ip + ":" + mp + t.Cleanup(func() { - removeImageFromLocalCache(t, "localhost:5001/redis:5.0-alpine") - }) - t.Cleanup(func() { - require.NoError(t, registryC.Terminate(context.Background())) + removeImageFromLocalCache(t, addr+"/redis:5.0-alpine") }) - _, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + return addr } -func prepareRedisImage(ctx context.Context, req ContainerRequest, t *testing.T) (Container, error) { +func prepareRedisImage(ctx context.Context, req ContainerRequest) (Container, error) { genContainerReq := GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, } - redisC, err := GenericContainer(ctx, genContainerReq) + return GenericContainer(ctx, genContainerReq) +} + +// setAuthConfig sets the DOCKER_AUTH_CONFIG environment variable with +// authentication for with the given host, username and password. +// It returns the base64 encoded credentials. +func setAuthConfig(t *testing.T, host, username, password string) string { + t.Helper() + + var creds string + if username != "" || password != "" { + creds = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + } + + auth := fmt.Sprintf(`{ + "auths": { + %q: { + "username": %q, + "password": %q, + "auth": %q + } + }, + "credsStore": "desktop" +}`, + host, + username, + password, + creds, + ) + t.Setenv("DOCKER_AUTH_CONFIG", auth) + + return creds +} + +// localAddress returns the local address of the machine +// which can be used to connect to the local registry. +// This avoids the issues with localhost on WSL. +func localAddress(t *testing.T) string { + t.Helper() + if os.Getenv("WSL_DISTRO_NAME") == "" { + return "localhost" + } + + conn, err := net.Dial("udp", "golang.org:80") + require.NoError(t, err) + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP.String() +} + +//go:embed testdata/.docker/config.json +var dockerConfig string + +// reset resets the credentials cache. +func (c *credentialsCache) reset() { + c.mtx.Lock() + defer c.mtx.Unlock() + c.entries = make(map[string]credentials) +} + +func Test_getDockerAuthConfigs(t *testing.T) { + t.Run("HOME/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata") + + requireValidAuthConfig(t) + }) + + t.Run("HOME/not-found", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + + authConfigs, err := getDockerAuthConfigs() + require.NoError(t, err) + require.NotNil(t, authConfigs) + require.Empty(t, authConfigs) + }) + + t.Run("HOME/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "invalid-config") + + authConfigs, err := getDockerAuthConfigs() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, authConfigs) + }) + + t.Run("DOCKER_AUTH_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig) + + requireValidAuthConfig(t) + }) + + t.Run("DOCKER_AUTH_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + t.Setenv("DOCKER_AUTH_CONFIG", `{"auths": []}`) + + authConfigs, err := getDockerAuthConfigs() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, authConfigs) + }) + + t.Run("DOCKER_AUTH_CONFIG/identity-token", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + + // Reset the credentials cache to ensure our mocked method is called. + creds.reset() + + // Mock getRegistryCredentials to return identity-token for index.docker.io. + old := getRegistryCredentials + t.Cleanup(func() { + getRegistryCredentials = old + creds.reset() // Ensure our mocked results aren't cached. + }) + getRegistryCredentials = func(hostname string) (string, string, error) { + switch hostname { + case core.IndexDockerIO: + return "", "identity-token", nil + default: + return "username", "password", nil + } + } + t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig) + + authConfigs, err := getDockerAuthConfigs() + require.NoError(t, err) + require.Equal(t, map[string]registry.AuthConfig{ + core.IndexDockerIO: { + IdentityToken: "identity-token", + }, + privateRegistry: { + Username: "username", + Password: "password", + }, + exampleRegistry: { + Username: "username", + Password: "password", + }, + }, authConfigs) + }) + + t.Run("DOCKER_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", ".docker")) + + requireValidAuthConfig(t) + }) + + t.Run("DOCKER_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", "invalid-config", ".docker")) + + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) + }) +} + +// requireValidAuthConfig checks that the given authConfigs map contains the expected keys. +func requireValidAuthConfig(t *testing.T) { + t.Helper() + + authConfigs, err := getDockerAuthConfigs() + require.NoError(t, err) + + // We can only check the keys as the values are not deterministic as they depend + // on users environment. + expected := map[string]registry.AuthConfig{ + core.IndexDockerIO: {}, + exampleRegistry: {}, + privateRegistry: {}, + } + for k := range authConfigs { + authConfigs[k] = registry.AuthConfig{} + } + require.Equal(t, expected, authConfigs) +} + +// testDockerConfigHome sets the user's home directory to the given path +// and unsets the DOCKER_CONFIG and DOCKER_AUTH_CONFIG environment variables. +func testDockerConfigHome(t *testing.T, dirs ...string) { + t.Helper() - return redisC, err + dir := filepath.Join(dirs...) + t.Setenv("DOCKER_AUTH_CONFIG", "") + t.Setenv("DOCKER_CONFIG", "") + t.Setenv("HOME", dir) + t.Setenv("USERPROFILE", dir) // Windows } diff --git a/docker_client.go b/docker_client.go index eb719cf25e..04df712916 100644 --- a/docker_client.go +++ b/docker_client.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types/system" "github.com/docker/docker/client" + "github.com/testcontainers/testcontainers-go/internal" "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -31,7 +32,7 @@ var ( var _ client.SystemAPIClient = &DockerClient{} // Events returns a channel to listen to events that happen to the docker daemon. -func (c *DockerClient) Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) { +func (c *DockerClient) Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error) { return c.Client.Events(ctx, options) } @@ -56,18 +57,30 @@ func (c *DockerClient) Info(ctx context.Context) (system.Info, error) { Server Version: %v API Version: %v Operating System: %v - Total Memory: %v MB + Total Memory: %v MB%s + Testcontainers for Go Version: v%s Resolved Docker Host: %s Resolved Docker Socket Path: %s Test SessionID: %s Test ProcessID: %s ` + infoLabels := "" + if len(dockerInfo.Labels) > 0 { + infoLabels = ` + Labels:` + for _, lb := range dockerInfo.Labels { + infoLabels += "\n " + lb + } + } Logger.Printf(infoMessage, packagePath, - dockerInfo.ServerVersion, c.Client.ClientVersion(), + dockerInfo.ServerVersion, + c.Client.ClientVersion(), dockerInfo.OperatingSystem, dockerInfo.MemTotal/1024/1024, - core.ExtractDockerHost(ctx), - core.ExtractDockerSocket(ctx), + infoLabels, + internal.Version, + core.MustExtractDockerHost(ctx), + core.MustExtractDockerSocket(ctx), core.SessionID(), core.ProcessID(), ) diff --git a/docker_exec_test.go b/docker_exec_test.go index 11f187c226..65f9e71e07 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -51,19 +51,18 @@ func TestExecWithOptions(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) // always append the multiplexed option for having the output // in a readable format tt.opts = append(tt.opts, tcexec.Multiplexed()) - code, reader, err := container.Exec(ctx, tt.cmds, tt.opts...) + code, reader, err := ctr.Exec(ctx, tt.cmds, tt.opts...) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) @@ -84,15 +83,14 @@ func TestExecWithMultiplexedResponse(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}, tcexec.Multiplexed()) + code, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}, tcexec.Multiplexed()) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) @@ -112,15 +110,14 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}) + code, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) diff --git a/docker_files_test.go b/docker_files_test.go index 6fcfc92a0b..6b32168081 100644 --- a/docker_files_test.go +++ b/docker_files_test.go @@ -13,24 +13,22 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +const testBashImage string = "bash:5.2.26" + func TestCopyFileToContainer(t *testing.T) { ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) defer cnl() // copyFileOnCreate { absPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) r, err := os.Open(absPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: testBashImage, Files: []testcontainers.ContainerFile{ { Reader: r, @@ -45,9 +43,8 @@ func TestCopyFileToContainer(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) } func TestCopyFileToRunningContainer(t *testing.T) { @@ -57,17 +54,13 @@ func TestCopyFileToRunningContainer(t *testing.T) { // Not using the assertations here to avoid leaking the library into the example // copyFileAfterCreate { waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) helloPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash:5.2.26", + Image: testBashImage, Files: []testcontainers.ContainerFile{ { HostFilePath: waitForPath, @@ -79,20 +72,17 @@ func TestCopyFileToRunningContainer(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - err = container.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700) + err = ctr.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700) // } require.NoError(t, err) // Give some time to the wait script to catch the hello script being created - err = wait.ForLog("done").WithStartupTimeout(2*time.Second).WaitUntilReady(ctx, container) + err = wait.ForLog("done").WithStartupTimeout(2*time.Second).WaitUntilReady(ctx, ctr) require.NoError(t, err) - - require.NoError(t, container.Terminate(ctx)) } func TestCopyDirectoryToContainer(t *testing.T) { @@ -102,13 +92,11 @@ func TestCopyDirectoryToContainer(t *testing.T) { // Not using the assertations here to avoid leaking the library into the example // copyDirectoryToContainer { dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: testBashImage, Files: []testcontainers.ContainerFile{ { HostFilePath: dataDirectory, @@ -125,9 +113,8 @@ func TestCopyDirectoryToContainer(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) } func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { @@ -136,17 +123,13 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { // copyDirectoryToRunningContainerAsFile { dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) waitForPath, err := filepath.Abs(filepath.Join(dataDirectory, "waitForHello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: testBashImage, Files: []testcontainers.ContainerFile{ { HostFilePath: waitForPath, @@ -158,25 +141,17 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } + _, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + require.NoError(t, err) // because the container path is a directory, it will use the copy dir method as fallback - err = container.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700) - if err != nil { - t.Fatal(err) - } - // } - + err = ctr.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) + // } } func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { @@ -186,17 +161,13 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { // Not using the assertations here to avoid leaking the library into the example // copyDirectoryToRunningContainerAsDir { waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: testBashImage, Files: []testcontainers.ContainerFile{ { HostFilePath: waitForPath, @@ -208,22 +179,14 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } - - err = container.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700) - if err != nil { - t.Fatal(err) - } - // } + _, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + require.NoError(t, err) + err = ctr.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) + // } } diff --git a/docker_mounts.go b/docker_mounts.go index aed3010361..d8af3fae3e 100644 --- a/docker_mounts.go +++ b/docker_mounts.go @@ -126,9 +126,7 @@ func mapToDockerMounts(containerMounts ContainerMounts) []mount.Mount { Labels: make(map[string]string), } } - for k, v := range GenericLabels() { - containerMount.VolumeOptions.Labels[k] = v - } + AddGenericLabels(containerMount.VolumeOptions.Labels) } mounts = append(mounts, containerMount) diff --git a/docker_test.go b/docker_test.go index 77b7021b62..3fa686632f 100644 --- a/docker_test.go +++ b/docker_test.go @@ -18,10 +18,10 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/client" "github.com/docker/docker/errdefs" - "github.com/docker/go-units" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,10 +29,10 @@ import ( ) const ( - mysqlImage = "docker.io/mysql:8.0.36" - nginxDelayedImage = "docker.io/menedev/delayed-nginx:1.15.2" - nginxImage = "docker.io/nginx" - nginxAlpineImage = "docker.io/nginx:alpine" + mysqlImage = "mysql:8.0.36" + nginxDelayedImage = "menedev/delayed-nginx:1.15.2" + nginxImage = "nginx" + nginxAlpineImage = "nginx:alpine" nginxDefaultPort = "80/tcp" nginxHighPort = "8080/tcp" daemonMaxVersion = "1.41" @@ -55,9 +55,7 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { SkipIfDockerDesktop(t, ctx) absPath, err := filepath.Abs(filepath.Join("testdata", "nginx-highport.conf")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) gcr := GenericContainerRequest{ ProviderType: providerType, @@ -73,7 +71,7 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { nginxHighPort, }, Privileged: true, - WaitingFor: wait.ForListeningPort(nginxHighPort), + WaitingFor: wait.ForHTTP("/").WithPort(nginxHighPort), HostConfigModifier: func(hc *container.HostConfig) { hc.NetworkMode = "host" }, @@ -82,23 +80,14 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { } nginxC, err := GenericContainer(ctx, gcr) + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) - // host, err := nginxC.Host(ctx) - // if err != nil { - // t.Errorf("Expected host %s. Got '%d'.", host, err) - // } - // endpoint, err := nginxC.PortEndpoint(ctx, nginxHighPort, "http") - if err != nil { - t.Errorf("Expected server endpoint. Got '%v'.", err) - } + require.NoErrorf(t, err, "Expected server endpoint") _, err = http.Get(endpoint) - if err != nil { - t.Errorf("Expected OK response. Got '%d'.", err) - } + require.NoErrorf(t, err, "Expected OK response") } func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testing.T) { @@ -113,26 +102,17 @@ func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testi } nginxC, err := GenericContainer(ctx, gcr) - if err != nil { - t.Fatal(err) - } - - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) + require.NoError(t, err) endpoint, err := nginxC.Endpoint(ctx, "http") - if err != nil { - t.Errorf("Expected server endpoint. Got '%v'.", err) - } + require.NoErrorf(t, err, "Expected server endpoint") resp, err := http.Get(endpoint) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) } func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { @@ -158,11 +138,11 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { } nginx, err := GenericContainer(ctx, gcr) + CleanupContainer(t, nginx) if err != nil { // Error when NetworkMode = host and Network = []string{"bridge"} t.Logf("Can't use Network and NetworkMode together, %s\n", err) } - terminateContainerOnEnd(t, ctx, nginx) } func TestContainerWithHostNetwork(t *testing.T) { @@ -174,15 +154,13 @@ func TestContainerWithHostNetwork(t *testing.T) { SkipIfDockerDesktop(t, ctx) absPath, err := filepath.Abs(filepath.Join("testdata", "nginx-highport.conf")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) gcr := GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ Image: nginxAlpineImage, - WaitingFor: wait.ForListeningPort(nginxHighPort), + WaitingFor: wait.ForHTTP("/").WithPort(nginxHighPort), Files: []ContainerFile{ { HostFilePath: absPath, @@ -197,30 +175,21 @@ func TestContainerWithHostNetwork(t *testing.T) { } nginxC, err := GenericContainer(ctx, gcr) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) portEndpoint, err := nginxC.PortEndpoint(ctx, nginxHighPort, "http") - if err != nil { - t.Errorf("Expected port endpoint %s. Got '%d'.", portEndpoint, err) - } + require.NoErrorf(t, err, "Expected port endpoint %s", portEndpoint) t.Log(portEndpoint) _, err = http.Get(portEndpoint) - if err != nil { - t.Errorf("Expected OK response. Got '%v'.", err) - } + require.NoErrorf(t, err, "Expected OK response") host, err := nginxC.Host(ctx) - if err != nil { - t.Errorf("Expected host %s. Got '%d'.", host, err) - } + require.NoErrorf(t, err, "Expected host %s", host) _, err = http.Get("http://" + host + ":8080") - if err != nil { - t.Errorf("Expected OK response. Got '%v'.", err) - } + assert.NoErrorf(t, err, "Expected OK response") } func TestContainerReturnItsContainerID(t *testing.T) { @@ -234,13 +203,19 @@ func TestContainerReturnItsContainerID(t *testing.T) { }, }, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) - if nginxA.GetContainerID() == "" { - t.Errorf("expected a containerID but we got an empty string.") - } + assert.NotEmptyf(t, nginxA.GetContainerID(), "expected a containerID but we got an empty string.") +} + +// testLogConsumer is a simple implementation of LogConsumer that logs to the test output. +type testLogConsumer struct { + t *testing.T +} + +func (l *testLogConsumer) Accept(log Log) { + l.t.Log(log.LogType + ": " + strings.TrimSpace(string(log.Content))) } func TestContainerTerminationResetsState(t *testing.T) { @@ -253,24 +228,22 @@ func TestContainerTerminationResetsState(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, + LogConsumerCfg: &LogConsumerConfig{ + Consumers: []LogConsumer{&testLogConsumer{t: t}}, + }, }, Started: true, }) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginxA) + require.NoError(t, err) err = nginxA.Terminate(ctx) - if err != nil { - t.Fatal(err) - } - if nginxA.SessionID() != "" { - t.Fatal("Internal state must be reset.") - } - ports, err := nginxA.Ports(ctx) - if err == nil || ports != nil { - t.Fatal("expected error from container inspect.") - } + require.NoError(t, err) + require.Empty(t, nginxA.SessionID()) + + inspect, err := nginxA.Inspect(ctx) + require.Error(t, err) + require.Nil(t, inspect) } func TestContainerStateAfterTermination(t *testing.T) { @@ -282,6 +255,9 @@ func TestContainerStateAfterTermination(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, + LogConsumerCfg: &LogConsumerConfig{ + Consumers: []LogConsumer{&testLogConsumer{t: t}}, + }, }, Started: true, }) @@ -290,57 +266,47 @@ func TestContainerStateAfterTermination(t *testing.T) { t.Run("Nil State after termination", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginx) + require.NoError(t, err) // terminate the container before the raw state is set err = nginx.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err := nginx.State(ctx) require.Error(t, err, "expected error from container inspect.") - assert.Nil(t, state, "expected nil container inspect.") + require.Nil(t, state, "expected nil container inspect.") }) - t.Run("Non-nil State after termination if raw as already set", func(t *testing.T) { + t.Run("Nil State after termination if raw as already set", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginx) + require.NoError(t, err) state, err := nginx.State(ctx) require.NoError(t, err, "unexpected error from container inspect before container termination.") - - assert.NotNil(t, state, "unexpected nil container inspect before container termination.") + require.NotNil(t, state, "unexpected nil container inspect before container termination.") // terminate the container before the raw state is set err = nginx.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err = nginx.State(ctx) require.Error(t, err, "expected error from container inspect after container termination.") - - assert.NotNil(t, state, "unexpected nil container inspect after container termination.") + require.Nil(t, state, "unexpected nil container inspect after container termination.") }) } func TestContainerTerminationRemovesDockerImage(t *testing.T) { t.Run("if not built from Dockerfile", func(t *testing.T) { ctx := context.Background() - client, err := NewDockerClientWithOpts(ctx) - if err != nil { - t.Fatal(err) - } - defer client.Close() + dockerClient, err := NewDockerClientWithOpts(ctx) + require.NoError(t, err) + defer dockerClient.Close() - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ Image: nginxAlpineImage, @@ -350,26 +316,21 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } - err = container.Terminate(ctx) - if err != nil { - t.Fatal(err) - } - _, _, err = client.ImageInspectWithRaw(ctx, nginxAlpineImage) - if err != nil { - t.Fatal("nginx image should not have been removed") - } + CleanupContainer(t, ctr) + require.NoError(t, err) + + err = ctr.Terminate(ctx) + require.NoError(t, err) + + _, _, err = dockerClient.ImageInspectWithRaw(ctx, nginxAlpineImage) + require.NoErrorf(t, err, "nginx image should not have been removed") }) t.Run("if built from Dockerfile", func(t *testing.T) { ctx := context.Background() - client, err := NewDockerClientWithOpts(ctx) - if err != nil { - t.Fatal(err) - } - defer client.Close() + dockerClient, err := NewDockerClientWithOpts(ctx) + require.NoError(t, err) + defer dockerClient.Close() req := ContainerRequest{ FromDockerfile: FromDockerfile{ @@ -378,30 +339,23 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatal(err) - } - containerID := container.GetContainerID() - resp, err := client.ContainerInspect(ctx, containerID) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, ctr) + require.NoError(t, err) + containerID := ctr.GetContainerID() + resp, err := dockerClient.ContainerInspect(ctx, containerID) + require.NoError(t, err) imageID := resp.Config.Image - err = container.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + err = ctr.Terminate(ctx) + require.NoError(t, err) - _, _, err = client.ImageInspectWithRaw(ctx, imageID) - if err == nil { - t.Fatal("custom built image should have been removed", err) - } + _, _, err = dockerClient.ImageInspectWithRaw(ctx, imageID) + require.Errorf(t, err, "custom built image should have been removed") }) } @@ -414,12 +368,12 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, + WaitingFor: wait.ForHTTP("/").WithPort(nginxDefaultPort), }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) nginxB, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, @@ -428,41 +382,30 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, - WaitingFor: wait.ForListeningPort(nginxDefaultPort), + WaitingFor: wait.ForHTTP("/").WithPort(nginxDefaultPort), }, Started: true, }) - + CleanupContainer(t, nginxB) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxB) endpointA, err := nginxA.PortEndpoint(ctx, nginxDefaultPort, "http") require.NoError(t, err) resp, err := http.Get(endpointA) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) endpointB, err := nginxB.PortEndpoint(ctx, nginxDefaultPort, "http") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err = http.Get(endpointB) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) } func TestContainerCreation(t *testing.T) { @@ -475,45 +418,29 @@ func TestContainerCreation(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, - WaitingFor: wait.ForListeningPort(nginxDefaultPort), + WaitingFor: wait.ForHTTP("/").WithPort(nginxDefaultPort), }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) endpoint, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") require.NoError(t, err) resp, err := http.Get(endpoint) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) networkIP, err := nginxC.ContainerIP(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkIP) == 0 { - t.Errorf("Expected an IP address, got %v", networkIP) - } + require.NoError(t, err) + require.NotEmptyf(t, networkIP, "Expected an IP address, got %v", networkIP) networkAliases, err := nginxC.NetworkAliases(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkAliases) != 1 { - fmt.Printf("%v", networkAliases) - t.Errorf("Expected number of connected networks %d. Got %d.", 0, len(networkAliases)) - } - - if len(networkAliases["bridge"]) != 0 { - t.Errorf("Expected number of aliases for 'bridge' network %d. Got %d.", 0, len(networkAliases["bridge"])) - } + require.NoError(t, err) + require.Lenf(t, networkAliases, 1, "Expected number of connected networks %d. Got %d.", 0, len(networkAliases)) + require.Contains(t, networkAliases, "bridge") + assert.Emptyf(t, networkAliases["bridge"], "Expected number of aliases for 'bridge' network %d. Got %d.", 0, len(networkAliases["bridge"])) } func TestContainerCreationWithName(t *testing.T) { @@ -529,54 +456,39 @@ func TestContainerCreationWithName(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, - WaitingFor: wait.ForListeningPort(nginxDefaultPort), + WaitingFor: wait.ForHTTP("/").WithPort(nginxDefaultPort), Name: creationName, Networks: []string{"bridge"}, }, Started: true, }) + CleanupContainer(t, nginxC) + require.NoError(t, err) + inspect, err := nginxC.Inspect(ctx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) - name, err := nginxC.Name(ctx) - if err != nil { - t.Fatal(err) - } - if name != expectedName { - t.Errorf("Expected container name '%s'. Got '%s'.", expectedName, name) - } + name := inspect.Name + assert.Equalf(t, expectedName, name, "Expected container name '%s'. Got '%s'.", expectedName, name) networks, err := nginxC.Networks(ctx) - if err != nil { - t.Fatal(err) - } - if len(networks) != 1 { - t.Errorf("Expected networks 1. Got '%d'.", len(networks)) - } + require.NoError(t, err) + require.Lenf(t, networks, 1, "Expected networks 1. Got '%d'.", len(networks)) network := networks[0] switch providerType { case ProviderDocker: - if network != Bridge { - t.Errorf("Expected network name '%s'. Got '%s'.", Bridge, network) - } + assert.Equalf(t, Bridge, network, "Expected network name '%s'. Got '%s'.", Bridge, network) case ProviderPodman: - if network != Podman { - t.Errorf("Expected network name '%s'. Got '%s'.", Podman, network) - } + assert.Equalf(t, Podman, network, "Expected network name '%s'. Got '%s'.", Podman, network) } endpoint, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") require.NoError(t, err) resp, err := http.Get(endpoint) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) } func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { @@ -590,27 +502,20 @@ func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, - WaitingFor: wait.ForListeningPort(nginxDefaultPort), // default startupTimeout is 60s + WaitingFor: wait.ForHTTP("/").WithPort(nginxDefaultPort), // default startupTimeout is 60s }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err := http.Get(origin) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) } func TestContainerCreationTimesOut(t *testing.T) { @@ -627,12 +532,9 @@ func TestContainerCreationTimesOut(t *testing.T) { }, Started: true, }) + CleanupContainer(t, nginxC) - terminateContainerOnEnd(t, ctx, nginxC) - - if err == nil { - t.Error("Expected timeout") - } + assert.Errorf(t, err, "Expected timeout") } func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { @@ -649,23 +551,16 @@ func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err := http.Get(origin) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equalf(t, http.StatusOK, resp.StatusCode, "Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) } func TestContainerCreationTimesOutWithHttp(t *testing.T) { @@ -678,15 +573,12 @@ func TestContainerCreationTimesOutWithHttp(t *testing.T) { ExposedPorts: []string{ nginxDefaultPort, }, - WaitingFor: wait.ForHTTP("/").WithStartupTimeout(1 * time.Second), + WaitingFor: wait.ForHTTP("/").WithStartupTimeout(time.Millisecond * 500), }, Started: true, }) - terminateContainerOnEnd(t, ctx, nginxC) - - if err == nil { - t.Error("Expected timeout") - } + CleanupContainer(t, nginxC) + require.Error(t, err, "expected timeout") } func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { @@ -705,11 +597,8 @@ func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { ContainerRequest: req, Started: true, }) - if err == nil { - t.Error("Expected timeout") - } - - terminateContainerOnEnd(t, ctx, c) + CleanupContainer(t, c) + assert.Errorf(t, err, "Expected timeout") } func TestContainerCreationWaitsForLog(t *testing.T) { @@ -728,20 +617,15 @@ func TestContainerCreationWaitsForLog(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, mysqlC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, mysqlC) } func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { - t.Log("getting ctx") ctx := context.Background() - t.Log("got ctx, creating container request") - // fromDockerfileWithBuildArgs { ba := "build args value" - req := ContainerRequest{ FromDockerfile: FromDockerfile{ Context: filepath.Join(".", "testdata"), @@ -762,38 +646,33 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { } c, err := GenericContainer(ctx, genContainerReq) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) ep, err := c.Endpoint(ctx, "http") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err := http.Get(ep + "/env") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, 200, resp.StatusCode) - assert.Equal(t, ba, string(body)) + require.NoError(t, err) + require.Equal(t, http.StatusAccepted, resp.StatusCode) + require.Equal(t, ba, string(body)) } func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { - rescueStdout := os.Stderr - r, w, _ := os.Pipe() + r, w, err := os.Pipe() + require.NoError(t, err) + + oldStderr := os.Stderr os.Stderr = w + t.Cleanup(func() { + os.Stderr = oldStderr + }) - t.Log("getting ctx") ctx := context.Background() - t.Log("got ctx, creating container request") // fromDockerfile { req := ContainerRequest{ @@ -812,18 +691,18 @@ func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { } c, err := GenericContainer(ctx, genContainerReq) + CleanupContainer(t, c) + require.NoError(t, err) + err = w.Close() require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) - _ = w.Close() - out, _ := io.ReadAll(r) - os.Stdout = rescueStdout - temp := strings.Split(string(out), "\n") + out, err := io.ReadAll(r) + require.NoError(t, err) - if !regexp.MustCompile(`(?i)^Step\s*1/1\s*:\s*FROM docker.io/alpine$`).MatchString(temp[0]) { - t.Errorf("Expected stdout firstline to be %s. Got '%s'.", "Step 1/1 : FROM docker.io/alpine", temp[0]) - } + temp := strings.Split(string(out), "\n") + require.NotEmpty(t, temp) + assert.Regexpf(t, `^Step\s*1/\d+\s*:\s*FROM alpine$`, temp[0], "Expected stdout first line to be %s. Got '%s'.", "Step 1/* : FROM alpine", temp[0]) } func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { @@ -845,11 +724,8 @@ func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { ContainerRequest: req, Started: true, }) - if err == nil { - t.Fatal("Expected timeout") - } - - terminateContainerOnEnd(t, ctx, c) + CleanupContainer(t, c) + require.Errorf(t, err, "Expected timeout") } func TestContainerCreationWaitingForHostPort(t *testing.T) { @@ -866,9 +742,8 @@ func TestContainerCreationWaitingForHostPort(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) } func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing.T) { @@ -883,9 +758,8 @@ func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing. ContainerRequest: req, Started: true, }) - + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) } func TestCMD(t *testing.T) { @@ -898,7 +772,7 @@ func TestCMD(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine", + Image: "alpine", WaitingFor: wait.ForAll( wait.ForLog("command override!"), ), @@ -910,9 +784,8 @@ func TestCMD(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func TestEntrypoint(t *testing.T) { @@ -925,7 +798,7 @@ func TestEntrypoint(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine", + Image: "alpine", WaitingFor: wait.ForAll( wait.ForLog("entrypoint override!"), ), @@ -937,9 +810,8 @@ func TestEntrypoint(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func TestWorkingDir(t *testing.T) { @@ -952,7 +824,7 @@ func TestWorkingDir(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine", + Image: "alpine", WaitingFor: wait.ForAll( wait.ForLog("/var/tmp/test"), ), @@ -965,31 +837,35 @@ func TestWorkingDir(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func ExampleDockerProvider_CreateContainer() { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create container: %s", err) + return + } state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -1001,55 +877,74 @@ func ExampleDockerProvider_CreateContainer() { func ExampleContainer_Host() { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create container: %s", err) + return + } // containerHost { - ip, _ := nginxC.Host(ctx) + ip, err := nginxC.Host(ctx) + if err != nil { + log.Printf("failed to create container: %s", err) + return + } // } - println(ip) + fmt.Println(ip) state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) // Output: + // localhost // true } func ExampleContainer_Start() { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() - _ = nginxC.Start(ctx) + if err != nil { + log.Printf("failed to create container: %s", err) + return + } + + if err = nginxC.Start(ctx); err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -1061,23 +956,28 @@ func ExampleContainer_Start() { func ExampleContainer_Stop() { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create and start container: %s", err) + return + } + fmt.Println("Container has been started") timeout := 10 * time.Second - err := nginxC.Stop(ctx, &timeout) - if err != nil { - log.Fatalf("failed to stop container: %s", err) // nolint:gocritic + if err = nginxC.Stop(ctx, &timeout); err != nil { + log.Printf("failed to terminate container: %s", err) + return } fmt.Println("Container has been stopped") @@ -1090,19 +990,24 @@ func ExampleContainer_Stop() { func ExampleContainer_MappedPort() { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create and start container: %s", err) + return + } + // buildingAddresses { ip, _ := nginxC.Host(ctx) port, _ := nginxC.MappedPort(ctx, "80") @@ -1111,7 +1016,8 @@ func ExampleContainer_MappedPort() { state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -1122,9 +1028,7 @@ func ExampleContainer_MappedPort() { func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { absPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) defer cnl() @@ -1135,7 +1039,7 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { bashC, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ - Image: "docker.io/bash", + Image: "bash:5.2.26", Files: []ContainerFile{ { HostFilePath: absPath, @@ -1148,81 +1052,64 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, bashC, RemoveVolumes(volumeName)) require.NoError(t, err) - require.NoError(t, bashC.Terminate(ctx)) } func TestContainerWithTmpFs(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/busybox", + Image: "busybox", Cmd: []string{"sleep", "10"}, Tmpfs: map[string]string{"/testtmpfs": "rw"}, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) path := "/testtmpfs/test.file" // exec_reader_example { - c, reader, err := container.Exec(ctx, []string{"ls", path}) - if err != nil { - t.Fatal(err) - } - if c != 1 { - t.Fatalf("File %s should not have existed, expected return code 1, got %v", path, c) - } + c, reader, err := ctr.Exec(ctx, []string{"ls", path}) + require.NoError(t, err) + require.Equalf(t, 1, c, "File %s should not have existed, expected return code 1, got %v", path, c) buf := new(strings.Builder) _, err = io.Copy(buf, reader) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // See the logs from the command execution. t.Log(buf.String()) // } // exec_example { - c, _, err = container.Exec(ctx, []string{"touch", path}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should have been created successfully, expected return code 0, got %v", path, c) - } + c, _, err = ctr.Exec(ctx, []string{"touch", path}) + require.NoError(t, err) + require.Zerof(t, c, "File %s should have been created successfully, expected return code 0, got %v", path, c) // } - c, _, err = container.Exec(ctx, []string{"ls", path}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should exist, expected return code 0, got %v", path, c) - } + c, _, err = ctr.Exec(ctx, []string{"ls", path}) + require.NoError(t, err) + require.Zerof(t, c, "File %s should exist, expected return code 0, got %v", path, c) } func TestContainerNonExistentImage(t *testing.T) { t.Run("if the image not found don't propagate the error", func(t *testing.T) { - _, err := GenericContainer(context.Background(), GenericContainerRequest{ + ctr, err := GenericContainer(context.Background(), GenericContainerRequest{ ContainerRequest: ContainerRequest{ Image: "postgres:nonexistent-version", }, Started: true, }) + CleanupContainer(t, ctr) var nf errdefs.ErrNotFound - if !errors.As(err, &nf) { - t.Fatalf("the error should have been an errdefs.ErrNotFound: %v", err) - } + require.ErrorAsf(t, err, &nf, "the error should have been an errdefs.ErrNotFound: %v", err) }) t.Run("the context cancellation is propagated to container creation", func(t *testing.T) { @@ -1231,16 +1118,13 @@ func TestContainerNonExistentImage(t *testing.T) { c, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ - Image: "docker.io/postgres:12", + Image: "postgres:12", WaitingFor: wait.ForLog("log"), }, Started: true, }) - if !errors.Is(err, ctx.Err()) { - t.Fatalf("err should be a ctx cancelled error %v", err) - } - - terminateContainerOnEnd(t, context.Background(), c) // use non-cancelled context + CleanupContainer(t, c) + require.ErrorIsf(t, err, ctx.Err(), "err should be a ctx cancelled error %v", err) }) } @@ -1256,14 +1140,12 @@ func TestContainerCustomPlatformImage(t *testing.T) { c, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ - Image: "docker.io/redis:latest", + Image: "redis:latest", ImagePlatform: nonExistentPlatform, }, Started: false, }) - - terminateContainerOnEnd(t, ctx, c) - + CleanupContainer(t, c) require.Error(t, err) }) @@ -1274,14 +1156,13 @@ func TestContainerCustomPlatformImage(t *testing.T) { c, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ - Image: "docker.io/mysql:8.0.36", + Image: "mysql:8.0.36", ImagePlatform: "linux/amd64", }, Started: false, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) dockerCli, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1306,31 +1187,44 @@ func TestContainerWithCustomHostname(t *testing.T) { Image: nginxImage, Hostname: hostname, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) + CleanupContainer(t, ctr) + require.NoError(t, err) + + actualHostname := readHostname(t, ctr.GetContainerID()) + require.Equalf(t, actualHostname, hostname, "expected hostname %s, got %s", hostname, actualHostname) +} +func TestContainerInspect_RawInspectIsCleanedOnStop(t *testing.T) { + ctr, err := GenericContainer(context.Background(), GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: nginxImage, + }, + Started: true, + }) + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - if actualHostname := readHostname(t, container.GetContainerID()); actualHostname != hostname { - t.Fatalf("expected hostname %s, got %s", hostname, actualHostname) - } + inspect, err := ctr.Inspect(context.Background()) + require.NoError(t, err) + + require.NotEmpty(t, inspect.ID) + + require.NoError(t, ctr.Stop(context.Background(), nil)) } func readHostname(tb testing.TB, containerId string) string { + tb.Helper() containerClient, err := NewDockerClientWithOpts(context.Background()) - if err != nil { - tb.Fatalf("Failed to create Docker client: %v", err) - } + require.NoErrorf(tb, err, "Failed to create Docker client") defer containerClient.Close() containerDetails, err := containerClient.ContainerInspect(context.Background(), containerId) - if err != nil { - tb.Fatalf("Failed to inspect container: %v", err) - } + require.NoErrorf(tb, err, "Failed to inspect container") return containerDetails.Config.Hostname } @@ -1363,18 +1257,13 @@ func TestDockerContainerCopyFileToContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "hello.sh"), tc.copiedFileName, 700) c, _, err := nginxC.Exec(ctx, []string{"bash", tc.copiedFileName}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should exist, expected return code 0, got %v", tc.copiedFileName, c) - } + require.NoError(t, err) + require.Zerof(t, c, "File %s should exist, expected return code 0, got %v", tc.copiedFileName, c) }) } } @@ -1391,19 +1280,16 @@ func TestDockerContainerCopyDirToContainer(t *testing.T) { }, Started: true, }) - - p := filepath.Join(".", "testdata", "Dokerfile") + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) + p := filepath.Join(".", "testdata", "Dokerfile") err = nginxC.CopyDirToContainer(ctx, p, "/tmp/testdata/Dockerfile", 700) require.Error(t, err) // copying a file using the directory method will raise an error p = filepath.Join(".", "testdata") err = nginxC.CopyDirToContainer(ctx, p, "/tmp/testdata", 700) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assertExtractedFiles(t, ctx, nginxC, p, "/tmp/testdata/") } @@ -1452,10 +1338,10 @@ func TestDockerCreateContainerWithFiles(t *testing.T) { }, Started: false, }) - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) if err != nil { - require.Contains(t, err.Error(), tc.errMsg) + require.ErrorContains(t, err, tc.errMsg) } else { for _, f := range tc.files { require.NoError(t, err) @@ -1537,7 +1423,7 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { }, Started: false, }) - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) require.Equal(t, (err != nil), tc.hasError) if err == nil { @@ -1577,34 +1463,23 @@ func TestDockerContainerCopyToContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) fileContent, err := os.ReadFile(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = nginxC.CopyToContainer(ctx, fileContent, tc.copiedFileName, 700) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) c, _, err := nginxC.Exec(ctx, []string{"bash", tc.copiedFileName}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should exist, expected return code 0, got %v", tc.copiedFileName, c) - } + require.NoError(t, err) + require.Zerof(t, c, "File %s should exist, expected return code 0, got %v", tc.copiedFileName, c) }) } } func TestDockerContainerCopyFileFromContainer(t *testing.T) { fileContent, err := os.ReadFile(filepath.Join(".", "testdata", "hello.sh")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) ctx := context.Background() nginxC, err := GenericContainer(ctx, GenericContainerRequest{ @@ -1616,30 +1491,21 @@ func TestDockerContainerCopyFileFromContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) copiedFileName := "hello_copy.sh" _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "hello.sh"), "/"+copiedFileName, 700) c, _, err := nginxC.Exec(ctx, []string{"bash", copiedFileName}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should exist, expected return code 0, got %v", copiedFileName, c) - } + require.NoError(t, err) + require.Zerof(t, c, "File %s should exist, expected return code 0, got %v", copiedFileName, c) reader, err := nginxC.CopyFileFromContainer(ctx, "/"+copiedFileName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer reader.Close() fileContentFromContainer, err := io.ReadAll(reader) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.Equal(t, fileContent, fileContentFromContainer) } @@ -1655,31 +1521,22 @@ func TestDockerContainerCopyEmptyFileFromContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) copiedFileName := "hello_copy.sh" _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "empty.sh"), "/"+copiedFileName, 700) c, _, err := nginxC.Exec(ctx, []string{"bash", copiedFileName}) - if err != nil { - t.Fatal(err) - } - if c != 0 { - t.Fatalf("File %s should exist, expected return code 0, got %v", copiedFileName, c) - } + require.NoError(t, err) + require.Zerof(t, c, "File %s should exist, expected return code 0, got %v", copiedFileName, c) reader, err := nginxC.CopyFileFromContainer(ctx, "/"+copiedFileName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer reader.Close() fileContentFromContainer, err := io.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - assert.Empty(t, fileContentFromContainer) + require.NoError(t, err) + require.Empty(t, fileContentFromContainer) } func TestDockerContainerResources(t *testing.T) { @@ -1692,7 +1549,7 @@ func TestDockerContainerResources(t *testing.T) { ctx := context.Background() - expected := []*units.Ulimit{ + expected := []*container.Ulimit{ { Name: "memlock", Hard: -1, @@ -1719,9 +1576,8 @@ func TestDockerContainerResources(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) c, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1756,8 +1612,8 @@ func TestContainerCapAdd(t *testing.T) { }, Started: true, }) + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) dockerClient, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1789,39 +1645,31 @@ func TestContainerRunningCheckingStatusCode(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatal(err) - } - - terminateContainerOnEnd(t, ctx, influx) + CleanupContainer(t, influx) + require.NoError(t, err) } func TestContainerWithUserID(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine:latest", + Image: "alpine:latest", User: "60125", Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - r, err := container.Logs(ctx) - if err != nil { - t.Fatal(err) - } + r, err := ctr.Logs(ctx) + require.NoError(t, err) defer r.Close() b, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) actual := regexp.MustCompile(`\D+`).ReplaceAllString(string(b), "") assert.Equal(t, req.User, actual) } @@ -1829,28 +1677,23 @@ func TestContainerWithUserID(t *testing.T) { func TestContainerWithNoUserID(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine:latest", + Image: "alpine:latest", Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - r, err := container.Logs(ctx) - if err != nil { - t.Fatal(err) - } + r, err := ctr.Logs(ctx) + require.NoError(t, err) defer r.Close() b, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) actual := regexp.MustCompile(`\D+`).ReplaceAllString(string(b), "") assert.Equal(t, "0", actual) } @@ -1859,28 +1702,17 @@ func TestGetGatewayIP(t *testing.T) { // When using docker compose with DinD mode, and using host port or http wait strategy // It's need to invoke GetGatewayIP for get the host provider, err := providerType.GetProvider(WithLogger(TestLogger(t))) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer provider.Close() - ip, err := provider.(*DockerProvider).GetGatewayIP(context.Background()) - if err != nil { - t.Fatal(err) + dockerProvider, ok := provider.(*DockerProvider) + if !ok { + t.Skip("provider is not a DockerProvider") } - if ip == "" { - t.Fatal("could not get gateway ip") - } -} -func TestProviderHasConfig(t *testing.T) { - provider, err := NewDockerProvider(WithLogger(TestLogger(t))) - if err != nil { - t.Fatal(err) - } - defer provider.Close() - - assert.NotNil(t, provider.Config(), "expecting DockerProvider to provide the configuration") + ip, err := dockerProvider.GetGatewayIP(context.Background()) + require.NoError(t, err) + require.NotEmpty(t, ip) } func TestNetworkModeWithContainerReference(t *testing.T) { @@ -1892,9 +1724,8 @@ func TestNetworkModeWithContainerReference(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) networkMode := fmt.Sprintf("container:%v", nginxA.GetContainerID()) nginxB, err := GenericContainer(ctx, GenericContainerRequest{ @@ -1907,13 +1738,13 @@ func TestNetworkModeWithContainerReference(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxB) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxB) } // creates a temporary dir in which the files will be extracted. Then it will compare the bytes of each file in the source with the bytes from the copied-from-container file func assertExtractedFiles(t *testing.T, ctx context.Context, container Container, hostFilePath string, containerFilePath string) { + t.Helper() // create all copied files into a temporary dir tmpDir := t.TempDir() @@ -1957,17 +1788,6 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container Container } } -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container) { - tb.Helper() - if ctr == nil { - return - } - tb.Cleanup(func() { - tb.Log("terminating container") - require.NoError(tb, ctr.Terminate(ctx)) - }) -} - func TestDockerProviderFindContainerByName(t *testing.T) { ctx := context.Background() provider, err := NewDockerProvider(WithLogger(TestLogger(t))) @@ -1983,10 +1803,14 @@ func TestDockerProviderFindContainerByName(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c1) require.NoError(t, err) - c1Name, err := c1.Name(ctx) + + c1Inspect, err := c1.Inspect(ctx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c1) + CleanupContainer(t, c1) + + c1Name := c1Inspect.Name c2, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, @@ -1997,8 +1821,8 @@ func TestDockerProviderFindContainerByName(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c2) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c2) c, err := provider.findContainerByName(ctx, "test") require.NoError(t, err) @@ -2033,16 +1857,18 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { }, }, }) + CleanupContainer(t, c) require.NoError(t, err, "create container should not fail") - defer func() { _ = c.Terminate(context.Background()) }() // Get the image ID. - containerName, err := c.Name(ctx) - require.NoError(t, err, "get container name should not fail") + containerInspect, err := c.Inspect(ctx) + require.NoError(t, err, "container inspect should not fail") + + containerName := containerInspect.Name containerDetails, err := cli.ContainerInspect(ctx, containerName) require.NoError(t, err, "inspect container should not fail") containerImage := containerDetails.Image t.Cleanup(func() { - _, _ = cli.ImageRemove(ctx, containerImage, types.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, containerImage, image.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -2054,7 +1880,7 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { if tt.keepBuiltImage { require.NoError(t, err, "image should still exist") } else { - require.Error(t, err, "image should not exist anymore") + require.Error(t, err, "image should not exist any more") } }) } @@ -2081,7 +1907,7 @@ func (f *errMockCli) ContainerList(_ context.Context, _ container.ListOptions) ( return []types.Container{{}}, f.err } -func (f *errMockCli) ImagePull(_ context.Context, _ string, _ types.ImagePullOptions) (io.ReadCloser, error) { +func (f *errMockCli) ImagePull(_ context.Context, _ string, _ image.PullOptions) (io.ReadCloser, error) { f.imagePullCount++ return io.NopCloser(&bytes.Buffer{}), f.err } @@ -2126,6 +1952,11 @@ func TestDockerProvider_BuildImage_Retries(t *testing.T) { errReturned: errdefs.NotImplemented(errors.New("unknown method")), shouldRetry: false, }, + { + name: "no retry on system error", + errReturned: errdefs.System(errors.New("system error")), + shouldRetry: false, + }, { name: "retry on non-permanent error", errReturned: errors.New("whoops"), @@ -2143,9 +1974,18 @@ func TestDockerProvider_BuildImage_Retries(t *testing.T) { // give a chance to retry ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - _, _ = p.BuildImage(ctx, &ContainerRequest{}) + _, err = p.BuildImage(ctx, &ContainerRequest{ + FromDockerfile: FromDockerfile{ + Context: filepath.Join(".", "testdata", "retry"), + }, + }) + if tt.errReturned != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } - assert.Greater(t, m.imageBuildCount, 0) + assert.Positive(t, m.imageBuildCount) assert.Equal(t, tt.shouldRetry, m.imageBuildCount > 1) }) } @@ -2196,7 +2036,7 @@ func TestDockerProvider_waitContainerCreation_retries(t *testing.T) { defer cancel() _, _ = p.waitContainerCreation(ctx, "someID") - assert.Greater(t, m.containerListCount, 0) + assert.Positive(t, m.containerListCount) assert.Equal(t, tt.shouldRetry, m.containerListCount > 1) }) } @@ -2255,10 +2095,33 @@ func TestDockerProvider_attemptToPullImage_retries(t *testing.T) { // give a chance to retry ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - _ = p.attemptToPullImage(ctx, "someTag", types.ImagePullOptions{}) + _ = p.attemptToPullImage(ctx, "someTag", image.PullOptions{}) - assert.Greater(t, m.imagePullCount, 0) + assert.Positive(t, m.imagePullCount) assert.Equal(t, tt.shouldRetry, m.imagePullCount > 1) }) } } + +func TestCustomPrefixTrailingSlashIsProperlyRemovedIfPresent(t *testing.T) { + hubPrefixWithTrailingSlash := "public.ecr.aws/" + dockerImage := "amazonlinux/amazonlinux:2023" + + ctx := context.Background() + req := ContainerRequest{ + Image: dockerImage, + ImageSubstitutors: []ImageSubstitutor{newPrependHubRegistry(hubPrefixWithTrailingSlash)}, + } + + c, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + CleanupContainer(t, c) + require.NoError(t, err) + + // enforce the concrete type, as GenericContainer returns an interface, + // which will be changed in future implementations of the library + dockerContainer := c.(*DockerContainer) + require.Equal(t, fmt.Sprintf("%s%s", hubPrefixWithTrailingSlash, dockerImage), dockerContainer.Image) +} diff --git a/docs/contributing.md b/docs/contributing.md index 4cb1cde09c..5bc9802e4f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,12 +1,105 @@ # Contributing -* Star the project on [Github](https://github.com/testcontainers/testcontainers-go) and help spread the word :) -* Join our [Slack workspace](http://slack.testcontainers.org) -* [Post an issue](https://github.com/testcontainers/testcontainers-go/issues) if you find any bugs -* Contribute improvements or fixes using a [Pull Request](https://github.com/testcontainers/testcontainers-go/pulls). If you're going to contribute, thank you! Please just be sure to: - * discuss with the authors on an issue ticket prior to doing anything big. - * follow the style, naming and structure conventions of the rest of the project. - * make commits atomic and easy to merge. - * when updating documentation, please see [our guidance for documentation contributions](contributing_docs.md). - * apply format running `go fmt`, or the shell script `./scripts/checks.sh` - * verify all tests are passing. Build and test the project with `make test-all` to do this. +`Testcontainers for Go` is open source, and we love to receive contributions from our community — you! + +There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests, or writing code for the core library or for a technology module. + +In any case, if you like the project, please star the project on [Github](https://github.com/testcontainers/testcontainers-go/stargazers) and help spread the word :) +Also join our [Slack workspace](http://slack.testcontainers.org) to get help, share your ideas, and chat with the community. + +## Questions + +GitHub is reserved for bug reports and feature requests; it is not the place for general questions. +If you have a question or an unconfirmed bug, please visit our [Slack workspace](https://testcontainers.slack.com/); +feedback and ideas are always welcome. + +## Code contributions + +If you have a bug fix or new feature that you would like to contribute, please find or open an [issue](https://github.com/testcontainers/testcontainers-go/issues) first. +It's important to talk about what you would like to do, as there may already be someone working on it, +or there may be context to be aware of before implementing the change. + +Next would be to fork the repository and make your changes in a feature branch. **Please do not commit changes to the `main` branch**, +otherwise we won't be able to contribute to your changes directly in the PR. + +### Submitting your changes + +Please just be sure to: + +* follow the style, naming and structure conventions of the rest of the project. +* make commits atomic and easy to merge. +* use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for the PR title. This will help us to understand the nature of the changes, and to generate the changelog after all the commits in the PR are squashed. + * Please use the `feat!`, `chore!`, `fix!`... types for breaking changes, as these categories are considered as `breaking change` in the changelog. Please use the `!` to denote a breaking change. + * Please use the `security` type for security fixes, as these categories are considered as `security` in the changelog. + * Please use the `feat` type for new features, as these categories are considered as `feature` in the changelog. + * Please use the `fix` type for bug fixes, as these categories are considered as `bug` in the changelog. + * Please use the `docs` type for documentation updates, as these categories are considered as `documentation` in the changelog. + * Please use the `chore` type for housekeeping commits, including `build`, `ci`, `style`, `refactor`, `test`, `perf` and so on, as these categories are considered as `chore` in the changelog. + * Please use the `deps` type for dependency updates, as these categories are considered as `dependencies` in the changelog. + +!!!important + There is a GitHub Actions workflow that will check if your PR title follows the conventional commits convention. If not, it contributes a failed check to your PR. + To know more about the conventions, please refer to the [workflow file](https://github.com/testcontainers/testcontainers-go/blob/main/.github/workflows/conventions.yml). + +* use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for your commit messages, as it improves the readability of the commit history, and the review process. Please follow the above conventions for the PR title. +* unless necessary, please try to **avoid pushing --force** to the published branch you submitted a PR from, as it makes it harder to review the changes from a given previous state. +* apply format running `make lint-all`. It will run `golangci-lint` for the core and modules with the configuration set in the root directory of the project. Please be aware that the lint stage on CI could fail if this is not done. + * For linting just the modules: `make -C modules lint-modules` + * For linting just the examples: `make -C examples lint-examples` + * For linting just the modulegen: `make -C modulegen lint` +* verify all tests are passing. Build and test the project with `make test-all` to do this. + * For a given module or example, go to the module or example directory and run `make test`. + * If you find an `ld warning` message on MacOS, you can ignore it. It is a indeed a warning: https://github.com/golang/go/issues/61229 +> === Errors +> ld: warning: '/private/var/folders/3y/8hbf585d4yl6f8j5yzqx6wz80000gn/T/go-link-2319589277/000018.o' has malformed LC_DYSYMTAB, expected 98 undefined symbols to start at index 1626, found 95 undefined symbols starting at index 1626 + +* when updating the `go.mod` file, please run `make tidy-all` to ensure all modules are updated. + +## Documentation contributions + +The _Testcontainers for Go_ documentation is a static site built with [MkDocs](https://www.mkdocs.org/). +We use the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme, which offers a number of useful extensions to MkDocs. + +We publish our documentation using Netlify. + +### Adding code snippets + +To include code snippets in the documentation, we use the [codeinclude plugin](https://github.com/rnorth/mkdocs-codeinclude-plugin), which uses the following syntax: + +> <!--codeinclude-->
+> [Human readable title for snippet](./relative_path_to_example_code.go) targeting_expression
+> [Human readable title for snippet](./relative_path_to_example_code.go) targeting_expression
+> <!--/codeinclude-->
+ +Where each title snippet in the same `codeinclude` block would represent a new tab +in the snippet, and each `targeting_expression` would be: + +- `block:someString` or +- `inside_block:someString` + +Please refer to the [codeinclude plugin documentation](https://github.com/rnorth/mkdocs-codeinclude-plugin) for more information. + +### Previewing rendered content + +#### Using Python locally + +From the root directory of the repository, you can use the following command to build and serve the documentation locally: + +```shell +make serve-docs +``` + +It will use a Python's virtual environment to install the required dependencies and start a local server at `http://localhost:8000`. + +Once finished, you can destroy the virtual environment with the following command: + +```shell +make clean-docs +``` + +#### PR Preview deployments + +Note that documentation for pull requests will automatically be published by Netlify as 'deploy previews'. +These deployment previews can be accessed via the `deploy/netlify` check that appears for each pull request. + +Please check the Github comment Netlify posts on the PR for the URL to the deployment preview. diff --git a/docs/contributing_docs.md b/docs/contributing_docs.md deleted file mode 100644 index 46f8714bc8..0000000000 --- a/docs/contributing_docs.md +++ /dev/null @@ -1,21 +0,0 @@ -# Contributing to documentation - -The Testcontainers for Go documentation is a static site built with [MkDocs](https://www.mkdocs.org/). -We use the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme, which offers a number of useful extensions to MkDocs. - -In addition we use a [custom plugin](https://github.com/rnorth/mkdocs-codeinclude-plugin) for inclusion of code snippets. - -We publish our documentation using Netlify. - -## Previewing rendered content - -### Using Python locally - -* Ensure that you have Python 3.8.0 or higher. -* Set up a virtualenv and run `pip install -r requirements.txt` in the `testcontainers-go` root directory. -* Once Python dependencies have been installed, run `mkdocs serve` to start a local auto-updating MkDocs server. - -### PR Preview deployments - -Note that documentation for pull requests will automatically be published by Netlify as 'deploy previews'. -These deployment previews can be accessed via the `deploy/netlify` check that appears for each pull request. diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index d7a23c8559..18d0e4b007 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -22,17 +22,17 @@ Using the `WithImageSubstitutors` options, you could define your own substitutio If you need to either pass additional environment variables to a container or override them, you can use `testcontainers.WithEnv` for example: ```golang -postgres, err = postgresModule.RunContainer(ctx, testcontainers.WithEnv(map[string]string{"POSTGRES_INITDB_ARGS": "--no-sync"})) +postgres, err = postgresModule.Run(ctx, "postgres:15-alpine", testcontainers.WithEnv(map[string]string{"POSTGRES_INITDB_ARGS": "--no-sync"})) ``` #### WithHostPortAccess -- Not available until the next release of testcontainers-go :material-tag: main +- Since testcontainers-go :material-tag: v0.31.0 If you need to access a port that is already running in the host, you can use `testcontainers.WithHostPortAccess` for example: ```golang -postgres, err = postgresModule.RunContainer(ctx, testcontainers.WithHostPortAccess(8080)) +postgres, err = postgresModule.Run(ctx, "postgres:15-alpine", testcontainers.WithHostPortAccess(8080)) ``` To understand more about this feature, please read the [Exposing host ports to the container](/features/networking/#exposing-host-ports-to-the-container) documentation. @@ -70,7 +70,8 @@ useful context instead of appearing out of band. ```golang func TestHandler(t *testing.T) { logger := TestLogger(t) - _, err := postgresModule.RunContainer(ctx, testcontainers.WithLogger(logger)) + ctr, err := postgresModule.Run(ctx, "postgres:15-alpine", testcontainers.WithLogger(logger)) + CleanupContainer(t, ctr) require.NoError(t, err) // Do something with container. } @@ -150,7 +151,7 @@ Please read the [Create containers: Advanced Settings](/features/creating_contai This option will merge the customized request into the module's own `ContainerRequest`. ```go -container, err := RunContainer(ctx, +container, err := Run(ctx, "postgres:13-alpine", /* Other module options */ testcontainers.CustomizeRequest(testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ diff --git a/docs/features/configuration.md b/docs/features/configuration.md index 5b0d5973d7..8da214e977 100644 --- a/docs/features/configuration.md +++ b/docs/features/configuration.md @@ -49,14 +49,25 @@ Please read more about customizing images in the [Image name substitution](image 1. Ryuk must be started as a privileged container. For that, you can set the `TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED` **environment variable**, or the `ryuk.container.privileged` **property** to `true`. 1. If your environment already implements automatic cleanup of containers after the execution, but does not allow starting privileged containers, you can turn off the Ryuk container by setting -`TESTCONTAINERS_RYUK_DISABLED` **environment variable** to `true`. -1. You can specify the connection timeout for Ryuk by setting the `ryuk.connection.timeout` **property**. The default value is 1 minute. -1. You can specify the reconnection timeout for Ryuk by setting the `ryuk.reconnection.timeout` **property**. The default value is 10 seconds. -1. You can configure Ryuk to run in verbose mode by setting any of the `ryuk.verbose` **property** or the `TESTCONTAINERS_RYUK_VERBOSE` **environment variable**. The default value is `false`. +`TESTCONTAINERS_RYUK_DISABLED` **environment variable** , or the `ryuk.disabled` **property** to `true`. +1. You can specify the connection timeout for Ryuk by setting the `RYUK_CONNECTION_TIMEOUT` **environment variable**, or the `ryuk.connection.timeout` **property**. The default value is 1 minute. +1. You can specify the reconnection timeout for Ryuk by setting the `RYUK_RECONNECTION_TIMEOUT` **environment variable**, or the `ryuk.reconnection.timeout` **property**. The default value is 10 seconds. +1. You can configure Ryuk to run in verbose mode by setting any of the `ryuk.verbose` **property** or the `RYUK_VERBOSE` **environment variable**. The default value is `false`. !!!info For more information about Ryuk, see [Garbage Collector](garbage_collector.md). +!!!warn + If using Ryuk and the Compose module, please increase the `ryuk.connection.timeout` to at least 5 minutes. + This is because the Compose module may take longer to start all the services. Besides, the `ryuk.reconnection.timeout` + should be increased to at least 30 seconds. For further information, please check [https://github.com/testcontainers/testcontainers-go/pull/2485](https://github.com/testcontainers/testcontainers-go/pull/2485). + +!!!warn + The following environment variables for configuring Ryuk have been deprecated: + `TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT`, `TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT` and + `TESTCONTAINERS_RYUK_VERBOSE` have been replaced by `RYUK_CONNECTION_TIMEOUT` + `RYUK_RECONNECTION_TIMEOUT` and `RYUK_VERBOSE` respectively. + ## Docker host detection _Testcontainers for Go_ will attempt to detect the Docker environment and configure everything to work automatically. @@ -80,7 +91,7 @@ See [Docker environment variables](https://docs.docker.com/engine/reference/comm 3. `${HOME}/.docker/desktop/docker.sock`. 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user. -7. The default Docker socket including schema will be returned if none of the above are set. +7. The library panics if none of the above are set, meaning that the Docker host was not detected. ## Docker socket path detection @@ -104,4 +115,4 @@ Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other container 6. Else, the default location of the docker socket is used: `/var/run/docker.sock` -In any case, if the docker socket schema is `tcp://`, the default docker socket path will be returned. +The library panics if the Docker host cannot be discovered. diff --git a/docs/features/creating_container.md b/docs/features/creating_container.md index decabc2a35..35e53d003e 100644 --- a/docs/features/creating_container.md +++ b/docs/features/creating_container.md @@ -11,7 +11,15 @@ up with Testcontainers and integrate into your tests: `testcontainers.GenericContainer` defines the container that should be run, similar to the `docker run` command. -The following test creates an NGINX container and validates that it returns 200 for the status code: +The following test creates an NGINX container on both the `bridge` (docker default +network) and the `foo` network and validates that it returns 200 for the status code. + +It also demonstrates how to use `CleanupContainer` ensures that nginx container +is removed when the test ends even if the underlying `GenericContainer` errored +as well as the `CleanupNetwork` which does the same for networks. + +The alternatives for these outside of tests as a `defer` are `TerminateContainer` +and `Network.Remove` which can be seen in the examples. ```go package main @@ -32,33 +40,38 @@ type nginxContainer struct { } -func setupNginx(ctx context.Context) (*nginxContainer, error) { +func setupNginx(ctx context.Context, networkName string) (*nginxContainer, error) { req := testcontainers.ContainerRequest{ Image: "nginx", ExposedPorts: []string{"80/tcp"}, + Networks: []string{"bridge", networkName}, WaitingFor: wait.ForHTTP("/"), } container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + var nginxC *nginxContainer + if container != nil { + nginxC = &nginxContainer{Container: c} + } if err != nil { - return nil, err + return nginxC, err } ip, err := container.Host(ctx) if err != nil { - return nil, err + return nginxC, err } mappedPort, err := container.MappedPort(ctx, "80") if err != nil { - return nil, err + return nginxC, err } - uri := fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) + nginxC.URI = fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) - return &nginxContainer{Container: container, URI: uri}, nil + return nginxC, nil } func TestIntegrationNginxLatestReturn(t *testing.T) { @@ -68,31 +81,33 @@ func TestIntegrationNginxLatestReturn(t *testing.T) { ctx := context.Background() - nginxC, err := setupNginx(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := nginxC.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } + networkName := "foo" + net, err := provider.CreateNetwork(ctx, NetworkRequest{ + Name: networkName, }) + require.NoError(t, err) + CleanupNetwork(t, net) + + nginxC, err := setupNginx(ctx, networkName) + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) resp, err := http.Get(nginxC.URI) - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) } ``` + + + ### Lifecycle hooks _Testcontainers for Go_ allows you to define your own lifecycle hooks for better control over your containers. You just need to define functions that return an error and receive the Go context as first argument, and a `ContainerRequest` for the `Creating` hook, and a `Container` for the rest of them as second argument. You'll be able to pass multiple lifecycle hooks at the `ContainerRequest` as an array of `testcontainers.ContainerLifecycleHooks`. The `testcontainers.ContainerLifecycleHooks` struct defines the following lifecycle hooks, each of them backed by an array of functions representing the hooks: +* `PreBuilds` - hooks that are executed before the image is built. This hook is only available when creating a container from a Dockerfile +* `PostBuilds` - hooks that are executed after the image is built. This hook is only available when creating a container from a Dockerfile * `PreCreates` - hooks that are executed before the container is created * `PostCreates` - hooks that are executed after the container is created * `PreStarts` - hooks that are executed before the container is started @@ -145,8 +160,8 @@ The aforementioned `GenericContainer` function and the `ContainerRequest` struct ## Reusable container -With `Reuse` option you can reuse an existing container. Reusing will work only if you pass an -existing container name via 'req.Name' field. If the name is not in a list of existing containers, +With `Reuse` option you can reuse an existing container. Reusing will work only if you pass an +existing container name via 'req.Name' field. If the name is not in a list of existing containers, the function will create a new generic container. If `Reuse` is true and `Name` is empty, you will get error. The following test creates an NGINX container, adds a file into it and then reuses the container again for checking the file: @@ -159,54 +174,70 @@ import ( "log" "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" ) const ( reusableContainerName = "my_test_reusable_container" ) -ctx := context.Background() +func main() { + ctx := context.Background() -n1, err := GenericContainer(ctx, GenericContainerRequest{ - ContainerRequest: ContainerRequest{ - 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) + n1, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "nginx:1.17.6", + ExposedPorts: []string{"80/tcp"}, + WaitingFor: wait.ForListeningPort("80/tcp"), + Name: reusableContainerName, + }, + Started: true, + }) + defer func() { + if err := testcontainers.TerminateContainer(n1); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Print(err) + return + } -copiedFileName := "hello_copy.sh" -err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700) + copiedFileName := "hello_copy.sh" + err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700) -if err != nil { - log.Fatal(err) -} + if err != nil { + log.Print(err) + return + } -n2, err := GenericContainer(ctx, GenericContainerRequest{ - ContainerRequest: ContainerRequest{ - 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) -} + n2, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "nginx:1.17.6", + ExposedPorts: []string{"80/tcp"}, + WaitingFor: wait.ForListeningPort("80/tcp"), + Name: reusableContainerName, + }, + Started: true, + Reuse: true, + }) + defer func() { + if err := testcontainers.TerminateContainer(n2); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Print(err) + return + } -c, _, err := n2.Exec(ctx, []string{"bash", copiedFileName}) -if err != nil { - log.Fatal(err) + c, _, err := n2.Exec(ctx, []string{"bash", copiedFileName}) + if err != nil { + log.Print(err) + return + } + fmt.Println(c) } -fmt.Println(c) ``` ## Parallel running @@ -253,10 +284,20 @@ func main() { } res, err := testcontainers.ParallelContainers(ctx, requests, testcontainers.ParallelContainersOptions{}) + for _, c := range res { + c := c + defer func() { + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", c) + } + }() + } + if err != nil { e, ok := err.(testcontainers.ParallelContainersError) if !ok { - log.Fatalf("unknown error: %v", err) + log.Printf("unknown error: %v", err) + return } for _, pe := range e.Errors { @@ -264,14 +305,5 @@ func main() { } return } - - for _, c := range res { - c := c - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", c) - } - }() - } } ``` diff --git a/docs/features/creating_networks.md b/docs/features/creating_networks.md index 09be152a55..7ee490104d 100644 --- a/docs/features/creating_networks.md +++ b/docs/features/creating_networks.md @@ -25,6 +25,6 @@ It's important to mention that the name of the network is automatically generate ## Usage example -[Creating a network](../../network/network_test.go) inside_block:createNetwork -[Creating a network with options](../../network/network_test.go) inside_block:newNetworkWithOptions +[Creating a network](../../network/examples_test.go) inside_block:createNetwork +[Creating a network with options](../../network/examples_test.go) inside_block:newNetworkWithOptions \ No newline at end of file diff --git a/docs/features/docker_compose.md b/docs/features/docker_compose.md index 8a2d068792..2dfa9f22ca 100644 --- a/docs/features/docker_compose.md +++ b/docs/features/docker_compose.md @@ -20,6 +20,10 @@ Because `compose` v2 is implemented in Go it's possible for _Testcontainers for use [`github.com/docker/compose`](https://github.com/docker/compose) directly and skip any process execution/_docker-compose-in-a-container_ scenario. The `ComposeStack` API exposes this variant of using `docker compose` in an easy way. +Before using the Compose module, there is some configuration that needs to be applied first. +It customizes the behaviour of the `Ryuk` container, which is used to clean up the resources created by the `docker compose` stack. +Please refer to [the Ryuk configuration](../configuration/#customizing-ryuk-the-resource-reaper) for more information. + ### Usage Use the convenience `NewDockerCompose(...)` constructor which creates a random identifier and takes a variable number @@ -173,7 +177,7 @@ further information. ## Usage of the `docker compose` binary -_Node:_ this API is deprecated and superseded by `ComposeStack` which takes advantage of `compose` v2 being +_Note:_ this API is deprecated and superseded by `ComposeStack` which takes advantage of `compose` v2 being implemented in Go as well by directly using the upstream project. You can override Testcontainers' default behaviour and make it use a diff --git a/docs/features/image_name_substitution.md b/docs/features/image_name_substitution.md index 08e9e1d6a0..4272d35c1e 100644 --- a/docs/features/image_name_substitution.md +++ b/docs/features/image_name_substitution.md @@ -45,7 +45,7 @@ _Testcontainers for Go_ will automatically apply the prefix to every image that _Testcontainers for Go_ will not apply the prefix to: * non-Hub image names (e.g. where another registry is set) -* Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a `docker.io` or `registry.hub.docker.com` host part) +* Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a `registry.hub.docker.com` host part) ## Developing a custom function for transforming image names on the fly @@ -68,7 +68,7 @@ You can implement a custom image name substitutor by: * implementing the `ImageNameSubstitutor` interface, exposed by the `testcontainers` package. * configuring _Testcontainers for Go_ to use your custom implementation, defined at the `ContainerRequest` level. -The following is an example image substitutor implementation prepending the `docker.io/` prefix, used in the tests: +The following is an example image substitutor implementation prepending the `registry.hub.docker.com/library/` prefix, used in the tests: [Image Substitutor Interface](../../options.go) inside_block:imageSubstitutor diff --git a/docs/features/networking.md b/docs/features/networking.md index 2eb6ef2812..69343b47c6 100644 --- a/docs/features/networking.md +++ b/docs/features/networking.md @@ -45,11 +45,11 @@ It is normally advisable to use `Host` and `MappedPort` together when constructi !!! info - Setting the `TC_HOST` environment variable overrides the host of the docker daemon where the container port is exposed. For example, `TC_HOST=172.17.0.1`. + Setting the `TESTCONTAINERS_HOST_OVERRIDE` environment variable overrides the host of the docker daemon where the container port is exposed. For example, `TESTCONTAINERS_HOST_OVERRIDE=172.17.0.1`. ## Exposing host ports to the container -- Not available until the next release of testcontainers-go :material-tag: main +- Since testcontainers-go :material-tag: v0.31.0 In some cases it is necessary to make a network connection from a container to a socket that is listening on the host machine. Natively, Docker has limited support for this model across platforms. Testcontainers, however, makes this possible, allowing your code to access services running on the host machine. diff --git a/docs/features/test_session_semantics.md b/docs/features/test_session_semantics.md new file mode 100644 index 0000000000..2db45dfa44 --- /dev/null +++ b/docs/features/test_session_semantics.md @@ -0,0 +1,33 @@ +# Test Session Semantics + +The test session semantics is a feature that allows _Testcontainers for Go_ to identify the current test session +and tag the containers created by the library with a unique session ID. + +This is needed because each Go package will be run in a separate process, so we need a way to identify the current test execution +to aggregate the tests executed in it. + +By test session, we mean: + +- a single `go test` invocation (including flags). +- a single `go test ./...` invocation, for all subpackages from that location (including flags). +- the execution of a single test or a set of tests using the IDE. + +As a consequence, _Testcontainers for Go_ will use the parent process ID (pid) of the current process and its creation date +to generate a unique session ID. + +We are using the parent pid because the current `go test` process running a given Go package will be a child of one of the following: + +- the process that is running the tests, e.g.: `go test`; +- the process that is running the application in development mode, e.g. `go run main.go -tags dev`; +- the process that is running the tests in the IDE, e.g.: `go test ./...`. + +That's why we need to use the parent pid to identify the current test session, as it must be unique. + +Finally, we will hash the combination of the `testcontainers-go:` string with the parent pid and the creation date +of that parent process to generate a unique session ID. + +After that, the `sessionID` will be used to: + +- identify the test session, aggregating the test execution of multiple packages in the same test session. +- pass the `sessionID` to the container runtime, as an HTTP header to the daemon. +- tag the containers created by _Testcontainers for Go_, adding a label to the container with this session ID. 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/features/wait/exit.md b/docs/features/wait/exit.md index 3487cb2d21..bcb1aaca36 100644 --- a/docs/features/wait/exit.md +++ b/docs/features/wait/exit.md @@ -9,7 +9,7 @@ The exit wait strategy will check that the container is not in the running state ```golang req := ContainerRequest{ - Image: "docker.io/alpine:latest", + Image: "alpine:latest", WaitingFor: wait.ForExit(), } ``` diff --git a/docs/features/wait/file.md b/docs/features/wait/file.md new file mode 100644 index 0000000000..8bdf8ade27 --- /dev/null +++ b/docs/features/wait/file.md @@ -0,0 +1,14 @@ +# File Wait Strategy + +File Wait Strategy waits for a file to exist in the container, and allows to set the following conditions: + +- the file to wait for. +- a matcher which reads the file content, no-op if nil or not set. +- the startup timeout to be used in seconds, default is 60 seconds. +- the poll interval to be used in milliseconds, default is 100 milliseconds. + +## Waiting for file to exist and extract the content + + +[Waiting for file to exist and extract the content](../../../wait/file_test.go) inside_block:waitForFileWithMatcher + diff --git a/docs/features/wait/health.md b/docs/features/wait/health.md index d4756f47bd..f724a11010 100644 --- a/docs/features/wait/health.md +++ b/docs/features/wait/health.md @@ -7,7 +7,7 @@ The health wait strategy will check that the container is in the healthy state a ```golang req := ContainerRequest{ - Image: "docker.io/alpine:latest", + Image: "alpine:latest", WaitingFor: wait.ForHealthCheck(), } ``` diff --git a/docs/features/wait/host_port.md b/docs/features/wait/host_port.md index 5d52f99cc1..10531e5e64 100644 --- a/docs/features/wait/host_port.md +++ b/docs/features/wait/host_port.md @@ -3,9 +3,10 @@ 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. +- skip the internal check. Variations on the HostPort wait strategy are supported, including: @@ -13,29 +14,49 @@ Variations on the HostPort wait strategy are supported, including: ```golang req := ContainerRequest{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForListeningPort("80/tcp"), } ``` -## 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{ - Image: "docker.io/nginx:alpine", + Image: "nginx:alpine", WaitingFor: wait.ForExposedPort(), } ``` -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", + Image: "nginx:alpine", ExposedPorts: []string{"80/tcp", "9080/tcp"}, WaitingFor: wait.ForExposedPort(), } -``` \ No newline at end of file +``` + +## Skipping the internal check + +_Testcontainers for Go_ checks if the container is listening to the port internally before returning the control to the caller. For that it uses a shell command to check the port status: + + +[Internal check](../../../wait/host_port.go) inside_block:buildInternalCheckCommand + + +But there are cases where this internal check is not needed, for example when a shell is not available in the container or +when the container doesn't bind the port internally until additional conditions are met. +In this case, the `wait.ForExposedPort.SkipInternalCheck` can be used to skip the internal check. + +```golang +req := ContainerRequest{ + Image: "nginx:alpine", + ExposedPorts: []string{"80/tcp", "9080/tcp"}, + WaitingFor: wait.ForExposedPort().SkipInternalCheck(), +} +``` diff --git a/docs/features/wait/http.md b/docs/features/wait/http.md index ab9d294fb5..b4b9b57b95 100644 --- a/docs/features/wait/http.md +++ b/docs/features/wait/http.md @@ -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. diff --git a/docs/features/wait/introduction.md b/docs/features/wait/introduction.md index 5af611321c..feef9dc939 100644 --- a/docs/features/wait/introduction.md +++ b/docs/features/wait/introduction.md @@ -8,12 +8,14 @@ Below you can find a list of the available wait strategies that you can use: - [Exec](./exec.md) - [Exit](./exit.md) +- [File](./file.md) - [Health](./health.md) - [HostPort](./host_port.md) - [HTTP](./http.md) - [Log](./log.md) - [Multi](./multi.md) - [SQL](./sql.md) +- [TLS](./tls.md) ## Startup timeout and Poll interval @@ -24,3 +26,8 @@ If the default 60s timeout is not sufficient, it can be updated with the `WithSt Besides that, it's possible to define a poll interval, which will actually stop 100 milliseconds the test execution. If the default 100 milliseconds poll interval is not sufficient, it can be updated with the `WithPollInterval(pollInterval time.Duration)` function. + +## Modifying request strategies + +It's possible for options to modify `ContainerRequest.WaitingFor` using +[Walk](walk.md). diff --git a/docs/features/wait/log.md b/docs/features/wait/log.md index 66c418b284..f1d40ff360 100644 --- a/docs/features/wait/log.md +++ b/docs/features/wait/log.md @@ -10,7 +10,7 @@ The Log wait strategy will check if a string occurs in the container logs for a ```golang req := ContainerRequest{ - Image: "docker.io/mysql:8.0.36", + Image: "mysql:8.0.36", ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "MYSQL_ROOT_PASSWORD": "password", @@ -24,7 +24,7 @@ Using a regular expression: ```golang req := ContainerRequest{ - Image: "docker.io/mysql:8.0.36", + Image: "mysql:8.0.36", ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "MYSQL_ROOT_PASSWORD": "password", diff --git a/docs/features/wait/multi.md b/docs/features/wait/multi.md index bfd053955b..d5f809d6c2 100644 --- a/docs/features/wait/multi.md +++ b/docs/features/wait/multi.md @@ -9,7 +9,7 @@ Available Options: ```golang req := ContainerRequest{ - Image: "docker.io/mysql:8.0.36", + Image: "mysql:8.0.36", ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "MYSQL_ROOT_PASSWORD": "password", diff --git a/docs/features/wait/tls.md b/docs/features/wait/tls.md new file mode 100644 index 0000000000..a98f78d84c --- /dev/null +++ b/docs/features/wait/tls.md @@ -0,0 +1,31 @@ +# TLS Strategy + +TLS Strategy waits for one or more files to exist in the container and uses them +and other details to construct a `tls.Config` which can be used to create secure +connections. + +It supports: + +- x509 PEM Certificate loaded from a certificate / key file pair. +- Root Certificate Authorities aka RootCAs loaded from PEM encoded files. +- Server name. +- Startup timeout to be used in seconds, default is 60 seconds. +- Poll interval to be used in milliseconds, default is 100 milliseconds. + +## Waiting for certificate pair + +The following snippets show how to configure a request to wait for certificate +pair to exist once started and then read the +[tls.Config](https://pkg.go.dev/crypto/tls#Config), alongside how to copy a test +certificate pair into a container image using a `Dockerfile`. + +It should be noted that copying certificate pairs into an images is only an +example which might be useful for testing with testcontainers-go and should not +be done with production images as that could expose your certificates if your +images become public. + + +[Wait for certificate](../../../wait/tls_test.go) inside_block:waitForTLSCert +[Read TLS Config](../../../wait/tls_test.go) inside_block:waitTLSConfig +[Dockerfile with certificate](../../../wait/testdata/http/Dockerfile) + diff --git a/docs/features/wait/walk.md b/docs/features/wait/walk.md new file mode 100644 index 0000000000..f8db724cc0 --- /dev/null +++ b/docs/features/wait/walk.md @@ -0,0 +1,19 @@ +# Walk + +Walk walks the strategies tree and calls the visit function for each node. + +This allows modules to easily amend default wait strategies, updating or +removing specific strategies based on requirements of functional options. + +For example removing a TLS strategy if a functional option enabled insecure mode +or changing the location of the certificate based on the configured user. + +If visit function returns `wait.VisitStop`, the walk stops. +If visit function returns `wait.VisitRemove`, the current node is removed. + +## Walk removing entries + +The following example shows how to remove a strategy based on its type. + +[Remove FileStrategy entries](../../../wait/walk_test.go) inside_block:walkRemoveFileStrategy + diff --git a/docs/modules/artemis.md b/docs/modules/artemis.md index 2195b1f250..da13e2178a 100644 --- a/docs/modules/artemis.md +++ b/docs/modules/artemis.md @@ -24,15 +24,23 @@ go get github.com/testcontainers/testcontainers-go/modules/artemis [Connecting to an Artemis container](../../modules/artemis/examples_test.go) inside_block:connectToArtemisContainer -## Module reference +## Module Reference -The Artemis module exposes one entrypoint function to create the Artemis container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Artemis module exposes one entrypoint function to create the Artemis container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -41,8 +49,8 @@ When starting the Artemis container, you can pass options in a variadic way to c #### Image -If you need to set a different Artemis Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Artemis. E.g. `testcontainers.WithImage("docker.io/apache/activemq-artemis:2.30.0")`. +If you need to set a different Artemis Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "apache/activemq-artemis:2.30.0")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/azurite.md b/docs/modules/azurite.md new file mode 100644 index 0000000000..f1c7c42d14 --- /dev/null +++ b/docs/modules/azurite.md @@ -0,0 +1,115 @@ +# Azurite + +Since testcontainers-go :material-tag: v0.32.0 + +## Introduction + +The Testcontainers module for Azurite. + +## Adding this module to your project dependencies + +Please run the following command to add the Azurite module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/azurite +``` + +## Usage example + + +[Creating a Azurite container](../../modules/azurite/examples_test.go) inside_block:runAzuriteContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Azurite module exposes one entrypoint function to create the Azurite container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*AzuriteContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Default Credentials + +The Azurite container uses the following default credentials: + + +[Default Credencials](../../modules/azurite/azurite.go) inside_block:defaultCredentials + + +### Container Options + +When starting the Azurite container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Azurite Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mcr.microsoft.com/azure-storage/azurite:3.28.0")`. + +{% include "../features/common_functional_options.md" %} + +#### WithInMemoryPersistence + +- Since testcontainers-go :material-tag: v0.32.0 + +If you want to use in-memory persistence, you can use `WithInMemoryPersistence(megabytes float64)`. E.g. `azurite.WithInMemoryPersistence(64.0)`. + +Please read the [Azurite documentation](https://github.com/Azure/Azurite?tab=readme-ov-file#use-in-memory-storage) for more information. + +!!! warning + This option is only available in Azurite versions 3.28.0 and later. + +### Container Methods + +The Azurite container exposes the following methods: + +#### ServiceURL + +- Since testcontainers-go :material-tag: v0.32.0 + +Returns the service URL to connect to the Azurite container and an error, passing the Go context and the service name as parameters. + +#### MustServiceURL + +- Since testcontainers-go :material-tag: v0.32.0 + +Returns the service URL to connect to the Azurite container, passing the Go context and the service name as parameters. If an error occurs, it will panic. + +### Examples + +#### Blob Operations + +In the following example, we will create a container with Azurite and perform some blob operations. For that, using the default +credentials, we will create an Azurite container, upload a blob to it, list the blobs, and download the blob. Finally, we will remove the created blob and container. + + +[Performing blob operations](../../modules/azurite/examples_test.go) inside_block:blobOperations + + +#### Queue Operations + +In the following example, we will create an Azurite container and perform some queue operations. For that, using the default +credentials, we will create a queue, list the queues, and finally we will remove the created queue. + + +[Performing queue operations](../../modules/azurite/examples_test.go) inside_block:queueOperations + + +#### Table Operations + +In the following example, we will create an Azurite container and perform some table operations. For that, using the default +credentials, we will create a table, list the tables, and finally we will remove the created table. + + +[Performing table operations](../../modules/azurite/examples_test.go) inside_block:tableOperations + \ No newline at end of file diff --git a/docs/modules/cassandra.md b/docs/modules/cassandra.md index a020db3832..f69705ecc9 100644 --- a/docs/modules/cassandra.md +++ b/docs/modules/cassandra.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/cassandra [Creating a Cassandra container](../../modules/cassandra/examples_test.go) inside_block:runCassandraContainer -## Module reference +## Module Reference -The Cassandra module exposes one entrypoint function to create the Cassandra container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Cassandra module exposes one entrypoint function to create the Cassandra container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CassandraContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CassandraContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Cassandra container, you can pass options in a variadic way to #### Image -If you need to set a different Cassandra Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Cassandra. E.g. `testcontainers.WithImage("cassandra:4.1.3")`. +If you need to set a different Cassandra Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "cassandra:4.1.3")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/chroma.md b/docs/modules/chroma.md index 4eb750e7f2..632528dffe 100644 --- a/docs/modules/chroma.md +++ b/docs/modules/chroma.md @@ -25,15 +25,23 @@ go get github.com/testcontainers/testcontainers-go/modules/chroma [Creating a Chroma container](../../modules/chroma/examples_test.go) inside_block:runChromaContainer -## Module reference +## Module Reference -The Chroma module exposes one entrypoint function to create the Chroma container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Chroma module exposes one entrypoint function to create the Chroma container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ChromaContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ChromaContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -42,8 +50,8 @@ When starting the Chroma container, you can pass options in a variadic way to co #### Image -If you need to set a different Chroma Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Chroma. E.g. `testcontainers.WithImage("chromadb/chroma:0.4.24")`. +If you need to set a different Chroma Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "chromadb/chroma:0.4.24")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/clickhouse.md b/docs/modules/clickhouse.md index 749ded3bec..0fb91bb918 100644 --- a/docs/modules/clickhouse.md +++ b/docs/modules/clickhouse.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/clickhouse [Test for a ClickHouse container](../../modules/clickhouse/examples_test.go) inside_block:runClickHouseContainer -## Module reference +## Module Reference -The ClickHouse module exposes one entrypoint function to create the ClickHouse container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The ClickHouse module exposes one entrypoint function to create the ClickHouse container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ClickHouseContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ClickHouseContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Ports @@ -45,8 +53,8 @@ When starting the ClickHouse container, you can pass options in a variadic way t #### Image -If you need to set a different ClickHouse Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for ClickHouse. E.g. `testcontainers.WithImage("clickhouse/clickhouse-server:23.3.8.21-alpine")`. +If you need to set a different ClickHouse Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "clickhouse/clickhouse-server:23.3.8.21-alpine")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/cockroachdb.md b/docs/modules/cockroachdb.md index 51c845b9f4..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 ``` @@ -20,28 +20,33 @@ go get github.com/testcontainers/testcontainers-go/modules/cockroachdb [Creating a CockroachDB container](../../modules/cockroachdb/examples_test.go) inside_block:runCockroachDBContainer -## Module reference +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. -The CockroachDB module exposes one entrypoint function to create the CockroachDB container, and this function receives two parameters: +The CockroachDB module exposes one entrypoint function to create the CockroachDB container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CockroachDBContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CockroachDBContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. -!!!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. - ### Container Options When starting the CockroachDB container, you can pass options in a variadic way to configure it. #### Image -If you need to set a different CockroachDB Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for CockroachDB. E.g. `testcontainers.WithImage("cockroachdb/cockroach:latest-v23.1")`. +If you need to set a different CockroachDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "cockroachdb/cockroach:latest-v23.1")`. {% include "../features/common_functional_options.md" %} @@ -49,9 +54,11 @@ for CockroachDB. E.g. `testcontainers.WithImage("cockroachdb/cockroach:latest-v2 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 @@ -59,10 +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`. -A helper `cockroachdb.NewTLSConfig` exists to generate all of this for you. +#### 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. + +`cockroachdb.WithNoClusterDefaults` disables the default cluster settings script. + +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 @@ -79,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/docs/modules/consul.md b/docs/modules/consul.md index e094efed9e..b2b77921df 100644 --- a/docs/modules/consul.md +++ b/docs/modules/consul.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/consul [Creating a Consul container](../../modules/consul/examples_test.go) inside_block:runConsulContainer -## Module reference +## Module Reference -The Consul module exposes one entrypoint function to create the Consul container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Consul module exposes one entrypoint function to create the Consul container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ConsulContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ConsulContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Consul container, you can pass options in a variadic way to co #### Image -If you need to set a different Consul Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Consul. E.g. `testcontainers.WithImage("docker.io/hashicorp/consul:1.15")`. +If you need to set a different Consul Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "hashicorp/consul:1.15")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/couchbase.md b/docs/modules/couchbase.md index 6f3a8a1398..43b0c85007 100644 --- a/docs/modules/couchbase.md +++ b/docs/modules/couchbase.md @@ -22,13 +22,21 @@ go get github.com/testcontainers/testcontainers-go/modules/couchbase ## Module Reference -The Couchbase module exposes one entrypoint function to create the Couchbase container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Couchbase module exposes one entrypoint function to create the Couchbase container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CouchbaseContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CouchbaseContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. Once the container is started, it will perform the following operations, **in this particular order**: @@ -64,14 +72,8 @@ When starting the Couchbase container, you can pass options in a variadic way to #### Image -If you need to set a different Couchbase Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Couchbase. E.g. `testcontainers.WithImage("docker.io/couchbase:6.5.1")`. - -By default, the container will use the following Docker image: - - -[Default Docker image](../../modules/couchbase/couchbase.go) inside_block:defaultImage - +If you need to set a different Couchbase Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "couchbase:6.5.1")`. You can find the Docker images that are currently tested in this module, for the Enterprise and Community editions, in the following list: @@ -84,7 +86,7 @@ You can find the Docker images that are currently tested in this module, for the #### Credentials If you need to change the default credentials for the admin user, you can use `WithAdminCredentials(user, password)` with a valid username and password. -When the password has less than 6 characters, the container won't be created and the `RunContainer` function will throw an error. +When the password has less than 6 characters, the container won't be created and the `New` function will throw an error. !!!info In the case this optional function is not called, the default username is `Administrator` and the default password is `password`. diff --git a/docs/modules/databend.md b/docs/modules/databend.md new file mode 100644 index 0000000000..0e3e2fe438 --- /dev/null +++ b/docs/modules/databend.md @@ -0,0 +1,72 @@ +# Databend + +Since testcontainers-go :material-tag: v0.34.0 + +## Introduction + +The Testcontainers module for Databend. + +## Adding this module to your project dependencies + +Please run the following command to add the Databend module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/databend +``` + +## Usage example + + +[Creating a Databend container](../../modules/databend/examples_test.go) inside_block:runDatabendContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.34.0 + +The Databend module exposes one entrypoint function to create the Databend container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatabendContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Databend container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Databend Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "datafuselabs/databend:v1.2.615")`. + +{% include "../features/common_functional_options.md" %} + +#### Set username, password + +If you need to set a different user/password/database, you can use `WithUsername`, `WithPassword` options. + +!!!info +The default values for the username is `databend`, for password is `databend` and for the default database name is `default`. + +### Container Methods + +The Databend container exposes the following methods: + +#### ConnectionString + +This method returns the connection string to connect to the Databend container, using the default `8000` port. +It's possible to pass extra parameters to the connection string, e.g. `sslmode=disable`. + + +[Get connection string](../../modules/databend/databend_test.go) inside_block:connectionString + + +#### MustGetConnectionString + +`MustConnectionString` panics if the address cannot be determined. diff --git a/docs/modules/dolt.md b/docs/modules/dolt.md index 3e7fdfc43e..2e0fb219b3 100644 --- a/docs/modules/dolt.md +++ b/docs/modules/dolt.md @@ -1,6 +1,6 @@ # Dolt -Not available until the next release of testcontainers-go :material-tag: main +Since testcontainers-go :material-tag: v0.31.0 ## Introduction @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/dolt [Creating a Dolt container](../../modules/dolt/examples_test.go) inside_block:runDoltContainer -## Module reference +## Module Reference -The Dolt module exposes one entrypoint function to create the Dolt container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Dolt module exposes one entrypoint function to create the Dolt container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Dolt container, you can pass options in a variadic way to conf #### 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")`. +If you need to set a different Dolt Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "dolthub/dolt-sql-server:1.32.4")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/dynamodb.md b/docs/modules/dynamodb.md new file mode 100644 index 0000000000..b7b53b64e9 --- /dev/null +++ b/docs/modules/dynamodb.md @@ -0,0 +1,77 @@ +# DynamoDB + +Since testcontainers-go :material-tag: v0.34.0 + +## Introduction + +The Testcontainers module for DynamoDB. + +## Adding this module to your project dependencies + +Please run the following command to add the DynamoDB module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/dynamodb +``` + +## Usage example + + +[Creating a DynamoDB container](../../modules/dynamodb/examples_test.go) inside_block:runDynamoDBContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.34.0 + +The DynamoDB module exposes one entrypoint function to create the DynamoDB container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DynamoDBContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the DynamoDB container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different DynamoDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "amazon/dynamodb-local:2.2.1")`. + +{% include "../features/common_functional_options.md" %} + +#### WithSharedDB + +- Since testcontainers-go :material-tag: v0.34.0 + +The `WithSharedDB` option tells the DynamoDB container to use a single database file. At the same time, it marks the container as reusable, which causes that successive calls to the `Run` function will return the same container instance, and therefore, the same database file. + +#### WithDisableTelemetry + +- Since testcontainers-go :material-tag: v0.34.0 + +You can turn off telemetry when starting the DynamoDB container, using the option `WithDisableTelemetry`. + +### Container Methods + +The DynamoDB container exposes the following methods: + +#### ConnectionString + +- Since testcontainers-go :material-tag: v0.34.0 + +The `ConnectionString` method returns the connection string to the DynamoDB container. This connection string can be used to connect to the DynamoDB container from your application, +using the AWS SDK or any other DynamoDB client of your choice. + + +[Creating a client](../../modules/dynamodb/dynamodb_test.go) inside_block:createClient + + +The above example uses `github.com/aws/aws-sdk-go-v2/service/dynamodb` to create a client and connect to the DynamoDB container. diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md index 18c58dc5eb..9d2da44213 100644 --- a/docs/modules/elasticsearch.md +++ b/docs/modules/elasticsearch.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/elasticsearch [Creating a Elasticsearch container](../../modules/elasticsearch/examples_test.go) inside_block:runElasticsearchContainer -## Module reference +## Module Reference -The Elasticsearch module exposes one entrypoint function to create the Elasticsearch container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Elasticsearch module exposes one entrypoint function to create the Elasticsearch container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ElasticsearchContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ElasticsearchContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Elasticsearch container, you can pass options in a variadic wa #### Image -If you need to set a different Elasticsearch Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Elasticsearch. E.g. `testcontainers.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.0.0")`. +If you need to set a different Elasticsearch Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "docker.elastic.co/elasticsearch/elasticsearch:8.0.0")`. {% include "../features/common_functional_options.md" %} @@ -66,7 +74,7 @@ The Elasticsearch container exposes its settings in order to configure the clien [Create an HTTP client](../../modules/elasticsearch/elasticsearch_test.go) inside_block:createHTTPClient -The `esContainer` instance is obtained from the `elasticsearch.RunContainer` function. +The `esContainer` instance is obtained from the `elasticsearch.New` function. In the case you configured the Elasticsearch container to set up a password, you'll need to add the `Authorization` header to the request. You can use the `SetBasicAuth` method from the HTTP request to generate the header value. diff --git a/docs/modules/etcd.md b/docs/modules/etcd.md new file mode 100644 index 0000000000..ffde87ac6b --- /dev/null +++ b/docs/modules/etcd.md @@ -0,0 +1,95 @@ +# etcd + +Since testcontainers-go :material-tag: v0.34.0 + +## Introduction + +The Testcontainers module for etcd. + +## Adding this module to your project dependencies + +Please run the following command to add the etcd module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/etcd +``` + +## Usage example + + +[Creating a etcd container](../../modules/etcd/examples_test.go) inside_block:runetcdContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.34.0 + +The etcd module exposes one entrypoint function to create the etcd container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*etcdContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the etcd container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different etcd Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "bitnami/etcd:latest")`. + +{% include "../features/common_functional_options.md" %} + +#### WithAdditionalArgs + +- Since testcontainers-go :material-tag: v0.34.0 + +You can pass additional arguments to the etcd container by using the `WithAdditionalArgs` option. The arguments are passed to the CMD of the etcd container. + +#### WithDataDir + +- Since testcontainers-go :material-tag: v0.34.0 + +You can set the data directory for the etcd container by using the `WithDataDir` boolean option. The data directory where the etcd data is stored is `/data.etcd`. + +#### WithNodes + +- Since testcontainers-go :material-tag: v0.34.0 + +You can set the number of nodes for the etcd cluster by using the `WithNodes` option, passing the node names for each of the nodes. Single-node clusters are not allowed, +for that reason the functional option receives three string arguments: the first node, the second node, and a variadic argument for the rest of the nodes. +The module starts a container for each node, having the first node a reference to the other nodes. E.g. `WithNodes("etcd-1", "etcd-2")`, `WithNodes("etcd-1", "etcd-2", "etcd-3")` and so on. + +The module creates a Docker network for the etcd cluster, and the nodes are connected to this network, so that they can communicate with each other through the network. + +#### WithClusterToken + +- Since testcontainers-go :material-tag: v0.34.0 + +Sets the cluster token for the etcd cluster. The cluster token is used to identify the etcd cluster. The default value is `mys3cr3ttok3n`. +The etcd container holds a reference to the cluster token, so you can use it with e.g. `ctr.ClusterToken`. + +### Container Methods + +- Since testcontainers-go :material-tag: v0.34.0 + +The etcd container exposes the following methods: + +#### ClientEndpoint + +- Since testcontainers-go :material-tag: v0.34.0 + +Returns the client endpoint for the etcd container and an error, if any. In the case of a cluster, it returns the client endpoint for the first node. + +#### PeerEndpoint + +- Since testcontainers-go :material-tag: v0.34.0 + +Returns the peer endpoint for the etcd container and an error, if any. In the case of a cluster, it returns the peer endpoint for the first node. diff --git a/docs/modules/gcloud.md b/docs/modules/gcloud.md index 08d17f362f..7a58c52388 100644 --- a/docs/modules/gcloud.md +++ b/docs/modules/gcloud.md @@ -65,7 +65,7 @@ It's important to set the `option.WithEndpoint()` option using the container's U [Obtaining a Firestore client](../../modules/gcloud/firestore_test.go) inside_block:firestoreClient -It's important to set the target string of the `grpc.Dial` method using the container's URI, as shown in the client example above. +It's important to set the target string of the `grpc.NewClient` method using the container's URI, as shown in the client example above. ### Pubsub @@ -74,7 +74,7 @@ It's important to set the target string of the `grpc.Dial` method using the cont [Obtaining a Pubsub client](../../modules/gcloud/pubsub_test.go) inside_block:pubsubClient -It's important to set the target string of the `grpc.Dial` method using the container's URI, as shown in the client example above. +It's important to set the target string of the `grpc.NewClient` method using the container's URI, as shown in the client example above. ### Spanner @@ -86,20 +86,28 @@ It's important to set the target string of the `grpc.Dial` method using the cont It's important to set the `option.WithEndpoint()` option using the container's URI, as shown in the Admin client example above. -## Module reference +## Module Reference -The GCloud module exposes one entrypoint function to create the different GCloud emulators, and each function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunXXXContainer(ctx, opts...)` functions are deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The GCloud module exposes one entrypoint function to create the different GCloud emulators, and each function receives three parameters: ```golang -func RunBigQueryContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*BigQueryContainer, error) -func RunBigTableContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*BigTableContainer, error) -func RunDatastoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DatastoreContainer, error) -func RunFirestoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*FirestoreContainer, error) -func RunPubsubContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*PubsubContainer, error) -func RunSpannerContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*SpannerContainer, error) +func RunBigQuery(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*BigQueryContainer, error) +func RunBigTable(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*BigTableContainer, error) +func RunDatastore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatastoreContainer, error) +func RunFirestore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*FirestoreContainer, error) +func RunPubsub(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*PubsubContainer, error) +func RunSpanner(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*SpannerContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -108,8 +116,8 @@ When starting any of the GCloud containers, you can pass options in a variadic w #### Image -If you need to set a different GCloud Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for GCloud. E.g. `testcontainers.WithImage("gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`. +If you need to set a different GCloud Docker image, you can set a valid Docker image as the second argument in the `RunXXX` function (`RunBigQuery, RunDatastore`, ...). +E.g. `RunXXX(context.Background(), "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/grafana-lgtm.md b/docs/modules/grafana-lgtm.md new file mode 100644 index 0000000000..b76c56bc66 --- /dev/null +++ b/docs/modules/grafana-lgtm.md @@ -0,0 +1,115 @@ +# Grafana LGTM + +Since testcontainers-go :material-tag: v0.33.0 + +## Introduction + +The Testcontainers module for Grafana LGTM. + +## Adding this module to your project dependencies + +Please run the following command to add the Grafana module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/grafanalgtm +``` + +## Usage example + + +[Creating a Grafana LGTM container](../../modules/grafana-lgtm/examples_test.go) inside_block:runGrafanaLGTMContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.33.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Grafana LGTM module exposes one entrypoint function to create the Grafana LGTM container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GrafanaLGTMContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Grafana LGTM container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Grafana LGTM Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "grafana/otel-lgtm:0.6.0")`. + +#### Admin Credentials + +- Since testcontainers-go :material-tag: v0.33.0 + +If you need to set different admin credentials in the Grafana LGTM container, you can set them using the `WithAdminCredentials(user, password)` option. + +{% include "../features/common_functional_options.md" %} + +### Container Methods + +The Grafana LGTM container exposes the following methods: + +!!!info + All the endpoint methods return their endpoints in the format `:`, so please use them accordingly to configure your client. + +#### Grafana Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `HttpEndpoint(ctx)` method returns the HTTP endpoint to connect to Grafana, using the default `3000` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +#### Loki Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `LokiEndpoint(ctx)` method returns the HTTP endpoint to connect to Loki, using the default `3100` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +#### Tempo Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `TempoEndpoint(ctx)` method returns the HTTP endpoint to connect to Tempo, using the default `3200` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +#### Otel HTTP Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `OtelHTTPEndpoint(ctx)` method returns the endpoint to connect to Otel using HTTP, using the default `4318` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +#### Otel gRPC Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `OtelGRPCEndpoint(ctx)` method returns the endpoint to connect to Otel using gRPC, using the default `4317` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +#### Prometheus Endpoint + +- Since testcontainers-go :material-tag: v0.33.0 + +The `PrometheusHttpEndpoint(ctx)` method returns the endpoint to connect to Prometheus, using the default `9090` port. The same method with the `Must` prefix returns just the endpoing, and panics if an error occurs. + +## Examples + +### Traces, Logs and Prometheus metrics for a simple Go process + +In this example, a simple application is created to generate traces, logs, and Prometheus metrics. +The application sends data to Grafana LGTM, and the Otel SDK is used to send the data. +The example demonstrates how to set up the Otel SDK and run the Grafana LGTM module, +configuring the Otel library to send data to Grafana LGTM thanks to the endpoints provided by the Grafana LGTM container. + + +[App sending Otel data](../../modules/grafana-lgtm/examples_test.go) inside_block:rollDiceApp +[Setup Otel SDK](../../modules/grafana-lgtm/examples_test.go) inside_block:setupOTelSDK +[Run the Grafana LGTM container](../../modules/grafana-lgtm/examples_test.go) inside_block:ExampleRun_otelCollector + \ No newline at end of file diff --git a/docs/modules/inbucket.md b/docs/modules/inbucket.md index eb94a0b918..c377e6d536 100644 --- a/docs/modules/inbucket.md +++ b/docs/modules/inbucket.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/inbucket [Creating a Inbucket container](../../modules/inbucket/examples_test.go) inside_block:runInbucketContainer -## Module reference +## Module Reference -The Inbucket module exposes one entrypoint function to create the Inbucket container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Inbucket module exposes one entrypoint function to create the Inbucket container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*InbucketContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*InbucketContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Inbucket container, you can pass options in a variadic way to #### Image -If you need to set a different Inbucket Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Inbucket. E.g. `testcontainers.WithImage("inbucket/inbucket:sha-2d409bb")`. +If you need to set a different Inbucket Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "inbucket/inbucket:sha-2d409bb")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/index.md b/docs/modules/index.md index b279765e9d..8db41723fa 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -46,8 +46,8 @@ We have provided a command line tool to generate the scaffolding for the code of - a Go module for the example, including: - go.mod and go.sum files, including the current version of _Testcontainer for Go_. - a Go package named after the module, in lowercase - - a Go file for the creation of the container, using a dedicated struct in which the image flag is set as Docker image. - - a Go test file for running a simple test for your container, consuming the above struct. + - a Go file for the creation of the container. + - a Go test file for running a simple test for your container, consuming the above struct and using the image flag as Docker image for the container. - a Go examples file for running the example in the docs site, also adding them to [https://pkg.go.dev](https://pkg.go.dev). - a Makefile to run the tests in a consistent manner - a markdown file in the docs/modules directory including the snippets for both the creation of the container and a simple test. By default, this generated file will contain all the documentation for the module, including: @@ -72,7 +72,7 @@ We have provided a command line tool to generate the scaffolding for the code of | Flag | Short | Type | Required | Description | |---------|-------|--------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------| | --name | -n | string | Yes | Name of the module, use camel-case when needed. Only alphanumerical characters are allowed (leading character must be a letter). | -| --image | -i | string | Yes | Fully-qualified name of the Docker image to be used by the module (i.e. 'docker.io/org/project:tag') | +| --image | -i | string | Yes | Fully-qualified name of the Docker image to be used in the examples and tests (i.e. 'org/project:tag') | | --title | -t | string | No | A variant of the name supporting mixed casing (i.e. 'MongoDB'). Only alphanumerical characters are allowed (leading character must be a letter). | @@ -101,10 +101,10 @@ go run . new module --name ${NAME_OF_YOUR_MODULE} --image "${REGISTRY}/${MODULE} We are going to propose a set of steps to follow when adding types and methods to the module: !!!warning - The `StartContainer` function will be eventually deprecated and replaced with `RunContainer`. We are keeping it in certain modules for backwards compatibility, but they will be removed in the future. + The `StartContainer` function will be eventually deprecated and replaced with `Run`. We are keeping it in certain modules for backwards compatibility, but they will be removed in the future. - Make sure a public `Container` type exists for the module. This type has to use composition to embed the `testcontainers.Container` type, promoting all the methods from it. -- Make sure a `RunContainer` function exists and is public. This function is the entrypoint to the module and will define the initial values for a `testcontainers.GenericContainerRequest` struct, including the image, the default exposed ports, wait strategies, etc. Therefore, the function must initialise the container request with the default values. +- Make sure a `Run` function exists and is public. This function is the entrypoint to the module and will define the initial values for a `testcontainers.GenericContainerRequest` struct, including the image in the function signature, the default exposed ports, wait strategies, etc. Therefore, the function must initialise the container request with the default values. - Define container options for the module leveraging the `testcontainers.ContainerCustomizer` interface, that has one single method: `Customize(req *GenericContainerRequest) error`. !!!warning @@ -123,7 +123,7 @@ We are going to propose a set of steps to follow when adding types and methods t - We consider that a best practice for the options is define a function using the `With` prefix, that returns a function returning a modified `testcontainers.GenericContainerRequest` type. For that, the library already provides a `testcontainers.CustomizeRequestOption` type implementing the `ContainerCustomizer` interface, and we encourage you to use this type for creating your own customizer functions. - At the same time, you could need to create your own container customizers for your module. Make sure they implement the `testcontainers.ContainerCustomizer` interface. Defining your own customizer functions is useful when you need to transfer a certain state that is not present at the `ContainerRequest` for the container, possibly using an intermediate Config struct. -- The options will be passed to the `RunContainer` function as variadic arguments after the Go context, and they will be processed right after defining the initial `testcontainers.GenericContainerRequest` struct using a for loop. +- The options will be passed to the `Run` function as variadic arguments after the Go context, and they will be processed right after defining the initial `testcontainers.GenericContainerRequest` struct using a for loop. ```golang // Config type represents an intermediate struct for transferring state from the options to the container @@ -131,12 +131,12 @@ type Config struct { data string } -// RunContainer function is the entrypoint to the module -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) { +// Run function is the entrypoint to the module +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { cfg := Config{} req := testcontainers.ContainerRequest{ - Image: "my-image", + Image: img, ... } genericContainerReq := testcontainers.GenericContainerRequest{ @@ -191,7 +191,6 @@ func (c *Container) ConnectionString(ctx context.Context) (string, error) {...} In order to simplify the creation of the container for a given module, `Testcontainers for Go` provides a set of `testcontainers.CustomizeRequestOption` functions to customize the container request for the module. These options are: -- `testcontainers.WithImage`: a function that sets the image for the container request. - `testcontainers.WithImageSubstitutors`: a function that sets your own substitutions to the container images. - `testcontainers.WithEnv`: a function that sets the environment variables for the container request. - `testcontainers.WithHostPortAccess`: a function that enables the container to access a port that is already running in the host. diff --git a/docs/modules/influxdb.md b/docs/modules/influxdb.md index c3075c97c9..6f58321a06 100644 --- a/docs/modules/influxdb.md +++ b/docs/modules/influxdb.md @@ -22,13 +22,21 @@ go get github.com/testcontainers/testcontainers-go/modules/influxdb ## Module Reference -The InfluxDB module exposes one entrypoint function to create the container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The InfluxDB module exposes one entrypoint function to create the container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*InfluxDbContainer, error) {} +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*InfluxDbContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -42,10 +50,11 @@ When starting the container, you can pass options in a variadic way to configure #### Image -To use a different Docker image, you can use the `testcontainers.WithImage` option to specify the -image, E.g. `testcontainers.WithImage("influxdb:1.8.0")`. By default, the 1.8.10 image is used. Note that -`influxdb:latest` will get you a version 2 image which is not supported by this module. +To use a different Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "influxdb:1.8.0")`. +!!!info + Note that `influxdb:latest` will get you a version 2 image which is not supported by this module. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/k3s.md b/docs/modules/k3s.md index c6edd0d255..617c1d4ffe 100644 --- a/docs/modules/k3s.md +++ b/docs/modules/k3s.md @@ -20,18 +20,25 @@ go get github.com/testcontainers/testcontainers-go/modules/k3s [Test for a K3s container](../../modules/k3s/k3s_test.go) inside_block:runK3sContainer -## Module reference +## Module Reference -The K3s module exposes one entrypoint function to create the K3s container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The K3s module exposes one entrypoint function to create the K3s container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*K3sContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*K3sContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. - ### Container Ports These are the ports used by the K3s container: @@ -44,8 +51,8 @@ When starting the K3s container, you can pass options in a variadic way to confi #### Image -If you need to set a different K3s Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for K3s. E.g. `testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1")`. +If you need to set a different K3s Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "rancher/k3s:v1.27.1-k3s1")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/k6.md b/docs/modules/k6.md index ce81d31c1c..723c87c948 100644 --- a/docs/modules/k6.md +++ b/docs/modules/k6.md @@ -27,15 +27,23 @@ go get github.com/testcontainers/testcontainers-go/modules/k6 [Creating a K6 container](../../modules/k6/examples_test.go) inside_block:runK6Container -## Module reference +## Module Reference -The K6 module exposes one entrypoint function to run the K6 container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The K6 module exposes one entrypoint function to run the K6 container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*K6Container, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*K6Container, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -47,8 +55,8 @@ When starting the K6 container, you can pass options in a variadic way to config !!! warning The K6 module uses a `k6x` image to build a `k6` binary with all the required extensions. Therefore, only the [szkiba/k6x](https://hub.docker.com/r/szkiba/k6x) image should be used with this module. -If you need to set a different K6 Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for k6x. E.g. `testcontainers.WithImage("szkiba/k6x:v0.3.1")`. +If you need to set a different K6 Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "szkiba/k6x:v0.3.1")`. {% include "../features/common_functional_options.md" %} @@ -57,7 +65,7 @@ for k6x. E.g. `testcontainers.WithImage("szkiba/k6x:v0.3.1")`. `SetEnvVar` sets an [environment variable](https://k6.io/docs/using-k6/environment-variables/) for the test script using the '--env' command-line flag in the k6 command in the container. ```golang -k6.RunContainer(ctx, k6.SetEnvVar("URL","test.k6.io"), k6.WithTestScript("/tests/test.js")) +k6.New(ctx, k6.SetEnvVar("URL","test.k6.io"), k6.WithTestScript("/tests/test.js")) ``` #### WithCache @@ -70,7 +78,7 @@ By default, a new volume is created and automatically removed when the test sess This is convenient for example for CI/CD environments. In other cases, such as local testing, it can be convenient to reuse the same cache volume across test sessions.In this cases, the TC_K6_BUILD_CACHE environment variables can used to provide the name of a volume to be used and kept across test sessions. If this volume does not exist, it will be created. ```golang -k6.RunContainer(ctx, WithCache(), k6.WithTestScript("/tests/test.js")) +k6.New(ctx, WithCache(), k6.WithTestScript("/tests/test.js")) ``` #### WithCmdOptions @@ -78,7 +86,7 @@ k6.RunContainer(ctx, WithCache(), k6.WithTestScript("/tests/test.js")) Use `WithCmdOptions` to pass a variadic list of strings as [options](https://k6.io/docs/using-k6/k6-options/reference/) to the k6 run command ```golang -k6.RunContainer(ctx, WithCmdOptions("--vus=10", "--duration=30s"), k6.WithTestScript("/tests/test.js")) +k6.New(ctx, WithCmdOptions("--vus=10", "--duration=30s"), k6.WithTestScript("/tests/test.js")) ``` #### WithTestScript @@ -86,7 +94,7 @@ k6.RunContainer(ctx, WithCmdOptions("--vus=10", "--duration=30s"), k6.WithTestSc Use the `WithTestScript` option to specify the test script to run. The path to the script must be an absolute path. This option copies the script file to the container and pass it to k6's `run` command. At least one `WithTestScript` or `WithRemoteTestScript` option must be specified. ```golang -k6.RunContainer(ctx, k6.WithTestScript("/tests/test.js")) +k6.New(ctx, k6.WithTestScript("/tests/test.js")) ``` #### WithRemoteTestScript @@ -100,7 +108,7 @@ uri, _ := url.Parse(scriptUrl) desc := k6.DownloadableFile{Uri: *uri , DownloadDir: t.TempDir()} options := k6.WithRemoteTestScript(desc) -k6.RunContainer(ctx, k6.WithCache(), options) +k6.New(ctx, k6.WithCache(), options) ``` ### Container Methods diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md index 86bf846de9..d2f951b043 100644 --- a/docs/modules/kafka.md +++ b/docs/modules/kafka.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka [Creating a Kafka container](../../modules/kafka/examples_test.go) inside_block:runKafkaContainer -## Module reference +## Module Reference -The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Kafka container, you can pass options in a variadic way to con #### Image -If you need to set a different Kafka Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Kafka. E.g. `testcontainers.WithImage("confluentinc/confluent-local:7.5.0")`. +If you need to set a different Kafka Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "confluentinc/confluent-local:7.5.0")`. !!! warning The minimal required version of Kafka for KRaft mode is `confluentinc/confluent-local:7.4.0`. If you are using an image that diff --git a/docs/modules/localstack.md b/docs/modules/localstack.md index 206f40d82f..77cc522907 100644 --- a/docs/modules/localstack.md +++ b/docs/modules/localstack.md @@ -25,15 +25,23 @@ Running LocalStack as a stand-in for multiple AWS services during a test: Environment variables listed in [Localstack's README](https://github.com/localstack/localstack#configurations) may be used to customize Localstack's configuration. Use the `testcontainers.WithEnv` option when creating the `LocalStackContainer` to apply those variables. -## Module reference +## Module Reference -The LocalStack module exposes one single function to create the LocalStack container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The LocalStack module exposes one single function to create the LocalStack container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*LocalStackContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*LocalstackContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -42,7 +50,8 @@ When starting the Localstack container, you can pass options in a variadic way t #### Image -By default, the image used is `localstack:1.4.0`. If you need to use a different image, you can use `testcontainers.WithImage` option. +If you need to set a different Localstack Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "localstack:1.4.0")`. {% include "../features/common_functional_options.md" %} @@ -54,7 +63,7 @@ It's possible to entirely override the default LocalStack container request: [Customize container request](../../modules/localstack/examples_test.go) inside_block:withCustomContainerRequest -With simply passing the `testcontainers.CustomizeRequest` functional option to the `RunContainer` function, you'll be able to configure the LocalStack container with your own needs, as this new container request will be merged with the original one. +With simply passing the `testcontainers.CustomizeRequest` functional option to the `New` function, you'll be able to configure the LocalStack container with your own needs, as this new container request will be merged with the original one. In the above example you can check how it's possible to copy files that are needed by the tests. The `flagsFn` function is a helper function that converts Docker labels used by Ryuk to a string with the format requested by LocalStack. @@ -94,6 +103,7 @@ For further reference on the SDK v1, please check out the AWS docs [here](https: ### Using the AWS SDK v2 +[EndpointResolver](../../modules/localstack/v2/s3_test.go) inside_block:awsResolverV2 [AWS SDK v2](../../modules/localstack/v2/s3_test.go) inside_block:awsSDKClientV2 diff --git a/docs/modules/mariadb.md b/docs/modules/mariadb.md index bb2eb63de6..082c9bd430 100644 --- a/docs/modules/mariadb.md +++ b/docs/modules/mariadb.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/mariadb [Creating a MariaDB container](../../modules/mariadb/examples_test.go) inside_block:runMariaDBContainer -## Module reference +## Module Reference -The MariaDB module exposes one entrypoint function to create the MariaDB container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The MariaDB module exposes one entrypoint function to create the MariaDB container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MariaDBContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MariaDBContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -41,8 +49,8 @@ When starting the MariaDB container, you can pass options in a variadic way to c #### Image -If you need to set a different MariaDB Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for MariaDB. E.g. `testcontainers.WithImage("mariadb:11.0.3")`. +If you need to set a different MariaDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mariadb:11.0.3")`. !!!info From MariaDB [docs](https://github.com/docker-library/docs/tree/master/mariadb#environment-variables): diff --git a/docs/modules/meilisearch.md b/docs/modules/meilisearch.md new file mode 100644 index 0000000000..d8fe865be0 --- /dev/null +++ b/docs/modules/meilisearch.md @@ -0,0 +1,77 @@ +# Meilisearch + +Since testcontainers-go :material-tag: v0.34.0 + +## Introduction + +The Testcontainers module for Meilisearch. + +## Adding this module to your project dependencies + +Please run the following command to add the Meilisearch module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/meilisearch +``` + +## Usage example + + +[Creating a Meilisearch container](../../modules/meilisearch/examples_test.go) inside_block:runMeilisearchContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.34.0 + +The Meilisearch module exposes one entrypoint function to create the Meilisearch container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MeilisearchContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Meilisearch container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Meilisearch Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "getmeili/meilisearch:v1.10.3")`. + +{% include "../features/common_functional_options.md" %} + +#### Master Key + +- Since testcontainers-go :material-tag: v0.34.0 + +If you need to set a master key, you can use the `WithMasterKey(key string)` option. Otherwise, the default will be used which is `just-a-master-key-for-test`, which is exported on the container fields. + +#### Dump Data + +- Since testcontainers-go :material-tag: v0.34.0 + +If you need to dump data in Meilisearch upon initialization for testing, you can use `WithDumpData(filepath string)` option where `filepath` can be an absolute path or relative path to a `dump` file. Please refer to the official Meilisearch documentation about dump files [here](https://www.meilisearch.com/docs/learn/advanced/snapshots_vs_dumps#dumps). + +### Container Methods + +The Meilisearch container exposes the following methods: + +#### Address + +- Since testcontainers-go :material-tag: v0.34.0 + +The `Address` method retrieves the address of the Meilisearch container. +It will use http as protocol, as TLS is not supported at the moment. + +#### MasterKey + +- Since testcontainers-go :material-tag: v0.34.0 + +The `MasterKey` method retrieves the master key of the Meilisearch container. diff --git a/docs/modules/milvus.md b/docs/modules/milvus.md index 4051316ff8..17e226b0c7 100644 --- a/docs/modules/milvus.md +++ b/docs/modules/milvus.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/milvus [Creating a Milvus container](../../modules/milvus/examples_test.go) inside_block:runMilvusContainer -## Module reference +## Module Reference -The Milvus module exposes one entrypoint function to create the Milvus container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Milvus module exposes one entrypoint function to create the Milvus container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MilvusContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MilvusContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Milvus container, you can pass options in a variadic way to co #### Image -If you need to set a different Milvus Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Milvus. E.g. `testcontainers.WithImage("milvusdb/milvus:v2.3.9")`. +If you need to set a different Milvus Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "milvusdb/milvus:v2.3.9")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/minio.md b/docs/modules/minio.md index 141aae23fe..d54bf3df31 100644 --- a/docs/modules/minio.md +++ b/docs/modules/minio.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/minio [Creating a Minio container](../../modules/minio/examples_test.go) inside_block:runMinioContainer -## Module reference +## Module Reference -The Minio module exposes one entrypoint function to create the Minio container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Minio module exposes one entrypoint function to create the Minio container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Minio container, you can pass options in a variadic way to con #### Image -If you need to set a different Minio Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Minio. E.g. `testcontainers.WithImage("minio/minio:RELEASE.2024-01-16T16-07-38Z")`. +If you need to set a different Minio Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "minio/minio:RELEASE.2024-01-16T16-07-38Z")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/mockserver.md b/docs/modules/mockserver.md index 4ebc2bd732..6e3fca77de 100644 --- a/docs/modules/mockserver.md +++ b/docs/modules/mockserver.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/mockserver [Creating a MockServer container](../../modules/mockserver/examples_test.go) inside_block:runMockServerContainer -## Module reference +## Module Reference -The MockServer module exposes one entrypoint function to create the MockServer container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The MockServer module exposes one entrypoint function to create the MockServer container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MockServerContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MockServerContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the MockServer container, you can pass options in a variadic way t #### Image -If you need to set a different MockServer Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for MockServer. E.g. `testcontainers.WithImage("mockserver/mockserver:5.15.0")`. +If you need to set a different MockServer Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mockserver/mockserver:5.15.0")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md index 31cb5dc38e..3af4ae952c 100644 --- a/docs/modules/mongodb.md +++ b/docs/modules/mongodb.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/mongodb [Creating a MongoDB container](../../modules/mongodb/examples_test.go) inside_block:runMongoDBContainer -## Module reference +## Module Reference -The MongoDB module exposes one entrypoint function to create the MongoDB container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The MongoDB module exposes one entrypoint function to create the MongoDB container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MongoDBContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MongoDBContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the MongoDB container, you can pass options in a variadic way to c #### Image -If you need to set a different MongoDB Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for MongoDB. E.g. `testcontainers.WithImage("mongo:6")`. +If you need to set a different MongoDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mongo:6")`. #### WithUsername @@ -58,6 +66,12 @@ It is used in conjunction with `WithUsername` to set a username and its password E.g. `testcontainers.WithPassword("mymongopwd")`. +#### WithReplicaSet + +- Since testcontainers-go :material-tag: v0.31.0 + +The `WithReplicaSet` functional option configures the container to run a single-node MongoDB replica set named `rs`. The MongoDB container will wait until the replica set is ready. + {% include "../features/common_functional_options.md" %} ### Container Methods diff --git a/docs/modules/mssql.md b/docs/modules/mssql.md index 80e48d48b1..51a63d417b 100644 --- a/docs/modules/mssql.md +++ b/docs/modules/mssql.md @@ -25,15 +25,23 @@ go get github.com/testcontainers/testcontainers-go/modules/mssql Please see the [`microsoft-mssql-server` image documentation](https://hub.docker.com/_/microsoft-mssql-server#environment-variables) for a link to the EULA document. -## Module reference +## Module Reference -The MS SQL Server module exposes one entrypoint function to create the MS SQL Server container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The MS SQL Server module exposes one entrypoint function to create the MS SQL Server container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MSSQLServerContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MSSQLServerContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -42,8 +50,8 @@ When starting the MS SQL Server container, you can pass options in a variadic wa #### Image -If you need to set a different MS SQL Server Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for MS SQL Server. E.g. `testcontainers.WithImage("mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04")`. +If you need to set a different MS SQL Server Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04")`. #### End User License Agreement diff --git a/docs/modules/mysql.md b/docs/modules/mysql.md index 5fbaa1bd49..5ef38d56da 100644 --- a/docs/modules/mysql.md +++ b/docs/modules/mysql.md @@ -22,13 +22,21 @@ go get github.com/testcontainers/testcontainers-go/modules/mysql ## Module Reference -The MySQL module exposes one entrypoint function to create the container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The MySQL module exposes one entrypoint function to create the container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MySQLContainer, error) { +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MySQLContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -41,14 +49,8 @@ When starting the MySQL container, you can pass options in a variadic way to con #### Image -If you need to set a different MySQL Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for MySQL. E.g. `testcontainers.WithImage("mysql:8.0.36")`. - -By default, the container will use the following Docker image: - - -[Default Docker image](../../modules/mysql/mysql.go) inside_block:defaultImage - +If you need to set a different MySQL Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "mysql:8.0.36")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/nats.md b/docs/modules/nats.md index 9d561bd39b..8a47c962fd 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/nats [Creating a NATS container](../../modules/nats/examples_test.go) inside_block:runNATSContainer -## Module reference +## Module Reference -The NATS module exposes one entrypoint function to create the NATS container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The NATS module exposes one entrypoint function to create the NATS container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*NATSContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*NATSContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,13 +45,15 @@ When starting the NATS container, you can pass options in a variadic way to conf #### Image -If you need to set a different NATS Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for NATS. E.g. `testcontainers.WithImage("nats:2.9")`. +If you need to set a different NATS Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "nats:2.9")`. {% include "../features/common_functional_options.md" %} #### Set username and password +- Since testcontainers-go :material-tag: v0.24.0 + If you need to set different credentials, you can use `WithUsername` and `WithPassword` options. By default, the username, the password are not set. To establish the connection with the NATS container: @@ -51,15 +61,54 @@ options. By default, the username, the password are not set. To establish the co [Connect using the credentials](../../modules/nats/examples_test.go) inside_block:natsConnect +#### Cmd Arguments + +- Since testcontainers-go :material-tag: v0.24.0 + +It's possible to pass extra arguments to the NATS container using the `testcontainers.WithArgument` option. E.g. `nats.WithArgument("cluster_name", "c1")`. +These arguments are passed to the NATS server when it starts, as part of the command line arguments of the entrypoint. + +!!! note + Arguments do not need to be prefixed with `--`: the NATS container will add them automatically. + + +[Passing arguments](../../modules/nats/examples_test.go) inside_block:withArguments + + +#### Custom configuration file + +- Not available until the next release of testcontainers-go :material-tag: main + +It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The content of `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. + +!!! note + Changing the connectivity (listen address or ports) can break the container setup. So configuration must be done with care. + ### Container Methods The NATS container exposes the following methods: #### ConnectionString +- Since testcontainers-go :material-tag: v0.24.0 + This method returns the connection string to connect to the NATS container, using the default `4222` port. It's possible to pass extra parameters to the connection string, in a variadic way. [Get connection string](../../modules/nats/nats_test.go) inside_block:connectionString + +#### MustConnectionString + +- Since testcontainers-go :material-tag: v0.30.0 + +Exactly like `ConnectionString`, but it panics if an error occurs, returning just a string. + +## Examples + +### NATS Cluster + + +[NATS Cluster](../../modules/nats/examples_test.go) inside_block:cluster + diff --git a/docs/modules/neo4j.md b/docs/modules/neo4j.md index f06e475860..e0188e015d 100644 --- a/docs/modules/neo4j.md +++ b/docs/modules/neo4j.md @@ -24,13 +24,21 @@ Running Neo4j as a single-instance server, with the [APOC plugin](https://neo4j. ## Module Reference -The Neo4j module exposes one entrypoint function to create the Neo4j container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Neo4j module exposes one entrypoint function to create the Neo4j container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Neo4jContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Neo4jContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Ports @@ -47,14 +55,8 @@ When starting the Neo4j container, you can pass options in a variadic way to con #### Image -If you need to set a different Neo4j Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Neo4j. E.g. `testcontainers.WithImage("docker.io/neo4j:4.4")`. - -By default, the container will use the following Docker image: - - -[Default Docker image](../../modules/neo4j/neo4j.go) inside_block:defaultImage - +If you need to set a different Neo4j Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "neo4j:4.4")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/ollama.md b/docs/modules/ollama.md index 19cde849c3..c16e612142 100644 --- a/docs/modules/ollama.md +++ b/docs/modules/ollama.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/ollama [Creating a Ollama container](../../modules/ollama/examples_test.go) inside_block:runOllamaContainer -## Module reference +## Module Reference -The Ollama module exposes one entrypoint function to create the Ollama container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Ollama module exposes one entrypoint function to create the Ollama container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OllamaContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OllamaContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Ollama container, you can pass options in a variadic way to co #### Image -If you need to set a different Ollama Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Ollama. E.g. `testcontainers.WithImage("ollama/ollama:0.1.25")`. +If you need to set a different Ollama Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "ollama/ollama:0.1.25")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/openfga.md b/docs/modules/openfga.md index 459be0bfa7..a7766ccc0c 100644 --- a/docs/modules/openfga.md +++ b/docs/modules/openfga.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/openfga [Creating a OpenFGA container](../../modules/openfga/examples_test.go) inside_block:runOpenFGAContainer -## Module reference +## Module Reference -The OpenFGA module exposes one entrypoint function to create the OpenFGA container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The OpenFGA module exposes one entrypoint function to create the OpenFGA container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenFGAContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenFGAContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the OpenFGA container, you can pass options in a variadic way to c #### Image -If you need to set a different OpenFGA Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for OpenFGA. E.g. `testcontainers.WithImage("openfga/openfga:v1.5.0")`. +If you need to set a different OpenFGA Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "openfga/openfga:v1.5.0")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/openldap.md b/docs/modules/openldap.md index b7311a61d0..0a5a7b7204 100644 --- a/docs/modules/openldap.md +++ b/docs/modules/openldap.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/openldap [Creating a OpenLDAP container](../../modules/openldap/examples_test.go) inside_block:runOpenLDAPContainer -## Module reference +## Module Reference -The OpenLDAP module exposes one entrypoint function to create the OpenLDAP container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The OpenLDAP module exposes one entrypoint function to create the OpenLDAP container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenLDAPContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenLDAPContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the OpenLDAP container, you can pass options in a variadic way to #### Image -If you need to set a different OpenLDAP Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for OpenLDAP. E.g. `testcontainers.WithImage("bitnami/openldap:2.6.6")`. +If you need to set a different OpenLDAP Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "bitnami/openldap:2.6.6")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/opensearch.md b/docs/modules/opensearch.md index 2046532db8..7edc0c3a70 100644 --- a/docs/modules/opensearch.md +++ b/docs/modules/opensearch.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/opensearch [Creating a OpenSearch container](../../modules/opensearch/examples_test.go) inside_block:runOpenSearchContainer -## Module reference +## Module Reference -The OpenSearch module exposes one entrypoint function to create the OpenSearch container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The OpenSearch module exposes one entrypoint function to create the OpenSearch container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenSearchContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenSearchContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the OpenSearch container, you can pass options in a variadic way t #### Image -If you need to set a different OpenSearch Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for OpenSearch. E.g. `testcontainers.WithImage("opensearchproject/opensearch:2.11.1")`. +If you need to set a different OpenSearch Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "opensearchproject/opensearch:2.11.1")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md index ebd1ed919e..930de50c15 100644 --- a/docs/modules/postgres.md +++ b/docs/modules/postgres.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/postgres [Creating a Postgres container](../../modules/postgres/examples_test.go) inside_block:runPostgresContainer -## Module reference +## Module Reference -The Postgres module exposes one entrypoint function to create the Postgres container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Postgres module exposes one entrypoint function to create the Postgres container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -40,8 +48,8 @@ When starting the Postgres container, you can pass options in a variadic way to #### Image -If you need to set a different Postgres Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Postgres. E.g. `testcontainers.WithImage("docker.io/postgres:16-alpine")`. +If you need to set a different Postgres Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "postgres:16-alpine")`. {% include "../features/common_functional_options.md" %} @@ -92,6 +100,15 @@ It's possible to use the Postgres container with PGVector, Timescale or Postgis, ## Examples +### Wait Strategies + +The postgres module works best with these wait strategies. +No default is supplied, so you need to set it explicitly. + + +[Example Wait Strategies](../../modules/postgres/wait_strategies.go) inside_block:waitStrategy + + ### Using Snapshots This example shows the usage of the postgres module's Snapshot feature to give each test a clean database without having to recreate the database container on every test or run heavy scripts to clean your database. This makes the individual @@ -102,7 +119,35 @@ tests very modular, since they always run on a brand-new database. The Snapshot logic requires dropping the connected database and using the system database to run commands, which will not work if the database for the container is set to `"postgres"`. - [Test with a reusable Postgres container](../../modules/postgres/postgres_test.go) inside_block:snapshotAndReset + +### Snapshot/Restore with custom driver + +- Since testcontainers-go :material-tag: v0.32.0 + +The snapshot/restore feature tries to use the `postgres` driver with go's included `sql.DB` package to perform database operations. +If the `postgres` driver is not installed, it will fall back to using `docker exec`, which works, but is slower. + +You can tell the module to use the database driver you have imported in your test package by setting `postgres.WithSQLDriver("name")` to your driver name. + +For example, if you use pgx, see the example below. + +```go +package my_test + +import ( + "testing" + + _ "github.com/jackc/pgx/v5/stdlib" + + "github.com/testcontainers/testcontainers-go/modules/postgres" +) +``` + +The above code snippet is importing the `pgx` driver and the _Testcontainers for Go_ Postgres module. + + +[Snapshot/Restore with custom driver](../../modules/postgres/postgres_test.go) inside_block:snapshotAndReset + \ No newline at end of file diff --git a/docs/modules/pulsar.md b/docs/modules/pulsar.md index f4d93621e4..99f93b2222 100644 --- a/docs/modules/pulsar.md +++ b/docs/modules/pulsar.md @@ -28,13 +28,21 @@ Create a `Pulsar` container to use it in your tests: ## Module Reference -The Pulsar module exposes one entrypoint function to create the containerr, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Pulsar module exposes one entrypoint function to create the containerr, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -43,8 +51,8 @@ When starting the Pulsar container, you can pass options in a variadic way to co #### Image -If you need to set a different Pulsar Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Pulsar. E.g. `testcontainers.WithImage("docker.io/apachepulsar/pulsar:2.10.2")`. +If you need to set a different Pulsar Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "apachepulsar/pulsar:2.10.2")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/qdrant.md b/docs/modules/qdrant.md index 3d75c35433..77bc9a2413 100644 --- a/docs/modules/qdrant.md +++ b/docs/modules/qdrant.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/qdrant [Creating a Qdrant container](../../modules/qdrant/examples_test.go) inside_block:runQdrantContainer -## Module reference +## Module Reference -The Qdrant module exposes one entrypoint function to create the Qdrant container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Qdrant module exposes one entrypoint function to create the Qdrant container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*QdrantContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*QdrantContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Qdrant container, you can pass options in a variadic way to co #### Image -If you need to set a different Qdrant Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Qdrant. E.g. `testcontainers.WithImage("qdrant/qdrant:v1.7.4")`. +If you need to set a different Qdrant Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "qdrant/qdrant:v1.7.4")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/rabbitmq.md b/docs/modules/rabbitmq.md index 4dcc201035..5b77aaf5ee 100644 --- a/docs/modules/rabbitmq.md +++ b/docs/modules/rabbitmq.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/rabbitmq [Creating a RabbitMQ container](../../modules/rabbitmq/examples_test.go) inside_block:runRabbitMQContainer -## Module reference +## Module Reference -The RabbitMQ module exposes one entrypoint function to create the RabbitMQ container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The RabbitMQ module exposes one entrypoint function to create the RabbitMQ container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RabbitMQContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RabbitMQContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the RabbitMQ container, you can pass options in a variadic way to #### Image -If you need to set a different RabbitMQ Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for RabbitMQ. E.g. `testcontainers.WithImage("rabbitmq:3.7.25-management-alpine")`. +If you need to set a different RabbitMQ Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "rabbitmq:3.7.25-management-alpine")`. !!!warning From [https://hub.docker.com/_/rabbitmq](https://hub.docker.com/_/rabbitmq): "As of RabbitMQ 3.9, all of the docker-specific variables listed below are deprecated and no longer used. Please use a configuration file instead; visit [rabbitmq.com/configure](https://rabbitmq.com/configure) to learn more about the configuration file. For a starting point, the 3.8 images will print out the config file it generated from supplied environment variables." diff --git a/docs/modules/redis.md b/docs/modules/redis.md index 50c5a3025f..e6bbd90851 100644 --- a/docs/modules/redis.md +++ b/docs/modules/redis.md @@ -22,13 +22,21 @@ go get github.com/testcontainers/testcontainers-go/modules/redis ## Module Reference -The Redis module exposes one entrypoint function to create the containerr, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Redis module exposes one entrypoint function to create the containerr, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RedisContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RedisContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -40,8 +48,8 @@ When starting the Redis container, you can pass options in a variadic way to con #### Image -If you need to set a different Redis Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Redis. E.g. `testcontainers.WithImage("docker.io/redis:7")`. +If you need to set a different Redis Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "redis:7")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/redpanda.md b/docs/modules/redpanda.md index ad2223ce83..028dbaf95f 100644 --- a/docs/modules/redpanda.md +++ b/docs/modules/redpanda.md @@ -25,15 +25,23 @@ go get github.com/testcontainers/testcontainers-go/modules/redpanda [Creating a Redpanda container](../../modules/redpanda/examples_test.go) inside_block:runRedpandaContainer -## Module reference +## Module Reference -The Redpanda module exposes one entrypoint function to create the Redpanda container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Redpanda module exposes one entrypoint function to create the Redpanda container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RedpandaContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RedpandaContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -42,8 +50,8 @@ When starting the Redpanda container, you can pass options in a variadic way to #### Image -If you need to set a different Redpanda Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Redpanda. E.g. `testcontainers.WithImage("docker.redpanda.com/redpandadata/redpanda:v23.1.7")`. +If you need to set a different Redpanda Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "docker.redpanda.com/redpandadata/redpanda:v23.1.7")`. {% include "../features/common_functional_options.md" %} @@ -53,6 +61,8 @@ If you need to enable TLS use `WithTLS` with a valid PEM encoded certificate and #### Additional Listener +- Since testcontainers-go :material-tag: v0.28.0 + There are scenarios where additional listeners are needed, for example if you want to consume/from another container in the same network @@ -71,12 +81,77 @@ Produce messages using the new registered listener [Produce/consume via registered listener](../../modules/redpanda/redpanda_test.go) inside_block:withListenerExec +#### Adding Service Accounts + +- Since testcontainers-go :material-tag: v0.20.0 + +It's possible to add service accounts to the Redpanda container using the `WithNewServiceAccount` option, setting the service account name and its password. +E.g. `WithNewServiceAccount("service-account", "password")`. + +#### Adding Super Users + +- Since testcontainers-go :material-tag: v0.20.0 + +When a super user is needed, you can use the `WithSuperusers` option, passing a variadic list of super users. +E.g. `WithSuperusers("superuser-1", "superuser-2")`. + +#### Enabling SASL + +- Since testcontainers-go :material-tag: v0.20.0 + +The `WithEnableSASL()` option enables SASL scram sha authentication. By default, no authentication (plaintext) is used. +When setting an authentication method, make sure to add users as well and authorize them using the `WithSuperusers()` option. + +#### WithEnableKafkaAuthorization + +- Since testcontainers-go :material-tag: v0.20.0 + +The `WithEnableKafkaAuthorization` enables authorization for connections on the Kafka API. + +#### WithEnableWasmTransform + +- Since testcontainers-go :material-tag: v0.28.0 + +The `WithEnableWasmTransform` enables wasm transform. + +!!!warning + Should not be used with RP versions before 23.3 + +#### WithEnableSchemaRegistryHTTPBasicAuth + +- Since testcontainers-go :material-tag: v0.20.0 + +The `WithEnableSchemaRegistryHTTPBasicAuth` enables HTTP basic authentication for the Schema Registry. + +#### WithAutoCreateTopics + +- Since testcontainers-go :material-tag: v0.22.0 + +The `WithAutoCreateTopics` option enables the auto-creation of topics. + +#### WithTLS + +- Since testcontainers-go :material-tag: v0.24.0 + +The `WithTLS` option enables TLS encryption. It requires a valid PEM encoded certificate and key, passed as byte slices. +E.g. `WithTLS([]byte(cert), []byte(key))`. + +#### WithBootstrapConfig + +- Since testcontainers-go :material-tag: v0.33.0 + +`WithBootstrapConfig` adds an arbitrary config key-value pair to the Redpanda container. Per the name, this config will be interpolated into the generated bootstrap +config file, which is particularly useful for configs requiring a restart when otherwise applied to a running Redpanda instance. +E.g. `WithBootstrapConfig("config_key", config_value)`, where `config_value` is of type `any`. + ### Container Methods The Redpanda container exposes the following methods: #### KafkaSeedBroker +- Since testcontainers-go :material-tag: v0.20.0 + KafkaSeedBroker returns the seed broker that should be used for connecting to the Kafka API with your Kafka client. It'll be returned in the format: "host:port" - for example: "localhost:55687". @@ -87,6 +162,8 @@ to the Kafka API with your Kafka client. It'll be returned in the format: #### SchemaRegistryAddress +- Since testcontainers-go :material-tag: v0.20.0 + SchemaRegistryAddress returns the address to the schema registry API. This is an HTTP-based API and thus the returned format will be: http://host:port. @@ -97,6 +174,8 @@ is an HTTP-based API and thus the returned format will be: http://host:port. #### AdminAPIAddress +- Since testcontainers-go :material-tag: v0.20.0 + AdminAPIAddress returns the address to the Redpanda Admin API. This is an HTTP-based API and thus the returned format will be: http://host:port. diff --git a/docs/modules/registry.md b/docs/modules/registry.md index bc54f27007..94e7f4dc84 100644 --- a/docs/modules/registry.md +++ b/docs/modules/registry.md @@ -20,25 +20,44 @@ go get github.com/testcontainers/testcontainers-go/modules/registry [Creating a Registry container](../../modules/registry/examples_test.go) inside_block:runRegistryContainer -## Module reference +## Module Reference -The Registry module exposes one entrypoint function to create the Registry container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Registry module exposes one entrypoint function to create the Registry container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RegistryContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RegistryContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. +### Docker Auth Config + +The module exposes a way to set the Docker Auth Config for the Registry container, thanks to the `SetDockerAuthConfig` function. +This is useful when you need to pull images from a private registry. It basically sets the `DOCKER_AUTH_CONFIG` environment variable +with authentication for the given host, username and password sets. It returns a function to reset the environment back to the previous state, +which is helpful when you need to reset the environment after a test. + +On the same hand, the module also exposes a way to build a Docker Auth Config for the Registry container, thanks to the `DockerAuthConfig` helper function. +This function returns a map of `AuthConfigs` including base64 encoded Auth field for the provided details. +It also accepts additional host, username and password triples to add more auth configurations. + ### Container Options When starting the Registry container, you can pass options in a variadic way to configure it. #### Image -If you need to set a different Registry Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Registry. E.g. `testcontainers.WithImage("registry:2.8.3")`. +If you need to set a different Registry Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "registry:2.8.3")`. {% include "../features/common_functional_options.md" %} @@ -72,10 +91,15 @@ Otherwise, the Registry will start but you won't be able to read any images from The Registry container exposes the following methods: +#### HostAddress + +This method returns the returns the host address including port of the Distribution Registry. +E.g. `localhost:32878`. + #### Address This method returns the HTTP address string to connect to the Distribution Registry, so that you can use to connect to the Registry. -E.g. `http://localhost:32878/v2/_catalog`. +E.g. `http://localhost:32878`. [HTTP Address](../../modules/registry/registry_test.go) inside_block:httpAddress diff --git a/docs/modules/surrealdb.md b/docs/modules/surrealdb.md index 9d026c0986..67659b8e19 100644 --- a/docs/modules/surrealdb.md +++ b/docs/modules/surrealdb.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/surrealdb [Creating a SurrealDB container](../../modules/surrealdb/examples_test.go) inside_block:runSurrealDBContainer -## Module reference +## Module Reference -The SurrealDB module exposes one entrypoint function to create the SurrealDB container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The SurrealDB module exposes one entrypoint function to create the SurrealDB container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*SurrealDBContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*SurrealDBContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the SurrealDB container, you can pass options in a variadic way to #### Image -If you need to set a different SurrealDB Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for SurrealDB. E.g. `testcontainers.WithImage("surrealdb/surrealdb:v1.1.1")`. +If you need to set a different SurrealDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "surrealdb/surrealdb:v1.1.1")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/valkey.md b/docs/modules/valkey.md new file mode 100644 index 0000000000..40b124f242 --- /dev/null +++ b/docs/modules/valkey.md @@ -0,0 +1,81 @@ +# Valkey + +Since testcontainers-go :material-tag: v0.33.0 + +## Introduction + +The Testcontainers module for Valkey. + +## Adding this module to your project dependencies + +Please run the following command to add the Valkey module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/valkey +``` + +## Usage example + + +[Creating a Valkey container](../../modules/valkey/examples_test.go) inside_block:runValkeyContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.33.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Valkey module exposes one entrypoint function to create the Valkey container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ValkeyContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Valkey container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Valkey Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "valkey/valkey:7.2.5")`. + +{% include "../features/common_functional_options.md" %} + +#### Snapshotting + +- Since testcontainers-go :material-tag: v0.33.0 + +By default Valkey saves snapshots of the dataset on disk, in a binary file called dump.rdb. You can configure Valkey to have it save the dataset every `N` seconds if there are at least `M` changes in the dataset. E.g. `WithSnapshotting(10, 1)`. + +#### Log Level + +- Since testcontainers-go :material-tag: v0.33.0 + +You can easily set the valkey logging level. E.g. `WithLogLevel(LogLevelDebug)`. + +#### Valkey configuration + +In the case you have a custom config file for Valkey, it's possible to copy that file into the container before it's started. E.g. `WithConfigFile(filepath.Join("testdata", "valkey.conf"))`. + +### Container Methods + +The Valkey container exposes the following methods: + +#### ConnectionString + +- Since testcontainers-go :material-tag: v0.33.0 + +This method returns the connection string to connect to the Valkey container, using the default `6379` port. + + +[Get connection string](../../modules/valkey/valkey_test.go) inside_block:connectionString + \ No newline at end of file diff --git a/docs/modules/vault.md b/docs/modules/vault.md index 722e3459e7..aadcbdcb5e 100644 --- a/docs/modules/vault.md +++ b/docs/modules/vault.md @@ -15,7 +15,7 @@ go get github.com/testcontainers/testcontainers-go/modules/vault ``` ## Usage example -The **RunContainer** function is the main entry point to create a new VaultContainer instance. +The **RunWithImage** function is the main entry point to create a new VaultContainer instance. It takes a context and zero or more Option values to configure the container. @@ -27,7 +27,7 @@ It takes a context and zero or more Option values to configure the container. [Use CLI to read data](../../modules/vault/vault_test.go) inside_block:containerCliRead -The `vaultContainer` is the container instance obtained from `RunContainer`. +The `vaultContainer` is the container instance obtained from `RunWithImage`. ### Use HTTP API to read data from Vault container: @@ -48,13 +48,21 @@ go get -u github.com/hashicorp/vault-client-go ## Module Reference -The Vault module exposes one entrypoint function to create the containerr, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Vault module exposes one entrypoint function to create the containerr, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*VaultContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*VaultContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -63,15 +71,8 @@ When starting the Vault container, you can pass options in a variadic way to con #### Image -If you need to set a different Vault Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Vault. E.g. `testcontainers.WithImage("hashicorp/vault:1.13.0")`. - -!!!info - Default image name is `hashicorp/vault:1.13.0`. - - -[Set image name](../../modules/vault/vault_test.go) inside_block:WithImageName - +If you need to set a different Vault Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "hashicorp/vault:1.13.0")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/vearch.md b/docs/modules/vearch.md new file mode 100644 index 0000000000..17baabbe46 --- /dev/null +++ b/docs/modules/vearch.md @@ -0,0 +1,65 @@ +# Vearch + +Since testcontainers-go :material-tag: v0.32.0 + +## Introduction + +The Testcontainers module for Vearch. + +## Adding this module to your project dependencies + +Please run the following command to add the Vearch module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/vearch +``` + +## Usage example + + +[Creating a Vearch container](../../modules/vearch/examples_test.go) inside_block:runVearchContainer + + +## Module Reference + +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Vearch module exposes one entrypoint function to create the Vearch container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*VearchContainer, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the Vearch container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different Vearch Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "vearch/vearch:3.5.1")`. + +{% include "../features/common_functional_options.md" %} + +### Container Methods + +The Vearch container exposes the following methods: + +#### REST Endpoint + +This method returns the REST endpoint of the Vearch container, using the default `9001` port. + + +[Get REST endpoint](../../modules/vearch/vearch_test.go) inside_block:restEndpoint + + + diff --git a/docs/modules/weaviate.md b/docs/modules/weaviate.md index a36c03ce8f..333cceb7cd 100644 --- a/docs/modules/weaviate.md +++ b/docs/modules/weaviate.md @@ -20,15 +20,23 @@ go get github.com/testcontainers/testcontainers-go/modules/weaviate [Creating a Weaviate container](../../modules/weaviate/examples_test.go) inside_block:runWeaviateContainer -## Module reference +## Module Reference -The Weaviate module exposes one entrypoint function to create the Weaviate container, and this function receives two parameters: +### Run function + +- Since testcontainers-go :material-tag: v0.32.0 + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The Weaviate module exposes one entrypoint function to create the Weaviate container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*WeaviateContainer, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*WeaviteContainer, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the Weaviate container, you can pass options in a variadic way to #### Image -If you need to set a different Weaviate Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for Weaviate. E.g. `testcontainers.WithImage("semitechnologies/weaviate:1.23.9")`. +If you need to set a different Weaviate Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "semitechnologies/weaviate:1.23.9")`. {% include "../features/common_functional_options.md" %} diff --git a/docs/modules/yugabytedb.md b/docs/modules/yugabytedb.md new file mode 100644 index 0000000000..9922c48574 --- /dev/null +++ b/docs/modules/yugabytedb.md @@ -0,0 +1,94 @@ +# YugabyteDB + +Since testcontainers-go :material-tag: v0.34.0 + +## Introduction + +The Testcontainers module for yugabyteDB. + +## Adding this module to your project dependencies + +Please run the following command to add the yugabyteDB module to your Go dependencies: + +``` +go get github.com/testcontainers/testcontainers-go/modules/yugabytedb +``` + +## Usage example + + +[Creating a yugabyteDB container](../../modules/yugabytedb/examples_test.go) inside_block:runyugabyteDBContainer + + +## Module Reference + +### Run function + +The yugabyteDB module exposes one entrypoint function to create the yugabyteDB container, and this function receives three parameters: + +```golang +func Run( + ctx context.Context, + img string, + opts ...testcontainers.ContainerCustomizer, +) (*Container, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + +### Container Options + +When starting the yugabyteDB container, you can pass options in a variadic way to configure it. + +#### Image + +If you need to set a different yugabyteDB Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "yugabytedb/yugabyte")`. + +{% include "../features/common_functional_options.md" %} + +#### Initial Database + +By default the yugabyteDB container will start with a database named `yugabyte` and the default credentials `yugabyte` and `yugabyte`. + +If you need to set a different database, and its credentials, you can use the `WithDatabaseName(dbName string)`, `WithDatabaseUser(dbUser string)` and `WithDatabasePassword(dbPassword string)` options. + +#### Initial Cluster Configuration + +By default the yugabyteDB container will start with a cluster keyspace named `yugabyte` and the default credentials `yugabyte` and `yugabyte`. + +If you need to set a different cluster keyspace, and its credentials, you can use the `WithKeyspace(keyspace string)`, `WithUser(user string)` and `WithPassword(password string)` options. + +### Container Methods + +The yugabyteDB container exposes the following methods: + +#### YSQLConnectionString + +This method returns the connection string for the yugabyteDB container when using +the YSQL query language. +The connection string can then be used to connect to the yugabyteDB container using +a standard PostgreSQL client. + + +[Create a postgres client using the connection string](../../modules/yugabytedb/examples_test.go) block:ExampleContainer_YSQLConnectionString + + +### Usage examples + +#### Usage with YSQL and gocql + +To use the YCQL query language, you need to configure the cluster +with the keyspace, user, and password. + +By default, the yugabyteDB container will start with a cluster keyspace named `yugabyte` and the default credentials `yugabyte` and `yugabyte` but you can change it using the `WithKeyspace`, `WithUser` and `WithPassword` options. + +In order to get the appropriate host and port to connect to the yugabyteDB container, +you can use the `GetHost` and `GetMappedPort` methods on the Container struct. +See the examples below: + + +[Create a yugabyteDB client using the cluster configuration](../../modules/yugabytedb/yugabytedb_test.go) block:TestYugabyteDB_YCQL + \ No newline at end of file diff --git a/docs/quickstart.md b/docs/quickstart.md index 822b7edd84..ed6bbfcd4a 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -9,7 +9,7 @@ Please read the [system requirements](../system_requirements/) page before you s ## 2. Install _Testcontainers for Go_ -We use [gomod](https://blog.golang.org/using-go-modules) and you can get it installed via: +We use [go mod](https://blog.golang.org/using-go-modules) and you can get it installed via: ``` go get github.com/testcontainers/testcontainers-go @@ -20,6 +20,9 @@ go get github.com/testcontainers/testcontainers-go ```go import ( "context" + "testing" + + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -36,14 +39,8 @@ func TestWithRedis(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("Could not start redis: %s", err) - } - defer func() { - if err := redisC.Terminate(ctx); err != nil { - log.Fatalf("Could not stop redis: %s", err) - } - }() + testcontainers.CleanupContainer(t, redisC) + require.NoError(t, err) } ``` @@ -74,7 +71,8 @@ start, leaving to you the decision about when to start it. All the containers must be removed at some point, otherwise they will run until the host is overloaded. One of the ways we have to clean up is by deferring the -terminated function: `defer redisC.Terminate(ctx)`. +terminated function: `defer testcontainers.TerminateContainer(redisC)` which +automatically handles nil container so is safe to use even in the error case. !!!tip diff --git a/docs/system_requirements/ci/aws_codebuild.md b/docs/system_requirements/ci/aws_codebuild.md index 1320e6bdba..a64d3691e0 100644 --- a/docs/system_requirements/ci/aws_codebuild.md +++ b/docs/system_requirements/ci/aws_codebuild.md @@ -11,7 +11,7 @@ version: 0.2 phases: install: runtime-versions: - golang: 1.21 + golang: 1.22 build: commands: - go test ./... diff --git a/docs/system_requirements/ci/circle_ci.md b/docs/system_requirements/ci/circle_ci.md index 1abdec0462..f453c35df3 100644 --- a/docs/system_requirements/ci/circle_ci.md +++ b/docs/system_requirements/ci/circle_ci.md @@ -6,18 +6,59 @@ Your CircleCI configuration should use a dedicated VM for Testcontainers to work. You can achieve this by specifying the executor type in your `.circleci/config.yml` to be `machine` instead of the default `docker` executor (see [Choosing an Executor Type](https://circleci.com/docs/executor-intro/) for more info). -Here is a sample CircleCI configuration that does a checkout of a project and runs Maven: +Here is a sample CircleCI configuration that does a checkout of a project and runs `go test` for a project. Go is installed for the `tests` job using [`gvm`](https://github.com/andrewkroh/gvm), and a workflow matrix has been defined to run the job with different Go versions. Go steps are finally executed from the `go` orb. ```yml +version: 2.1 + +orbs: + go: circleci/go@1.11.0 + +executors: + machine_executor_amd64: + machine: + image: ubuntu-2204:2024.01.2 + environment: + architecture: "amd64" + platform: "linux/amd64" + jobs: - build: - # Check https://circleci.com/docs/executor-intro#linux-vm for more details - machine: true - image: ubuntu-2204:2023.04.2 + tests: + executor: machine_executor_amd64 + parameters: + go-version: + type: string steps: - # install Go 1.x - # checkout the project - - run: go test./... + - run: + name: Install GVM + command: | + mkdir ~/gvmbin + curl -sL -o ~/gvmbin/gvm https://github.com/andrewkroh/gvm/releases/download/v0.5.2/gvm-linux-amd64 + chmod +x ~/gvmbin/gvm + echo 'export PATH=$PATH:~/gvmbin' >> "$BASH_ENV" + - run: + name: Install Go + command: | + eval "$(gvm << parameters.go-version >>)" + echo 'eval "$(gvm << parameters.go-version >>)"' >> "$BASH_ENV" + go version + - checkout # checkout source code + - go/load-cache # Load cached Go modules. + - go/mod-download # Run 'go mod download'. + - go/save-cache # Save Go modules to cache. + - go/test: # Runs 'go test ./...' but includes extensive parameterization for finer tuning. + covermode: atomic + failfast: true + race: true + +workflows: + build-and-test: + jobs: + - tests: + matrix: + parameters: + go-version: ["1.21.7", "1.22.3"] + ``` You can learn more about the best practices of using Testcontainers together with CircleCI in [this article](https://www.atomicjar.com/2022/12/testcontainers-with-circleci/) for Java. diff --git a/docs/system_requirements/ci/concourse_ci.md b/docs/system_requirements/ci/concourse_ci.md index 2d711d3b73..ede2aac2ae 100644 --- a/docs/system_requirements/ci/concourse_ci.md +++ b/docs/system_requirements/ci/concourse_ci.md @@ -36,7 +36,7 @@ jobs: start_docker cd repo - docker run -it --rm -v "$PWD:$PWD" -w "$PWD" -v /var/run/docker.sock:/var/run/docker.sock golang:1.21 go test ./... + docker run -it --rm -v "$PWD:$PWD" -w "$PWD" -v /var/run/docker.sock:/var/run/docker.sock golang:1.22 go test ./... ``` Finally, you can use Concourse's [fly CLI](https://concourse-ci.org/fly.html) to set the pipeline and trigger the job: diff --git a/docs/system_requirements/ci/dind_patterns.md b/docs/system_requirements/ci/dind_patterns.md index 853a174d32..267a4baaf5 100644 --- a/docs/system_requirements/ci/dind_patterns.md +++ b/docs/system_requirements/ci/dind_patterns.md @@ -24,7 +24,7 @@ $ tree . └── platform └── integration_test.go -$ docker run -it --rm -v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock golang:1.21 go test ./... -v +$ docker run -it --rm -v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock golang:1.22 go test ./... -v ``` Where: @@ -45,7 +45,7 @@ The same can be achieved with Docker Compose: ```yaml tests: - image: golang:1.21 + image: golang:1.22 stop_signal: SIGKILL stdin_open: true tty: true diff --git a/docs/system_requirements/ci/gitlab_ci.md b/docs/system_requirements/ci/gitlab_ci.md index 6289dc59ae..679292a010 100644 --- a/docs/system_requirements/ci/gitlab_ci.md +++ b/docs/system_requirements/ci/gitlab_ci.md @@ -57,7 +57,7 @@ variables: DOCKER_DRIVER: overlay2 test: - image: golang:1.21 + image: golang:1.22 stage: test script: go test ./... -v ``` diff --git a/docs/system_requirements/ci/tekton.md b/docs/system_requirements/ci/tekton.md index a8280a0802..28f23f77fb 100644 --- a/docs/system_requirements/ci/tekton.md +++ b/docs/system_requirements/ci/tekton.md @@ -16,7 +16,7 @@ spec: - name: source steps: - name: read - image: golang:1.21 + image: golang:1.22 workingDir: $(workspaces.source.path) script: go test ./... -v volumeMounts: diff --git a/docs/system_requirements/ci/travis.md b/docs/system_requirements/ci/travis.md index 0701d2e367..bfc81c303d 100644 --- a/docs/system_requirements/ci/travis.md +++ b/docs/system_requirements/ci/travis.md @@ -7,7 +7,7 @@ is the minimal required config. language: go go: - 1.x -- "1.21" +- "1.22" services: - docker diff --git a/docs/system_requirements/using_colima.md b/docs/system_requirements/using_colima.md index a040cfaf32..6cb853adde 100644 --- a/docs/system_requirements/using_colima.md +++ b/docs/system_requirements/using_colima.md @@ -26,13 +26,21 @@ default Current DOCKER_HOST based configuration unix:///var/run/docker.sock If you're using an older version of Colima or have other applications that are unaware of Docker context the following workaround is available: -1. Locate your Docker Socket, see: [Colima's FAQ - Docker Socket Location](https://github.com/abiosoft/colima/blob/main/docs/FAQ.md#docker-socket-location) +- Locate your Docker Socket, see: [Colima's FAQ - Docker Socket Location](https://github.com/abiosoft/colima/blob/main/docs/FAQ.md#docker-socket-location) -2. Set the `DOCKER_HOST` environment variable to match the located Docker Socket +- Create a symbolic link from the default Docker Socket to the expected location, and restart Colima with the `--network-address` flag. + +``` + sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock + colima stop + colima start --network-address +``` + +- Set the `DOCKER_HOST` environment variable to match the located Docker Socket * Example: `export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"` -3. As of testcontainers-go v0.14.0 set `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` +- As of testcontainers-go v0.14.0 set `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` to `/var/run/docker.sock` as the default value refers to your `DOCKER_HOST` environment variable. diff --git a/docs/system_requirements/using_podman.md b/docs/system_requirements/using_podman.md index fae9731a65..4143306901 100644 --- a/docs/system_requirements/using_podman.md +++ b/docs/system_requirements/using_podman.md @@ -3,7 +3,7 @@ _Testcontainers for Go_ supports the use of Podman (rootless or rootful) instead of Docker. In most scenarios no special setup is required in _Testcontainers for Go_. -_Testcontainers for Go_ will automatically discover the socket based on the `DOCKER_HOST` or the `TC_HOST` environment variables. +_Testcontainers for Go_ will automatically discover the socket based on the `DOCKER_HOST` environment variables. Alternatively you can configure the host with a `.testcontainers.properties` file. The discovered Docker host is taken into account when starting a reaper container. The discovered socket is used to detect the use of Podman. @@ -27,7 +27,7 @@ func TestSomething(t *testing.T) { req := tc.GenericContainerRequest{ ProviderType: tc.ProviderPodman, ContainerRequest: tc.ContainerRequest{ - Image: "docker.io/nginx:alpine" + Image: "nginx:alpine" }, } diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html index 057ec4ae5e..64460698df 100644 --- a/docs/theme/partials/header.html +++ b/docs/theme/partials/header.html @@ -124,9 +124,10 @@ {% endif %} - {% if "search" in config.plugins %} + {% if "material/search" in config.plugins %} diff --git a/docs/theme/partials/nav.html b/docs/theme/partials/nav.html index acf6c9565e..2072d2ff9b 100644 --- a/docs/theme/partials/nav.html +++ b/docs/theme/partials/nav.html @@ -18,6 +18,7 @@ --> +{% import "partials/nav-item.html" as item with context %} {% set class = "md-nav md-nav--primary" %} {% if "navigation.tabs" in features %} {% set class = class ~ " md-nav--lifted" %} @@ -45,8 +46,7 @@
    {% for nav_item in nav %} {% set path = "__nav_" ~ loop.index %} - {% set level = 1 %} - {% include "partials/nav-item.html" %} + {{ item.render(nav_item, path, 1) }} {% endfor %}
  • diff --git a/examples/Makefile b/examples/Makefile index 1df43cc412..d6a6ea09fd 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -10,4 +10,4 @@ lint-examples: .PHONY: tidy-examples tidy-examples: - @find . -type f -name Makefile -execdir make tools-tidy \; + @find . -type f -name Makefile -execdir make tidy \; diff --git a/examples/nginx/go.mod b/examples/nginx/go.mod index d60be577c2..a31fbd366e 100644 --- a/examples/nginx/go.mod +++ b/examples/nginx/go.mod @@ -1,22 +1,26 @@ module github.com/testcontainers/testcontainers-go/examples/nginx -go 1.21 +go 1.22 -require github.com/testcontainers/testcontainers-go v0.30.0 +require ( + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) replace github.com/testcontainers/testcontainers-go => ../.. require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -24,11 +28,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -37,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -48,11 +54,10 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/examples/nginx/go.sum b/examples/nginx/go.sum index d8ba86a7c1..447eec4038 100644 --- a/examples/nginx/go.sum +++ b/examples/nginx/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -89,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -122,23 +131,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -149,38 +154,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/examples/nginx/nginx.go b/examples/nginx/nginx.go index fd0e530ea6..6a5718c3e0 100644 --- a/examples/nginx/nginx.go +++ b/examples/nginx/nginx.go @@ -24,21 +24,24 @@ func startContainer(ctx context.Context) (*nginxContainer, error) { ContainerRequest: req, Started: true, }) + var nginxC *nginxContainer + if container != nil { + nginxC = &nginxContainer{Container: container} + } if err != nil { - return nil, err + return nginxC, err } ip, err := container.Host(ctx) if err != nil { - return nil, err + return nginxC, err } mappedPort, err := container.MappedPort(ctx, "80") if err != nil { - return nil, err + return nginxC, err } - uri := fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) - - return &nginxContainer{Container: container, URI: uri}, nil + nginxC.URI = fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) + return nginxC, nil } diff --git a/examples/nginx/nginx_test.go b/examples/nginx/nginx_test.go index 3d7b8ada48..fe662daf07 100644 --- a/examples/nginx/nginx_test.go +++ b/examples/nginx/nginx_test.go @@ -4,6 +4,10 @@ import ( "context" "net/http" "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" ) func TestIntegrationNginxLatestReturn(t *testing.T) { @@ -14,23 +18,10 @@ func TestIntegrationNginxLatestReturn(t *testing.T) { ctx := context.Background() nginxC, err := startContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := nginxC.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) resp, err := http.Get(nginxC.URI) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) } diff --git a/examples/toxiproxy/go.mod b/examples/toxiproxy/go.mod index a3c57204ba..644c7a9a0d 100644 --- a/examples/toxiproxy/go.mod +++ b/examples/toxiproxy/go.mod @@ -1,27 +1,29 @@ module github.com/testcontainers/testcontainers-go/examples/toxiproxy -go 1.21 +go 1.22 require ( github.com/Shopify/toxiproxy/v2 v2.8.0 github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.6.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // 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.2.0 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -29,10 +31,11 @@ require ( 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/golang/protobuf v1.5.4 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -41,6 +44,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -52,13 +56,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/examples/toxiproxy/go.sum b/examples/toxiproxy/go.sum index 29348d3b09..dbbd37fcaf 100644 --- a/examples/toxiproxy/go.sum +++ b/examples/toxiproxy/go.sum @@ -4,22 +4,23 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Shopify/toxiproxy/v2 v2.8.0 h1:d7OzvAc0Rco3QO8jVsDSfadQ1up0Ca42hK+EGEpnQWs= github.com/Shopify/toxiproxy/v2 v2.8.0/go.mod h1:k0V84e/dLQmVNuI6S0G7TpXCl611OSRYdptoxm0XTzA= 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -27,10 +28,10 @@ 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.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -50,8 +51,6 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -62,12 +61,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -94,6 +99,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -105,6 +112,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -138,23 +147,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -165,36 +170,35 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -202,5 +206,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/examples/toxiproxy/redis.go b/examples/toxiproxy/redis.go index ead526773d..c66e52550f 100644 --- a/examples/toxiproxy/redis.go +++ b/examples/toxiproxy/redis.go @@ -27,9 +27,9 @@ func setupRedis(ctx context.Context, network string, networkAlias []string) (*re ContainerRequest: req, Started: true, }) - if err != nil { - return nil, err + var nginxC *redisContainer + if container != nil { + nginxC = &redisContainer{Container: container} } - - return &redisContainer{Container: container}, nil + return nginxC, err } diff --git a/examples/toxiproxy/toxiproxy.go b/examples/toxiproxy/toxiproxy.go index e7903a9f99..1a226e8c61 100644 --- a/examples/toxiproxy/toxiproxy.go +++ b/examples/toxiproxy/toxiproxy.go @@ -31,21 +31,25 @@ func startContainer(ctx context.Context, network string, networkAlias []string) ContainerRequest: req, Started: true, }) + var toxiC *toxiproxyContainer + if container != nil { + toxiC = &toxiproxyContainer{Container: container} + } if err != nil { - return nil, err + return toxiC, err } mappedPort, err := container.MappedPort(ctx, "8474") if err != nil { - return nil, err + return toxiC, err } hostIP, err := container.Host(ctx) if err != nil { - return nil, err + return toxiC, err } - uri := fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) + toxiC.URI = fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) - return &toxiproxyContainer{Container: container, URI: uri}, nil + return toxiC, nil } diff --git a/examples/toxiproxy/toxiproxy_test.go b/examples/toxiproxy/toxiproxy_test.go index 5ec70ef899..c372d739b8 100644 --- a/examples/toxiproxy/toxiproxy_test.go +++ b/examples/toxiproxy/toxiproxy_test.go @@ -9,71 +9,47 @@ import ( toxiproxy "github.com/Shopify/toxiproxy/v2/client" "github.com/go-redis/redis/v8" "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/network" ) func TestToxiproxy(t *testing.T) { ctx := context.Background() - newNetwork, err := network.New(ctx, network.WithCheckDuplicate()) - if err != nil { - t.Fatal(err) - } + newNetwork, err := network.New(ctx) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name toxiproxyContainer, err := startContainer(ctx, networkName, []string{"toxiproxy"}) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, toxiproxyContainer) + require.NoError(t, err) redisContainer, err := setupRedis(ctx, networkName, []string{"redis"}) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := toxiproxyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - if err := newNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to terminate network: %s", err) - } - }) + testcontainers.CleanupContainer(t, redisContainer) + require.NoError(t, err) toxiproxyClient := toxiproxy.NewClient(toxiproxyContainer.URI) proxy, err := toxiproxyClient.CreateProxy("redis", "0.0.0.0:8666", "redis:6379") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) toxiproxyProxyPort, err := toxiproxyContainer.MappedPort(ctx, "8666") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) toxiproxyProxyHostIP, err := toxiproxyContainer.Host(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) redisUri := fmt.Sprintf("redis://%s:%s?read_timeout=2s", toxiproxyProxyHostIP, toxiproxyProxyPort.Port()) options, err := redis.ParseURL(redisUri) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) redisClient := redis.NewClient(options) + defer func() { - err := flushRedis(ctx, *redisClient) - if err != nil { - t.Fatal(err) - } + require.NoError(t, flushRedis(ctx, *redisClient)) }() // Set data @@ -81,28 +57,18 @@ func TestToxiproxy(t *testing.T) { value := "Cabbage Biscuits" ttl, _ := time.ParseDuration("2h") err = redisClient.Set(ctx, key, value, ttl).Err() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = proxy.AddToxic("latency_down", "latency", "downstream", 1.0, toxiproxy.Attributes{ "latency": 1000, "jitter": 100, }) - if err != nil { - return - } + require.NoError(t, err) // Get data savedValue, err := redisClient.Get(ctx, key).Result() - if err != nil { - t.Fatal(err) - } - - // perform assertions - if savedValue != value { - t.Fatalf("Expected value %s. Got %s.", savedValue, value) - } + require.NoError(t, err) + require.Equal(t, value, savedValue) } func flushRedis(ctx context.Context, client redis.Client) error { diff --git a/exec/processor.go b/exec/processor.go index 73e12d26c2..9c852fb5aa 100644 --- a/exec/processor.go +++ b/exec/processor.go @@ -2,15 +2,17 @@ package exec import ( "bytes" + "fmt" "io" + "sync" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/stdcopy" ) // ProcessOptions defines options applicable to the reader processor type ProcessOptions struct { - ExecConfig types.ExecConfig + ExecConfig container.ExecOptions Reader io.Reader } @@ -21,7 +23,7 @@ type ProcessOptions struct { // - attach stderr: true func NewProcessOptions(cmd []string) *ProcessOptions { return &ProcessOptions{ - ExecConfig: types.ExecConfig{ + ExecConfig: container.ExecOptions{ Cmd: cmd, Detach: false, AttachStdout: true, @@ -60,6 +62,43 @@ func WithEnv(env []string) ProcessOption { }) } +// safeBuffer is a goroutine safe buffer. +type safeBuffer struct { + mtx sync.Mutex + buf bytes.Buffer + err error +} + +// Error sets an error for the next read. +func (sb *safeBuffer) Error(err error) { + sb.mtx.Lock() + defer sb.mtx.Unlock() + + sb.err = err +} + +// Write writes p to the buffer. +// It is safe for concurrent use by multiple goroutines. +func (sb *safeBuffer) Write(p []byte) (n int, err error) { + sb.mtx.Lock() + defer sb.mtx.Unlock() + + return sb.buf.Write(p) +} + +// Read reads up to len(p) bytes into p from the buffer. +// It is safe for concurrent use by multiple goroutines. +func (sb *safeBuffer) Read(p []byte) (n int, err error) { + sb.mtx.Lock() + defer sb.mtx.Unlock() + + if sb.err != nil { + return 0, sb.err + } + + return sb.buf.Read(p) +} + // Multiplexed returns a [ProcessOption] that configures the command execution // to combine stdout and stderr into a single stream without Docker's multiplexing headers. func Multiplexed() ProcessOption { @@ -73,13 +112,14 @@ func Multiplexed() ProcessOption { done := make(chan struct{}) - var outBuff bytes.Buffer - var errBuff bytes.Buffer + var outBuff safeBuffer + var errBuff safeBuffer go func() { + defer close(done) if _, err := stdcopy.StdCopy(&outBuff, &errBuff, opts.Reader); err != nil { + outBuff.Error(fmt.Errorf("copying output: %w", err)) return } - close(done) }() <-done diff --git a/file_test.go b/file_test.go index e5e660dee7..edf12af3b3 100644 --- a/file_test.go +++ b/file_test.go @@ -6,9 +6,9 @@ import ( "archive/tar" "bytes" "compress/gzip" + "errors" "fmt" "io" - "log" "os" "path/filepath" "testing" @@ -38,7 +38,7 @@ func Test_IsDir(t *testing.T) { { filepath: "foobar.doc", expected: false, - err: fmt.Errorf("does not exist"), + err: errors.New("does not exist"), }, } @@ -79,34 +79,24 @@ func Test_TarDir(t *testing.T) { } buff, err := tarDir(src, 0o755) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) tmpDir := filepath.Join(t.TempDir(), "subfolder") err = untar(tmpDir, bytes.NewReader(buff.Bytes())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) srcFiles, err := os.ReadDir(src) - if err != nil { - log.Fatal(err) - } + require.NoError(t, err) for _, srcFile := range srcFiles { if srcFile.IsDir() { continue } srcBytes, err := os.ReadFile(filepath.Join(src, srcFile.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) untarBytes, err := os.ReadFile(filepath.Join(tmpDir, "testdata", srcFile.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.Equal(t, srcBytes, untarBytes) } }) @@ -115,28 +105,20 @@ func Test_TarDir(t *testing.T) { func Test_TarFile(t *testing.T) { b, err := os.ReadFile(filepath.Join(".", "testdata", "Dockerfile")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) buff, err := tarFile("Docker.file", func(tw io.Writer) error { _, err := tw.Write(b) return err }, int64(len(b)), 0o755) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) tmpDir := t.TempDir() err = untar(tmpDir, bytes.NewReader(buff.Bytes())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) untarBytes, err := os.ReadFile(filepath.Join(tmpDir, "Docker.file")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.Equal(t, b, untarBytes) } diff --git a/from_dockerfile_test.go b/from_dockerfile_test.go index 7576499a64..75f80537d2 100644 --- a/from_dockerfile_test.go +++ b/from_dockerfile_test.go @@ -5,20 +5,19 @@ import ( "fmt" "io" "log" + "path/filepath" "strings" "testing" "time" "github.com/docker/docker/api/types" - "github.com/stretchr/testify/assert" + "github.com/docker/docker/api/types/image" "github.com/stretchr/testify/require" ) func TestBuildImageFromDockerfile(t *testing.T) { provider, err := NewDockerProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer provider.Close() cli := provider.Client() @@ -36,27 +35,23 @@ func TestBuildImageFromDockerfile(t *testing.T) { // } }) require.NoError(t, err) - assert.Equal(t, "test-repo:test-tag", tag) + require.Equal(t, "test-repo:test-tag", tag) _, _, err = cli.ImageInspectWithRaw(ctx, tag) require.NoError(t, err) t.Cleanup(func() { - _, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{ + _, err := cli.ImageRemove(ctx, tag, image.RemoveOptions{ Force: true, PruneChildren: true, }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) } func TestBuildImageFromDockerfile_NoRepo(t *testing.T) { provider, err := NewDockerProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer provider.Close() cli := provider.Client() @@ -71,27 +66,45 @@ func TestBuildImageFromDockerfile_NoRepo(t *testing.T) { }, }) require.NoError(t, err) - assert.True(t, strings.HasPrefix(tag, "test-repo:")) + require.True(t, strings.HasPrefix(tag, "test-repo:")) _, _, err = cli.ImageInspectWithRaw(ctx, tag) require.NoError(t, err) t.Cleanup(func() { - _, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{ + _, err := cli.ImageRemove(ctx, tag, image.RemoveOptions{ Force: true, PruneChildren: true, }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) } +func TestBuildImageFromDockerfile_BuildError(t *testing.T) { + ctx := context.Background() + dockerClient, err := NewDockerClientWithOpts(ctx) + require.NoError(t, err) + + defer dockerClient.Close() + + req := ContainerRequest{ + FromDockerfile: FromDockerfile{ + Dockerfile: "error.Dockerfile", + Context: filepath.Join(".", "testdata"), + }, + } + ctr, err := GenericContainer(ctx, GenericContainerRequest{ + ProviderType: providerType, + ContainerRequest: req, + Started: true, + }) + CleanupContainer(t, ctr) + require.EqualError(t, err, `create container: build image: The command '/bin/sh -c exit 1' returned a non-zero code: 1`) +} + func TestBuildImageFromDockerfile_NoTag(t *testing.T) { provider, err := NewDockerProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer provider.Close() cli := provider.Client() @@ -106,19 +119,17 @@ func TestBuildImageFromDockerfile_NoTag(t *testing.T) { }, }) require.NoError(t, err) - assert.True(t, strings.HasSuffix(tag, ":test-tag")) + require.True(t, strings.HasSuffix(tag, ":test-tag")) _, _, err = cli.ImageInspectWithRaw(ctx, tag) require.NoError(t, err) t.Cleanup(func() { - _, err := cli.ImageRemove(ctx, tag, types.ImageRemoveOptions{ + _, err := cli.ImageRemove(ctx, tag, image.RemoveOptions{ Force: true, PruneChildren: true, }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) } @@ -129,10 +140,9 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { c, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = fmt.Sprintf("target%d", i) }, @@ -140,6 +150,7 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) r, err := c.Logs(ctx) @@ -147,12 +158,7 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { logs, err := io.ReadAll(r) require.NoError(t, err) - - assert.Equal(t, fmt.Sprintf("target%d\n\n", i), string(logs)) - - t.Cleanup(func() { - require.NoError(t, c.Terminate(ctx)) - }) + require.Equal(t, fmt.Sprintf("target%d\n\n", i), string(logs)) } } @@ -163,10 +169,9 @@ func ExampleGenericContainer_buildFromDockerfile() { c, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = "target2" }, @@ -175,18 +180,26 @@ func ExampleGenericContainer_buildFromDockerfile() { Started: true, }) // } + defer func() { + if err := TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err != nil { - log.Fatalf("failed to start container: %v", err) + log.Printf("failed to start container: %v", err) + return } r, err := c.Logs(ctx) if err != nil { - log.Fatalf("failed to get logs: %v", err) + log.Printf("failed to get logs: %v", err) + return } logs, err := io.ReadAll(r) if err != nil { - log.Fatalf("failed to read logs: %v", err) + log.Printf("failed to read logs: %v", err) + return } fmt.Println(string(logs)) @@ -199,13 +212,12 @@ func TestBuildImageFromDockerfile_TargetDoesNotExist(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - _, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = "target-foo" }, @@ -213,5 +225,6 @@ func TestBuildImageFromDockerfile_TargetDoesNotExist(t *testing.T) { }, Started: true, }) + CleanupContainer(t, ctr) require.Error(t, err) } diff --git a/generate.go b/generate.go new file mode 100644 index 0000000000..19ae49695d --- /dev/null +++ b/generate.go @@ -0,0 +1,3 @@ +package testcontainers + +//go:generate mockery diff --git a/generic.go b/generic.go index 65fec35c88..fd13a607de 100644 --- a/generic.go +++ b/generic.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "sync" "github.com/testcontainers/testcontainers-go/internal/core" @@ -57,7 +58,7 @@ func GenericContainer(ctx context.Context, req GenericContainerRequest) (Contain } provider, err := req.ProviderType.GetProvider(WithLogger(logging)) if err != nil { - return nil, err + return nil, fmt.Errorf("get provider: %w", err) } defer provider.Close() @@ -74,12 +75,20 @@ func GenericContainer(ctx context.Context, req GenericContainerRequest) (Contain } if err != nil { // At this point `c` might not be nil. Give the caller an opportunity to call Destroy on the container. - return c, fmt.Errorf("%w: failed to create container", err) + // TODO: Remove this debugging. + if strings.Contains(err.Error(), "toomanyrequests") { + // Debugging information for rate limiting. + cfg, err := getDockerConfig() + if err == nil { + fmt.Printf("XXX: too many requests: %+v", cfg) + } + } + return c, fmt.Errorf("create container: %w", err) } if req.Started && !c.IsRunning() { if err := c.Start(ctx); err != nil { - return c, fmt.Errorf("failed to start container: %w", err) + return c, fmt.Errorf("start container: %w", err) } } return c, nil @@ -92,7 +101,17 @@ type GenericProvider interface { ImageProvider } -// GenericLabels returns a map of labels that can be used to identify containers created by this library +// GenericLabels returns a map of labels that can be used to identify resources +// created by this library. This includes the standard LabelSessionID if the +// reaper is enabled, otherwise this is excluded to prevent resources being +// incorrectly reaped. func GenericLabels() map[string]string { return core.DefaultLabels(core.SessionID()) } + +// AddGenericLabels adds the generic labels to target. +func AddGenericLabels(target map[string]string) { + for k, v := range GenericLabels() { + target[k] = v + } +} diff --git a/generic_test.go b/generic_test.go index 72688876ec..7c0de2a246 100644 --- a/generic_test.go +++ b/generic_test.go @@ -3,14 +3,14 @@ package testcontainers import ( "context" "errors" - "net/http" "os" "os/exec" - "strings" "sync" "testing" "time" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/wait" @@ -23,6 +23,8 @@ const ( func TestGenericReusableContainer(t *testing.T) { ctx := context.Background() + reusableContainerName := reusableContainerName + "_" + time.Now().Format("20060102150405") + n1, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ @@ -35,7 +37,7 @@ func TestGenericReusableContainer(t *testing.T) { }) require.NoError(t, err) require.True(t, n1.IsRunning()) - terminateContainerOnEnd(t, ctx, n1) + CleanupContainer(t, n1) copiedFileName := "hello_copy.sh" err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700) @@ -120,7 +122,7 @@ func TestGenericContainerShouldReturnRefOnError(t *testing.T) { }) require.Error(t, err) require.NotNil(t, c) - terminateContainerOnEnd(t, context.Background(), c) + CleanupContainer(t, c) } func TestGenericReusableContainerInSubprocess(t *testing.T) { @@ -133,18 +135,42 @@ func TestGenericReusableContainerInSubprocess(t *testing.T) { // create containers in subprocesses, as "go test ./..." does. output := createReuseContainerInSubprocess(t) + t.Log(output) // check is reuse container with WaitingFor work correctly. - require.True(t, strings.Contains(output, "🚧 Waiting for container id")) - require.True(t, strings.Contains(output, "🔔 Container is ready")) + require.Contains(t, output, "âŗ Waiting for container id") + require.Contains(t, output, "🔔 Container is ready") }() } wg.Wait() + + cli, err := NewDockerClientWithOpts(context.Background()) + require.NoError(t, err) + + f := filters.NewArgs(filters.KeyValuePair{Key: "name", Value: reusableContainerName}) + + ctrs, err := cli.ContainerList(context.Background(), container.ListOptions{ + All: true, + Filters: f, + }) + require.NoError(t, err) + require.Len(t, ctrs, 1) + + provider, err := NewDockerProvider() + require.NoError(t, err) + + provider.SetClient(cli) + + nginxC, err := provider.ContainerFromType(context.Background(), ctrs[0]) + CleanupContainer(t, nginxC) + require.NoError(t, err) } func createReuseContainerInSubprocess(t *testing.T) string { - cmd := exec.Command(os.Args[0], "-test.run=TestHelperContainerStarterProcess") - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + t.Helper() + // force verbosity in subprocesses, so that the output is printed + cmd := exec.Command(os.Args[0], "-test.run=TestHelperContainerStarterProcess", "-test.v=true") + cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") output, err := cmd.CombinedOutput() require.NoError(t, err, string(output)) @@ -174,18 +200,4 @@ func TestHelperContainerStarterProcess(t *testing.T) { }) require.NoError(t, err) require.True(t, nginxC.IsRunning()) - - origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") - require.NoError(t, err) - - // check is reuse container with WaitingFor work correctly. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, origin, nil) - require.NoError(t, err) - req.Close = true - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - require.Equal(t, http.StatusOK, resp.StatusCode) } diff --git a/go.mod b/go.mod index cc8f2d6cc6..78b747b76e 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,14 @@ module github.com/testcontainers/testcontainers-go -go 1.21 +go 1.22 require ( dario.cat/mergo v1.0.0 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/containerd/containerd v1.7.15 - github.com/cpuguy83/dockercfg v0.3.1 - github.com/docker/docker v25.0.5+incompatible + github.com/containerd/platforms v0.2.1 + github.com/cpuguy83/dockercfg v0.3.2 + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/docker/go-units v0.5.0 github.com/google/uuid v1.6.0 github.com/magiconair/properties v1.8.7 github.com/moby/patternmatcher v0.6.0 @@ -17,26 +16,27 @@ require ( github.com/opencontainers/image-spec v1.1.0 github.com/shirou/gopsutil/v3 v3.23.12 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.22.0 - golang.org/x/sys v0.19.0 + golang.org/x/crypto v0.28.0 + golang.org/x/sys v0.26.0 ) require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -47,6 +47,7 @@ require ( github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -54,13 +55,8 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/grpc v1.64.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.0 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index c42313d42c..38497f9611 100644 --- a/go.sum +++ b/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -41,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -53,8 +51,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -67,6 +65,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -102,6 +102,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -135,23 +137,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -162,33 +160,31 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -199,5 +195,5 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/image_substitutors_test.go b/image_substitutors_test.go index 64e3b95d2d..c9d6aee244 100644 --- a/image_substitutors_test.go +++ b/image_substitutors_test.go @@ -1,47 +1,70 @@ package testcontainers import ( + "context" "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go/internal/config" ) +func TestCustomHubSubstitutor(t *testing.T) { + t.Run("should substitute the image with the provided one", func(t *testing.T) { + s := NewCustomHubSubstitutor("quay.io") + + img, err := s.Substitute("foo/foo:latest") + require.NoError(t, err) + + require.Equalf(t, "quay.io/foo/foo:latest", img, "expected quay.io/foo/foo:latest, got %s", img) + }) + t.Run("should not substitute the image if it is already using the provided hub", func(t *testing.T) { + s := NewCustomHubSubstitutor("quay.io") + + img, err := s.Substitute("quay.io/foo/foo:latest") + require.NoError(t, err) + + require.Equalf(t, "quay.io/foo/foo:latest", img, "expected quay.io/foo/foo:latest, got %s", img) + }) + t.Run("should not substitute the image if hub image name prefix config exist", func(t *testing.T) { + t.Cleanup(config.Reset) + config.Reset() + t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "registry.mycompany.com/mirror") + s := NewCustomHubSubstitutor("quay.io") + + img, err := s.Substitute("foo/foo:latest") + require.NoError(t, err) + + require.Equalf(t, "foo/foo:latest", img, "expected foo/foo:latest, got %s", img) + }) +} + func TestPrependHubRegistrySubstitutor(t *testing.T) { t.Run("should prepend the hub registry to images from Docker Hub", func(t *testing.T) { t.Run("plain image", func(t *testing.T) { s := newPrependHubRegistry("my-registry") img, err := s.Substitute("foo:latest") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if img != "my-registry/foo:latest" { - t.Errorf("expected my-registry/foo, got %s", img) - } + require.Equalf(t, "my-registry/foo:latest", img, "expected my-registry/foo, got %s", img) }) t.Run("image with user", func(t *testing.T) { s := newPrependHubRegistry("my-registry") img, err := s.Substitute("user/foo:latest") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if img != "my-registry/user/foo:latest" { - t.Errorf("expected my-registry/foo, got %s", img) - } + require.Equalf(t, "my-registry/user/foo:latest", img, "expected my-registry/foo, got %s", img) }) t.Run("image with organization and user", func(t *testing.T) { s := newPrependHubRegistry("my-registry") img, err := s.Substitute("org/user/foo:latest") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if img != "my-registry/org/user/foo:latest" { - t.Errorf("expected my-registry/org/foo:latest, got %s", img) - } + require.Equalf(t, "my-registry/org/user/foo:latest", img, "expected my-registry/org/foo:latest, got %s", img) }) }) @@ -50,39 +73,54 @@ func TestPrependHubRegistrySubstitutor(t *testing.T) { s := newPrependHubRegistry("my-registry") img, err := s.Substitute("quay.io/foo:latest") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if img != "quay.io/foo:latest" { - t.Errorf("expected quay.io/foo:latest, got %s", img) - } + require.Equalf(t, "quay.io/foo:latest", img, "expected quay.io/foo:latest, got %s", img) }) - t.Run("explicitly including docker.io", func(t *testing.T) { + t.Run("explicitly including registry.hub.docker.com/library", func(t *testing.T) { s := newPrependHubRegistry("my-registry") - img, err := s.Substitute("docker.io/foo:latest") - if err != nil { - t.Fatal(err) - } + img, err := s.Substitute("registry.hub.docker.com/library/foo:latest") + require.NoError(t, err) - if img != "docker.io/foo:latest" { - t.Errorf("expected docker.io/foo:latest, got %s", img) - } + require.Equalf(t, "registry.hub.docker.com/library/foo:latest", img, "expected registry.hub.docker.com/library/foo:latest, got %s", img) }) t.Run("explicitly including registry.hub.docker.com", func(t *testing.T) { s := newPrependHubRegistry("my-registry") img, err := s.Substitute("registry.hub.docker.com/foo:latest") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if img != "registry.hub.docker.com/foo:latest" { - t.Errorf("expected registry.hub.docker.com/foo:latest, got %s", img) - } + require.Equalf(t, "registry.hub.docker.com/foo:latest", img, "expected registry.hub.docker.com/foo:latest, got %s", img) }) }) } + +func TestSubstituteBuiltImage(t *testing.T) { + req := GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + FromDockerfile: FromDockerfile{ + Context: "testdata", + Dockerfile: "echo.Dockerfile", + Tag: "my-image", + Repo: "my-repo", + }, + ImageSubstitutors: []ImageSubstitutor{newPrependHubRegistry("my-registry")}, + }, + Started: false, + } + + t.Run("should not use the properties prefix on built images", func(t *testing.T) { + config.Reset() + c, err := GenericContainer(context.Background(), req) + CleanupContainer(t, c) + require.NoError(t, err) + + json, err := c.Inspect(context.Background()) + require.NoError(t, err) + + require.Equalf(t, "my-registry/my-repo:my-image", json.Config.Image, "expected my-registry/my-repo:my-image, got %s", json.Config.Image) + }) +} diff --git a/image_test.go b/image_test.go index 17595b6590..b5a95640a8 100644 --- a/image_test.go +++ b/image_test.go @@ -6,16 +6,16 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/core" ) func TestImageList(t *testing.T) { - t.Setenv("DOCKER_HOST", core.ExtractDockerHost(context.Background())) + t.Setenv("DOCKER_HOST", core.MustExtractDockerHost(context.Background())) provider, err := ProviderDocker.GetProvider() - if err != nil { - t.Fatalf("failed to get provider %v", err) - } + require.NoErrorf(t, err, "failed to get provider") defer func() { _ = provider.Close() @@ -25,23 +25,14 @@ func TestImageList(t *testing.T) { Image: "redis:latest", } - container, err := provider.CreateContainer(context.Background(), req) - if err != nil { - t.Fatalf("creating test container %v", err) - } - - defer func() { - _ = container.Terminate(context.Background()) - }() + ctr, err := provider.CreateContainer(context.Background(), req) + CleanupContainer(t, ctr) + require.NoErrorf(t, err, "creating test container") images, err := provider.ListImages(context.Background()) - if err != nil { - t.Fatalf("listing images %v", err) - } + require.NoErrorf(t, err, "listing images") - if len(images) == 0 { - t.Fatal("no images retrieved") - } + require.NotEmptyf(t, images, "no images retrieved") // look if the list contains the container image for _, img := range images { @@ -54,12 +45,10 @@ func TestImageList(t *testing.T) { } func TestSaveImages(t *testing.T) { - t.Setenv("DOCKER_HOST", core.ExtractDockerHost(context.Background())) + t.Setenv("DOCKER_HOST", core.MustExtractDockerHost(context.Background())) provider, err := ProviderDocker.GetProvider() - if err != nil { - t.Fatalf("failed to get provider %v", err) - } + require.NoErrorf(t, err, "failed to get provider") defer func() { _ = provider.Close() @@ -69,27 +58,16 @@ func TestSaveImages(t *testing.T) { Image: "redis:latest", } - container, err := provider.CreateContainer(context.Background(), req) - if err != nil { - t.Fatalf("creating test container %v", err) - } - - defer func() { - _ = container.Terminate(context.Background()) - }() + ctr, err := provider.CreateContainer(context.Background(), req) + CleanupContainer(t, ctr) + require.NoErrorf(t, err, "creating test container") output := filepath.Join(t.TempDir(), "images.tar") err = provider.SaveImages(context.Background(), output, req.Image) - if err != nil { - t.Fatalf("saving image %q: %v", req.Image, err) - } + require.NoErrorf(t, err, "saving image %q", req.Image) info, err := os.Stat(output) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if info.Size() == 0 { - t.Fatalf("output file is empty") - } + require.NotZerof(t, info.Size(), "output file is empty") } diff --git a/internal/config/config.go b/internal/config/config.go index 1a7d031560..85be6acd86 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,7 +11,7 @@ import ( "github.com/magiconair/properties" ) -const ReaperDefaultImage = "testcontainers/ryuk:0.7.0" +const ReaperDefaultImage = "testcontainers/ryuk:0.11.0" var ( tcConfig Config @@ -20,18 +20,71 @@ var ( // testcontainersConfig { -// Config represents the configuration for Testcontainers +// Config represents the configuration for Testcontainers. +// User values are read from ~/.testcontainers.properties file which can be overridden +// using the specified environment variables. For more information, see [Custom Configuration]. +// +// The Ryuk prefixed fields controls the [Garbage Collector] feature, which ensures that +// resources are cleaned up after the test execution. +// +// [Garbage Collector]: https://golang.testcontainers.org/features/garbage_collector/ +// [Custom Configuration]: https://golang.testcontainers.org/features/configuration/ type Config struct { - Host string `properties:"docker.host,default="` - TLSVerify int `properties:"docker.tls.verify,default=0"` - CertPath string `properties:"docker.cert.path,default="` - HubImageNamePrefix string `properties:"hub.image.name.prefix,default="` - RyukDisabled bool `properties:"ryuk.disabled,default=false"` - RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"` + // Host is the address of the Docker daemon. + // + // Environment variable: DOCKER_HOST + Host string `properties:"docker.host,default="` + + // TLSVerify is a flag to enable or disable TLS verification when connecting to a Docker daemon. + // + // Environment variable: DOCKER_TLS_VERIFY + TLSVerify int `properties:"docker.tls.verify,default=0"` + + // CertPath is the path to the directory containing the Docker certificates. + // This is used when connecting to a Docker daemon over TLS. + // + // Environment variable: DOCKER_CERT_PATH + CertPath string `properties:"docker.cert.path,default="` + + // HubImageNamePrefix is the prefix used for the images pulled from the Docker Hub. + // This is useful when running tests in environments with restricted internet access. + // + // Environment variable: TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX + HubImageNamePrefix string `properties:"hub.image.name.prefix,default="` + + // RyukDisabled is a flag to enable or disable the Garbage Collector. + // Setting this to true will prevent testcontainers from automatically cleaning up + // resources, which is particularly important in tests which timeout as they + // don't run test clean up. + // + // Environment variable: TESTCONTAINERS_RYUK_DISABLED + RyukDisabled bool `properties:"ryuk.disabled,default=false"` + + // RyukPrivileged is a flag to enable or disable the privileged mode for the Garbage Collector container. + // Setting this to true will run the Garbage Collector container in privileged mode. + // + // Environment variable: TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED + RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"` + + // RyukReconnectionTimeout is the time to wait before attempting to reconnect to the Garbage Collector container. + // + // Environment variable: RYUK_RECONNECTION_TIMEOUT RyukReconnectionTimeout time.Duration `properties:"ryuk.reconnection.timeout,default=10s"` - RyukConnectionTimeout time.Duration `properties:"ryuk.connection.timeout,default=1m"` - RyukVerbose bool `properties:"ryuk.verbose,default=false"` - TestcontainersHost string `properties:"tc.host,default="` + + // RyukConnectionTimeout is the time to wait before timing out when connecting to the Garbage Collector container. + // + // Environment variable: RYUK_CONNECTION_TIMEOUT + RyukConnectionTimeout time.Duration `properties:"ryuk.connection.timeout,default=1m"` + + // RyukVerbose is a flag to enable or disable verbose logging for the Garbage Collector. + // + // Environment variable: RYUK_VERBOSE + RyukVerbose bool `properties:"ryuk.verbose,default=false"` + + // TestcontainersHost is the address of the Testcontainers host. + // + // Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE + TestcontainersHost string `properties:"tc.host,default="` } // } @@ -41,15 +94,6 @@ type Config struct { func Read() Config { tcConfigOnce.Do(func() { tcConfig = read() - - if tcConfig.RyukDisabled { - ryukDisabledMessage := ` -********************************************************************************************** -Ryuk has been disabled for the current execution. This can cause unexpected behavior in your environment. -More on this: https://golang.testcontainers.org/features/garbage_collector/ -**********************************************************************************************` - fmt.Println(ryukDisabledMessage) - } }) return tcConfig @@ -82,11 +126,21 @@ func read() Config { config.RyukPrivileged = ryukPrivilegedEnv == "true" } - ryukVerboseEnv := os.Getenv("TESTCONTAINERS_RYUK_VERBOSE") + ryukVerboseEnv := readTestcontainersEnv("RYUK_VERBOSE") if parseBool(ryukVerboseEnv) { config.RyukVerbose = ryukVerboseEnv == "true" } + ryukReconnectionTimeoutEnv := readTestcontainersEnv("RYUK_RECONNECTION_TIMEOUT") + if timeout, err := time.ParseDuration(ryukReconnectionTimeoutEnv); err == nil { + config.RyukReconnectionTimeout = timeout + } + + ryukConnectionTimeoutEnv := readTestcontainersEnv("RYUK_CONNECTION_TIMEOUT") + if timeout, err := time.ParseDuration(ryukConnectionTimeoutEnv); err == nil { + config.RyukConnectionTimeout = timeout + } + return config } @@ -114,3 +168,18 @@ func parseBool(input string) bool { _, err := strconv.ParseBool(input) return err == nil } + +// readTestcontainersEnv reads the environment variable with the given name. +// It checks for the environment variable with the given name first, and then +// checks for the environment variable with the given name prefixed with "TESTCONTAINERS_". +func readTestcontainersEnv(envVar string) string { + value := os.Getenv(envVar) + if value != "" { + return value + } + + // TODO: remove this prefix after the next major release + const prefix string = "TESTCONTAINERS_" + + return os.Getenv(prefix + envVar) +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 6c9cb0dd3c..591fcff11c 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,13 +1,13 @@ package config import ( - "fmt" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -19,10 +19,13 @@ const ( // unset environment variables to avoid side effects // execute this function before each test func resetTestEnv(t *testing.T) { + t.Helper() t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "") t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "") t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "") - t.Setenv("TESTCONTAINERS_RYUK_VERBOSE", "") + t.Setenv("RYUK_VERBOSE", "") + t.Setenv("RYUK_RECONNECTION_TIMEOUT", "") + t.Setenv("RYUK_CONNECTION_TIMEOUT", "") } func TestReadConfig(t *testing.T) { @@ -43,7 +46,7 @@ func TestReadConfig(t *testing.T) { Host: "", // docker socket is empty at the properties file } - assert.Equal(t, expected, config) + require.Equal(t, expected, config) t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "false") @@ -74,14 +77,18 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", defaultHubPrefix) t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true") + t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s") + t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s") config := read() expected := Config{ - HubImageNamePrefix: defaultHubPrefix, - RyukDisabled: true, - RyukPrivileged: true, - Host: "", // docker socket is empty at the properties file + HubImageNamePrefix: defaultHubPrefix, + RyukDisabled: true, + RyukPrivileged: true, + Host: "", // docker socket is empty at the properties file + RyukReconnectionTimeout: 13 * time.Second, + RyukConnectionTimeout: 12 * time.Second, } assert.Equal(t, expected, config) @@ -118,14 +125,18 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", defaultHubPrefix) t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true") - t.Setenv("TESTCONTAINERS_RYUK_VERBOSE", "true") + t.Setenv("RYUK_VERBOSE", "true") + t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s") + t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s") config := read() expected := Config{ - HubImageNamePrefix: defaultHubPrefix, - RyukDisabled: true, - RyukPrivileged: true, - RyukVerbose: true, + HubImageNamePrefix: defaultHubPrefix, + RyukDisabled: true, + RyukPrivileged: true, + RyukVerbose: true, + RyukReconnectionTimeout: 13 * time.Second, + RyukConnectionTimeout: 12 * time.Second, } assert.Equal(t, expected, config) @@ -133,10 +144,10 @@ func TestReadTCConfig(t *testing.T) { t.Run("HOME contains TC properties file", func(t *testing.T) { defaultRyukConnectionTimeout := 60 * time.Second - defaultRyukReonnectionTimeout := 10 * time.Second + defaultRyukReconnectionTimeout := 10 * time.Second defaultConfig := Config{ RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, } tests := []struct { @@ -152,7 +163,7 @@ func TestReadTCConfig(t *testing.T) { Config{ Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -164,7 +175,7 @@ func TestReadTCConfig(t *testing.T) { Config{ Host: tcpDockerHost4711, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -179,7 +190,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost1234, TLSVerify: 1, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -188,7 +199,7 @@ func TestReadTCConfig(t *testing.T) { map[string]string{}, Config{ RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -200,7 +211,7 @@ func TestReadTCConfig(t *testing.T) { Config{ Host: tcpDockerHost1234, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -210,7 +221,7 @@ func TestReadTCConfig(t *testing.T) { Config{ Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -230,7 +241,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost1234, CertPath: "/tmp/certs", RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -240,7 +251,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -250,7 +261,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -263,6 +274,31 @@ func TestReadTCConfig(t *testing.T) { RyukConnectionTimeout: 12 * time.Second, }, }, + { + "With Ryuk container timeouts configured using env vars", + ``, + map[string]string{ + "RYUK_RECONNECTION_TIMEOUT": "13s", + "RYUK_CONNECTION_TIMEOUT": "12s", + }, + Config{ + RyukReconnectionTimeout: 13 * time.Second, + RyukConnectionTimeout: 12 * time.Second, + }, + }, + { + "With Ryuk container timeouts configured using env vars and properties. Env var wins", + `ryuk.connection.timeout=22s + ryuk.reconnection.timeout=23s`, + map[string]string{ + "RYUK_RECONNECTION_TIMEOUT": "13s", + "RYUK_CONNECTION_TIMEOUT": "12s", + }, + Config{ + RyukReconnectionTimeout: 13 * time.Second, + RyukConnectionTimeout: 12 * time.Second, + }, + }, { "With Ryuk verbose configured using properties", `ryuk.verbose=true`, @@ -270,7 +306,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -282,7 +318,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -294,7 +330,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -306,7 +342,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -318,7 +354,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -341,31 +377,31 @@ func TestReadTCConfig(t *testing.T) { "With Ryuk verbose using an env var and properties. Env var wins (0)", `ryuk.verbose=true`, map[string]string{ - "TESTCONTAINERS_RYUK_VERBOSE": "true", + "RYUK_VERBOSE": "true", }, Config{ RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { "With Ryuk verbose using an env var and properties. Env var wins (1)", `ryuk.verbose=false`, map[string]string{ - "TESTCONTAINERS_RYUK_VERBOSE": "true", + "RYUK_VERBOSE": "true", }, Config{ RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { "With Ryuk verbose using an env var and properties. Env var wins (2)", `ryuk.verbose=true`, map[string]string{ - "TESTCONTAINERS_RYUK_VERBOSE": "false", + "RYUK_VERBOSE": "false", }, defaultConfig, }, @@ -373,7 +409,7 @@ func TestReadTCConfig(t *testing.T) { "With Ryuk verbose using an env var and properties. Env var wins (3)", `ryuk.verbose=false`, map[string]string{ - "TESTCONTAINERS_RYUK_VERBOSE": "false", + "RYUK_VERBOSE": "false", }, defaultConfig, }, @@ -386,7 +422,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -398,7 +434,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -453,7 +489,7 @@ func TestReadTCConfig(t *testing.T) { Config{ HubImageNamePrefix: defaultHubPrefix + "/props/", RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -465,7 +501,7 @@ func TestReadTCConfig(t *testing.T) { Config{ HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, { @@ -477,22 +513,20 @@ func TestReadTCConfig(t *testing.T) { Config{ HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, - RyukReconnectionTimeout: defaultRyukReonnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, }, }, } for _, tt := range tests { - t.Run(fmt.Sprintf(tt.name), func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) t.Setenv("USERPROFILE", tmpDir) // Windows support for k, v := range tt.env { t.Setenv(k, v) } - if err := os.WriteFile(filepath.Join(tmpDir, ".testcontainers.properties"), []byte(tt.content), 0o600); err != nil { - t.Errorf("Failed to create the file: %v", err) - return - } + err := os.WriteFile(filepath.Join(tmpDir, ".testcontainers.properties"), []byte(tt.content), 0o600) + require.NoErrorf(t, err, "Failed to create the file") // config := read() diff --git a/internal/core/bootstrap.go b/internal/core/bootstrap.go index cf06dde7e2..1c45297704 100644 --- a/internal/core/bootstrap.go +++ b/internal/core/bootstrap.go @@ -2,6 +2,7 @@ package core import ( "crypto/sha256" + "encoding/hex" "fmt" "os" @@ -89,7 +90,7 @@ func init() { return } - sessionID = fmt.Sprintf("%x", hasher.Sum(nil)) + sessionID = hex.EncodeToString(hasher.Sum(nil)) } func ProcessID() string { diff --git a/internal/core/client.go b/internal/core/client.go index 64af509b9f..04a54bcbc5 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -14,7 +14,7 @@ import ( func NewClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) { tcConfig := config.Read() - dockerHost := ExtractDockerHost(ctx) + dockerHost := MustExtractDockerHost(ctx) opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()} if dockerHost != "" { diff --git a/internal/core/docker_host.go b/internal/core/docker_host.go index 0b55ec01c8..3088a3742b 100644 --- a/internal/core/docker_host.go +++ b/internal/core/docker_host.go @@ -56,7 +56,24 @@ func DefaultGatewayIP() (string, error) { return ip, nil } -// ExtractDockerHost Extracts the docker host from the different alternatives, caching the result to avoid unnecessary +// dockerHostCheck Use a vanilla Docker client to check if the Docker host is reachable. +// It will avoid recursive calls to this function. +var dockerHostCheck = func(ctx context.Context, host string) error { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithHost(host), client.WithAPIVersionNegotiation()) + if err != nil { + return fmt.Errorf("new client: %w", err) + } + defer cli.Close() + + _, err = cli.Info(ctx) + if err != nil { + return fmt.Errorf("docker info: %w", err) + } + + return nil +} + +// MustExtractDockerHost Extracts the docker host from the different alternatives, caching the result to avoid unnecessary // calculations. Use this function to get the actual Docker host. This function does not consider Windows containers at the moment. // The possible alternatives are: // @@ -66,16 +83,21 @@ func DefaultGatewayIP() (string, error) { // 4. Docker host from the default docker socket path, without the unix schema. // 5. Docker host from the "docker.host" property in the ~/.testcontainers.properties file. // 6. Rootless docker socket path. -// 7. Else, the default Docker socket including schema will be returned. -func ExtractDockerHost(ctx context.Context) string { +// 7. Else, because the Docker host is not set, it panics. +func MustExtractDockerHost(ctx context.Context) string { dockerHostOnce.Do(func() { - dockerHostCache = extractDockerHost(ctx) + cache, err := extractDockerHost(ctx) + if err != nil { + panic(err) + } + + dockerHostCache = cache }) return dockerHostCache } -// ExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema and +// MustExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema and // caching the result to avoid unnecessary calculations. Use this function to get the docker socket path, // not the host (e.g. mounting the socket in a container). This function does not consider Windows containers at the moment. // The possible alternatives are: @@ -83,12 +105,12 @@ func ExtractDockerHost(ctx context.Context) string { // 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file. // 2. The TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable. // 3. Using a Docker client, check if the Info().OperativeSystem is "Docker Desktop" and return the default docker socket path for rootless docker. -// 4. Else, Get the current Docker Host from the existing strategies: see ExtractDockerHost. +// 4. Else, Get the current Docker Host from the existing strategies: see MustExtractDockerHost. // 5. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock) // 6. Else, the default location of the docker socket is used (/var/run/docker.sock) // -// In any case, if the docker socket schema is "tcp://", the default docker socket path will be returned. -func ExtractDockerSocket(ctx context.Context) string { +// It panics if a Docker client cannot be created, or the Docker host cannot be discovered. +func MustExtractDockerSocket(ctx context.Context) string { dockerSocketPathOnce.Do(func() { dockerSocketPathCache = extractDockerSocket(ctx) }) @@ -98,7 +120,7 @@ func ExtractDockerSocket(ctx context.Context) string { // extractDockerHost Extracts the docker host from the different alternatives, without caching the result. // This internal method is handy for testing purposes. -func extractDockerHost(ctx context.Context) string { +func extractDockerHost(ctx context.Context) (string, error) { dockerHostFns := []func(context.Context) (string, error){ testcontainersHostFromProperties, dockerHostFromEnv, @@ -108,25 +130,35 @@ func extractDockerHost(ctx context.Context) string { rootlessDockerSocketPath, } - outerErr := ErrSocketNotFound + var errs []error for _, dockerHostFn := range dockerHostFns { dockerHost, err := dockerHostFn(ctx) if err != nil { - outerErr = fmt.Errorf("%w: %w", outerErr, err) + if !isHostNotSet(err) { + errs = append(errs, err) + } continue } - return dockerHost + if err = dockerHostCheck(ctx, dockerHost); err != nil { + errs = append(errs, fmt.Errorf("check host %q: %w", dockerHost, err)) + continue + } + + return dockerHost, nil } - // We are not supporting Windows containers at the moment - return DockerSocketPathWithSchema + if len(errs) > 0 { + return "", errors.Join(errs...) + } + + return "", ErrSocketNotFound } -// extractDockerHost Extracts the docker socket from the different alternatives, without caching the result. +// extractDockerSocket Extracts the docker socket from the different alternatives, without caching the result. // It will internally use the default Docker client, calling the internal method extractDockerSocketFromClient with it. // This internal method is handy for testing purposes. -// If a Docker client cannot be created, the program will panic. +// It panics if a Docker client cannot be created, or the Docker host is not discovered. func extractDockerSocket(ctx context.Context) string { cli, err := NewClient(ctx) if err != nil { @@ -140,6 +172,7 @@ func extractDockerSocket(ctx context.Context) string { // extractDockerSocketFromClient Extracts the docker socket from the different alternatives, without caching the result, // and receiving an instance of the Docker API client interface. // This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour. +// It panics if the Docker Info call errors, or the Docker host is not discovered. func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) string { // check that the socket is not a tcp or unix socket checkDockerSocketFn := func(socket string) string { @@ -160,7 +193,7 @@ func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) st return checkDockerSocketFn(tcHost) } - testcontainersDockerSocket, err := dockerSocketOverridePath(ctx) + testcontainersDockerSocket, err := dockerSocketOverridePath() if err == nil { return checkDockerSocketFn(testcontainersDockerSocket) } @@ -179,11 +212,33 @@ func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) st return DockerSocketPath } - dockerHost := extractDockerHost(ctx) + dockerHost, err := extractDockerHost(ctx) + if err != nil { + panic(err) // Docker host is required to get the Docker socket + } return checkDockerSocketFn(dockerHost) } +// isHostNotSet returns true if the error is related to the Docker host +// not being set, false otherwise. +func isHostNotSet(err error) bool { + switch { + case errors.Is(err, ErrTestcontainersHostNotSetInProperties), + errors.Is(err, ErrDockerHostNotSet), + errors.Is(err, ErrDockerSocketNotSetInContext), + errors.Is(err, ErrDockerSocketNotSetInProperties), + errors.Is(err, ErrSocketNotFoundInPath), + errors.Is(err, ErrXDGRuntimeDirNotSet), + errors.Is(err, ErrRootlessDockerNotFoundHomeRunDir), + errors.Is(err, ErrRootlessDockerNotFoundHomeDesktopDir), + errors.Is(err, ErrRootlessDockerNotFoundRunDir): + return true + default: + return false + } +} + // dockerHostFromEnv returns the docker host from the DOCKER_HOST environment variable, if it's not empty func dockerHostFromEnv(ctx context.Context) (string, error) { if dockerHostPath := os.Getenv("DOCKER_HOST"); dockerHostPath != "" { @@ -220,7 +275,7 @@ func dockerHostFromProperties(ctx context.Context) (string, error) { // dockerSocketOverridePath returns the docker socket from the TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable, // if it's not empty -func dockerSocketOverridePath(ctx context.Context) (string, error) { +func dockerSocketOverridePath() (string, error) { if dockerHostPath, exists := os.LookupEnv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"); exists { return dockerHostPath, nil } diff --git a/internal/core/docker_host_test.go b/internal/core/docker_host_test.go index dbdbaa31fe..6faac45776 100644 --- a/internal/core/docker_host_test.go +++ b/internal/core/docker_host_test.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "os" "path/filepath" "testing" @@ -40,6 +41,23 @@ var resetSocketOverrideFn = func() { os.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", originalDockerSocketOverride) } +func testCallbackCheckPassing(_ context.Context, _ string) error { + return nil +} + +func testCallbackCheckError(_ context.Context, _ string) error { + return errors.New("could not check the Docker host") +} + +func mockCallbackCheck(t *testing.T, fn func(_ context.Context, _ string) error) { + t.Helper() + oldCheck := dockerHostCheck + dockerHostCheck = fn + t.Cleanup(func() { + dockerHostCheck = oldCheck + }) +} + func TestExtractDockerHost(t *testing.T) { setupDockerHostNotFound(t) // do not mess with local .testcontainers.properties @@ -47,17 +65,21 @@ func TestExtractDockerHost(t *testing.T) { t.Setenv("HOME", tmpDir) t.Setenv("USERPROFILE", tmpDir) // Windows support - t.Run("Docker Host as extracted just once", func(t *testing.T) { + // apply the passing check to all sub-tests + mockCallbackCheck(t, testCallbackCheckPassing) + + t.Run("Docker Host is extracted just once", func(t *testing.T) { expected := "/path/to/docker.sock" t.Setenv("DOCKER_HOST", expected) - host := ExtractDockerHost(context.Background()) - assert.Equal(t, expected, host) + host := MustExtractDockerHost(context.Background()) + + require.Equal(t, expected, host) t.Setenv("DOCKER_HOST", "/path/to/another/docker.sock") - host = ExtractDockerHost(context.Background()) - assert.Equal(t, expected, host) + host = MustExtractDockerHost(context.Background()) + require.Equal(t, expected, host) }) t.Run("Testcontainers Host is resolved first", func(t *testing.T) { @@ -66,16 +88,30 @@ func TestExtractDockerHost(t *testing.T) { setupTestcontainersProperties(t, content) - host := extractDockerHost(context.Background()) + host, err := extractDockerHost(context.Background()) + require.NoError(t, err) + require.Equal(t, testRemoteHost, host) + }) + + t.Run("Testcontainers Host is resolved first but not reachable", func(t *testing.T) { + t.Setenv("DOCKER_HOST", "/path/to/docker.sock") + content := "tc.host=" + testRemoteHost + + setupTestcontainersProperties(t, content) - assert.Equal(t, testRemoteHost, host) + // mock the callback check to return an error + mockCallbackCheck(t, testCallbackCheckError) + + host, err := extractDockerHost(context.Background()) + require.Error(t, err) + require.Equal(t, "", host) }) t.Run("Docker Host as environment variable", func(t *testing.T) { t.Setenv("DOCKER_HOST", "/path/to/docker.sock") - host := extractDockerHost(context.Background()) - - assert.Equal(t, "/path/to/docker.sock", host) + host, err := extractDockerHost(context.Background()) + require.NoError(t, err) + require.Equal(t, "/path/to/docker.sock", host) }) t.Run("Malformed Docker Host is passed in context", func(t *testing.T) { @@ -84,9 +120,9 @@ func TestExtractDockerHost(t *testing.T) { ctx := context.Background() - host := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "path-to-docker-sock")) - - assert.Equal(t, DockerSocketPathWithSchema, host) + host, err := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "path-to-docker-sock")) + require.Error(t, err) + require.Equal(t, "", host) }) t.Run("Malformed Schema Docker Host is passed in context", func(t *testing.T) { @@ -94,17 +130,17 @@ func TestExtractDockerHost(t *testing.T) { setupRootlessNotFound(t) ctx := context.Background() - host := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "http://path to docker sock")) - - assert.Equal(t, DockerSocketPathWithSchema, host) + host, err := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "http://path to docker sock")) + require.Error(t, err) + require.Equal(t, "", host) }) t.Run("Unix Docker Host is passed in context", func(t *testing.T) { ctx := context.Background() - host := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, DockerSocketSchema+"/this/is/a/sample.sock")) - - assert.Equal(t, "/this/is/a/sample.sock", host) + host, err := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, DockerSocketSchema+"/this/is/a/sample.sock")) + require.NoError(t, err) + require.Equal(t, "/this/is/a/sample.sock", host) }) t.Run("Unix Docker Host is passed as docker.host", func(t *testing.T) { @@ -114,26 +150,26 @@ func TestExtractDockerHost(t *testing.T) { setupTestcontainersProperties(t, content) - host := extractDockerHost(context.Background()) - - assert.Equal(t, DockerSocketSchema+"/this/is/a/sample.sock", host) + host, err := extractDockerHost(context.Background()) + require.NoError(t, err) + require.Equal(t, DockerSocketSchema+"/this/is/a/sample.sock", host) }) t.Run("Default Docker socket", func(t *testing.T) { setupRootlessNotFound(t) tmpSocket := setupDockerSocket(t) - host := extractDockerHost(context.Background()) - - assert.Equal(t, tmpSocket, host) + host, err := extractDockerHost(context.Background()) + require.NoError(t, err) + require.Equal(t, tmpSocket, host) }) - t.Run("Default Docker Host when empty", func(t *testing.T) { + t.Run("Error when empty", func(t *testing.T) { setupDockerSocketNotFound(t) setupRootlessNotFound(t) - host := extractDockerHost(context.Background()) - - assert.Equal(t, DockerSocketPathWithSchema, host) + host, err := extractDockerHost(context.Background()) + require.Error(t, err) + require.Equal(t, "", host) }) t.Run("Extract Docker socket", func(t *testing.T) { @@ -147,7 +183,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := testcontainersHostFromProperties(context.Background()) require.NoError(t, err) - assert.Equal(t, testRemoteHost, socket) + require.Equal(t, testRemoteHost, socket) }) t.Run("Testcontainers host is not defined in properties", func(t *testing.T) { @@ -157,7 +193,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := testcontainersHostFromProperties(context.Background()) require.ErrorIs(t, err, ErrTestcontainersHostNotSetInProperties) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("DOCKER_HOST is set", func(t *testing.T) { @@ -169,7 +205,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromEnv(context.Background()) require.NoError(t, err) - assert.Equal(t, tmpSocket, socket) + require.Equal(t, tmpSocket, socket) }) t.Run("DOCKER_HOST is not set", func(t *testing.T) { @@ -177,7 +213,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromEnv(context.Background()) require.ErrorIs(t, err, ErrDockerHostNotSet) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE is set", func(t *testing.T) { @@ -189,9 +225,9 @@ func TestExtractDockerHost(t *testing.T) { err := createTmpDockerSocket(tmpDir) require.NoError(t, err) - socket, err := dockerSocketOverridePath(context.Background()) + socket, err := dockerSocketOverridePath() require.NoError(t, err) - assert.Equal(t, tmpSocket, socket) + require.Equal(t, tmpSocket, socket) }) t.Run("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE is not set", func(t *testing.T) { @@ -199,9 +235,9 @@ func TestExtractDockerHost(t *testing.T) { os.Unsetenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") - socket, err := dockerSocketOverridePath(context.Background()) + socket, err := dockerSocketOverridePath() require.ErrorIs(t, err, ErrDockerSocketOverrideNotSet) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("Context sets the Docker socket", func(t *testing.T) { @@ -209,7 +245,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromContext(context.WithValue(ctx, DockerHostContextKey, DockerSocketSchema+"/this/is/a/sample.sock")) require.NoError(t, err) - assert.Equal(t, "/this/is/a/sample.sock", socket) + require.Equal(t, "/this/is/a/sample.sock", socket) }) t.Run("Context sets a malformed Docker socket", func(t *testing.T) { @@ -217,7 +253,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromContext(context.WithValue(ctx, DockerHostContextKey, "path-to-docker-sock")) require.Error(t, err) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("Context sets a malformed schema for the Docker socket", func(t *testing.T) { @@ -225,7 +261,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromContext(context.WithValue(ctx, DockerHostContextKey, "http://example.com/docker.sock")) require.ErrorIs(t, err, ErrNoUnixSchema) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("Docker socket exists", func(t *testing.T) { @@ -233,7 +269,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerSocketPath(context.Background()) require.NoError(t, err) - assert.Equal(t, tmpSocket, socket) + require.Equal(t, tmpSocket, socket) }) t.Run("Docker host is defined in properties", func(t *testing.T) { @@ -244,7 +280,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromProperties(context.Background()) require.NoError(t, err) - assert.Equal(t, tmpSocket, socket) + require.Equal(t, tmpSocket, socket) }) t.Run("Docker host is not defined in properties", func(t *testing.T) { @@ -254,7 +290,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerHostFromProperties(context.Background()) require.ErrorIs(t, err, ErrDockerSocketNotSetInProperties) - assert.Empty(t, socket) + require.Empty(t, socket) }) t.Run("Docker socket does not exist", func(t *testing.T) { @@ -262,7 +298,7 @@ func TestExtractDockerHost(t *testing.T) { socket, err := dockerSocketPath(context.Background()) require.ErrorIs(t, err, ErrSocketNotFoundInPath) - assert.Empty(t, socket) + require.Empty(t, socket) }) }) } @@ -285,13 +321,15 @@ func (m mockCli) Info(ctx context.Context) (system.Info, error) { func TestExtractDockerSocketFromClient(t *testing.T) { setupDockerHostNotFound(t) + mockCallbackCheck(t, testCallbackCheckPassing) + t.Run("Docker socket from Testcontainers host defined in properties", func(t *testing.T) { content := "tc.host=" + testRemoteHost setupTestcontainersProperties(t, content) socket := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"}) - assert.Equal(t, DockerSocketPath, socket) + require.Equal(t, DockerSocketPath, socket) }) t.Run("Docker socket from Testcontainers host takes precedence over TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", func(t *testing.T) { @@ -303,7 +341,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) { t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/path/to/docker.sock") socket := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"}) - assert.Equal(t, DockerSocketPath, socket) + require.Equal(t, DockerSocketPath, socket) }) t.Run("Docker Socket as Testcontainers environment variable", func(t *testing.T) { @@ -314,7 +352,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) { t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/path/to/docker.sock") host := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"}) - assert.Equal(t, "/path/to/docker.sock", host) + require.Equal(t, "/path/to/docker.sock", host) }) t.Run("Docker Socket as Testcontainers environment variable, removes prefixes", func(t *testing.T) { @@ -324,11 +362,11 @@ func TestExtractDockerSocketFromClient(t *testing.T) { t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", DockerSocketSchema+"/path/to/docker.sock") host := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"}) - assert.Equal(t, "/path/to/docker.sock", host) + require.Equal(t, "/path/to/docker.sock", host) t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", testRemoteHost) host = extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"}) - assert.Equal(t, DockerSocketPath, host) + require.Equal(t, DockerSocketPath, host) }) t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Docker Desktop on non-Windows)", func(t *testing.T) { @@ -347,7 +385,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) { socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Docker Desktop"}) - assert.Equal(t, DockerSocketPath, socket) + require.Equal(t, DockerSocketPath, socket) }) t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Docker Desktop for Windows)", func(t *testing.T) { @@ -362,7 +400,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) { socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Docker Desktop"}) - assert.Equal(t, WindowsDockerSocketPath, socket) + require.Equal(t, WindowsDockerSocketPath, socket) }) t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Not Docker Desktop)", func(t *testing.T) { @@ -376,7 +414,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) { socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"}) - assert.Equal(t, "/this/is/a/sample.sock", socket) + require.Equal(t, "/this/is/a/sample.sock", socket) }) t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Not Docker Desktop), removes prefixes", func(t *testing.T) { @@ -389,11 +427,11 @@ func TestExtractDockerSocketFromClient(t *testing.T) { t.Setenv("DOCKER_HOST", DockerSocketSchema+"/this/is/a/sample.sock") socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"}) - assert.Equal(t, "/this/is/a/sample.sock", socket) + require.Equal(t, "/this/is/a/sample.sock", socket) t.Setenv("DOCKER_HOST", testRemoteHost) socket = extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"}) - assert.Equal(t, DockerSocketPath, socket) + require.Equal(t, DockerSocketPath, socket) }) t.Run("Unix Docker Socket is passed as docker.host property", func(t *testing.T) { @@ -409,7 +447,25 @@ func TestExtractDockerSocketFromClient(t *testing.T) { socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"}) - assert.Equal(t, "/this/is/a/sample.sock", socket) + require.Equal(t, "/this/is/a/sample.sock", socket) + }) + + t.Run("Unix Docker Socket is passed as docker.host property but not reachable", func(t *testing.T) { + content := "docker.host=" + DockerSocketSchema + "/this/is/a/sample.sock" + setupTestcontainersProperties(t, content) + + t.Cleanup(resetSocketOverrideFn) + + ctx := context.Background() + os.Unsetenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") + os.Unsetenv("DOCKER_HOST") + + mockCallbackCheck(t, testCallbackCheckError) + + require.Panics(t, func() { + // no need to check for the returned socket, as it must panic + _ = extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"}) + }) }) } @@ -460,10 +516,12 @@ func createTmpDockerSocket(parent string) error { // setupDockerHostNotFound sets up the environment for the test case where the DOCKER_HOST environment variable is // already set (e.g. rootless docker) therefore we need to unset it before the test func setupDockerHostNotFound(t *testing.T) { + t.Helper() t.Setenv("DOCKER_HOST", "") } func setupDockerSocket(t *testing.T) string { + t.Helper() t.Cleanup(func() { DockerSocketPath = originalDockerSocketPath DockerSocketPathWithSchema = originalDockerSocketPathWithSchema @@ -481,6 +539,7 @@ func setupDockerSocket(t *testing.T) string { } func setupDockerSocketNotFound(t *testing.T) { + t.Helper() t.Cleanup(func() { DockerSocketPath = originalDockerSocketPath DockerSocketPathWithSchema = originalDockerSocketPathWithSchema @@ -493,6 +552,7 @@ func setupDockerSocketNotFound(t *testing.T) { } func setupTestcontainersProperties(t *testing.T, content string) { + t.Helper() t.Cleanup(func() { // reset the properties file after the test config.Reset() @@ -507,8 +567,6 @@ func setupTestcontainersProperties(t *testing.T, content string) { t.Setenv("HOME", homeDir) t.Setenv("USERPROFILE", homeDir) // Windows support - if err := os.WriteFile(filepath.Join(homeDir, ".testcontainers.properties"), []byte(content), 0o600); err != nil { - t.Errorf("Failed to create the file: %v", err) - return - } + err = os.WriteFile(filepath.Join(homeDir, ".testcontainers.properties"), []byte(content), 0o600) + require.NoErrorf(t, err, "Failed to create the file") } diff --git a/internal/core/docker_rootless.go b/internal/core/docker_rootless.go index 44782d31b3..70cdebf240 100644 --- a/internal/core/docker_rootless.go +++ b/internal/core/docker_rootless.go @@ -3,11 +3,11 @@ package core import ( "context" "errors" - "fmt" "net/url" "os" "path/filepath" "runtime" + "strconv" ) var ( @@ -53,18 +53,24 @@ func rootlessDockerSocketPath(_ context.Context) (string, error) { rootlessSocketPathFromRunDir, } - outerErr := ErrRootlessDockerNotFound + var errs []error for _, socketPathFn := range socketPathFns { s, err := socketPathFn() if err != nil { - outerErr = fmt.Errorf("%w: %w", outerErr, err) + if !isHostNotSet(err) { + errs = append(errs, err) + } continue } return DockerSocketSchema + s, nil } - return "", outerErr + if len(errs) > 0 { + return "", errors.Join(errs...) + } + + return "", ErrRootlessDockerNotFound } func fileExists(f string) bool { @@ -138,7 +144,7 @@ func rootlessSocketPathFromHomeDesktopDir() (string, error) { // rootlessSocketPathFromRunDir returns the path to the rootless Docker socket from the /run/user//docker.sock file. func rootlessSocketPathFromRunDir() (string, error) { uid := os.Getuid() - f := filepath.Join(baseRunDir, "user", fmt.Sprintf("%d", uid), "docker.sock") + f := filepath.Join(baseRunDir, "user", strconv.Itoa(uid), "docker.sock") if fileExists(f) { return f, nil } diff --git a/internal/core/docker_rootless_test.go b/internal/core/docker_rootless_test.go index ef018eda53..d6a338acdb 100644 --- a/internal/core/docker_rootless_test.go +++ b/internal/core/docker_rootless_test.go @@ -2,9 +2,9 @@ package core import ( "context" - "fmt" "os" "path/filepath" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -70,7 +70,7 @@ func TestRootlessDockerSocketPathNotSupportedOnWindows(t *testing.T) { t.Setenv("GOOS", "windows") socketPath, err := rootlessDockerSocketPath(context.Background()) require.ErrorIs(t, err, ErrRootlessDockerNotSupportedWindows) - assert.Empty(t, socketPath) + require.Empty(t, socketPath) } func TestRootlessDockerSocketPath(t *testing.T) { @@ -161,7 +161,7 @@ func TestRootlessDockerSocketPath(t *testing.T) { }) uid := os.Getuid() - runDir := filepath.Join(tmpDir, "user", fmt.Sprintf("%d", uid)) + runDir := filepath.Join(tmpDir, "user", strconv.Itoa(uid)) err = createTmpDockerSocket(runDir) require.NoError(t, err) @@ -178,18 +178,13 @@ func TestRootlessDockerSocketPath(t *testing.T) { setupRootlessNotFound(t) socketPath, err := rootlessDockerSocketPath(context.Background()) - require.ErrorIs(t, err, ErrRootlessDockerNotFound) - assert.Empty(t, socketPath) - - // the wrapped error includes all the locations that were checked - require.ErrorContains(t, err, ErrRootlessDockerNotFoundXDGRuntimeDir.Error()) - require.ErrorContains(t, err, ErrRootlessDockerNotFoundHomeRunDir.Error()) - require.ErrorContains(t, err, ErrRootlessDockerNotFoundHomeDesktopDir.Error()) - require.ErrorContains(t, err, ErrRootlessDockerNotFoundRunDir.Error()) + require.ErrorIs(t, err, ErrRootlessDockerNotFoundXDGRuntimeDir) + require.Empty(t, socketPath) }) } func setupRootlessNotFound(t *testing.T) { + t.Helper() t.Cleanup(func() { baseRunDir = originalBaseRunDir os.Setenv("XDG_RUNTIME_DIR", originalXDGRuntimeDir) @@ -213,7 +208,7 @@ func setupRootlessNotFound(t *testing.T) { baseRunDir = tmpDir uid := os.Getuid() - runDir := filepath.Join(tmpDir, "run", "user", fmt.Sprintf("%d", uid)) + runDir := filepath.Join(tmpDir, "run", "user", strconv.Itoa(uid)) err = createTmpDir(runDir) require.NoError(t, err) } diff --git a/internal/core/images.go b/internal/core/images.go index 6c7213f12c..2892267e9c 100644 --- a/internal/core/images.go +++ b/internal/core/images.go @@ -2,6 +2,7 @@ package core import ( "bufio" + "io" "net/url" "os" "regexp" @@ -25,17 +26,22 @@ const ( var rxURL = regexp.MustCompile(URL) +// ExtractImagesFromDockerfile extracts images from the Dockerfile sourced from dockerfile. func ExtractImagesFromDockerfile(dockerfile string, buildArgs map[string]*string) ([]string, error) { - var images []string - file, err := os.Open(dockerfile) if err != nil { return nil, err } defer file.Close() + return ExtractImagesFromReader(file, buildArgs) +} + +// ExtractImagesFromReader extracts images from the Dockerfile sourced from r. +func ExtractImagesFromReader(r io.Reader, buildArgs map[string]*string) ([]string, error) { + var images []string var lines []string - scanner := bufio.NewScanner(file) + scanner := bufio.NewScanner(r) for scanner.Scan() { lines = append(lines, scanner.Text()) } diff --git a/internal/core/images_test.go b/internal/core/images_test.go index 760a5cb857..509a117c80 100644 --- a/internal/core/images_test.go +++ b/internal/core/images_test.go @@ -67,7 +67,7 @@ func TestExtractImagesFromDockerfile(t *testing.T) { images, err := ExtractImagesFromDockerfile(tt.dockerfile, tt.buildArgs) if tt.expectedError { require.Error(t, err) - assert.Empty(t, images) + require.Empty(t, images) } else { require.NoError(t, err) assert.Equal(t, tt.expected, images) diff --git a/internal/core/labels.go b/internal/core/labels.go index 58b054ab95..0814924234 100644 --- a/internal/core/labels.go +++ b/internal/core/labels.go @@ -1,23 +1,73 @@ package core import ( + "errors" + "fmt" + "strings" + "github.com/testcontainers/testcontainers-go/internal" + "github.com/testcontainers/testcontainers-go/internal/config" ) const ( - LabelBase = "org.testcontainers" - LabelLang = LabelBase + ".lang" - LabelReaper = LabelBase + ".reaper" - LabelRyuk = LabelBase + ".ryuk" + // LabelBase is the base label for all testcontainers labels. + LabelBase = "org.testcontainers" + + // LabelLang specifies the language which created the test container. + LabelLang = LabelBase + ".lang" + + // LabelReaper identifies the container as a reaper. + LabelReaper = LabelBase + ".reaper" + + // LabelRyuk identifies the container as a ryuk. + LabelRyuk = LabelBase + ".ryuk" + + // LabelSessionID specifies the session ID of the container. LabelSessionID = LabelBase + ".sessionId" - LabelVersion = LabelBase + ".version" + + // LabelVersion specifies the version of testcontainers which created the container. + LabelVersion = LabelBase + ".version" + + // LabelReap specifies the container should be reaped by the reaper. + LabelReap = LabelBase + ".reap" ) +// DefaultLabels returns the standard set of labels which +// includes LabelSessionID if the reaper is enabled. func DefaultLabels(sessionID string) map[string]string { - return map[string]string{ + labels := map[string]string{ LabelBase: "true", LabelLang: "go", - LabelSessionID: sessionID, LabelVersion: internal.Version, + LabelSessionID: sessionID, + } + + if !config.Read().RyukDisabled { + labels[LabelReap] = "true" + } + + return labels +} + +// AddDefaultLabels adds the default labels for sessionID to target. +func AddDefaultLabels(sessionID string, target map[string]string) { + for k, v := range DefaultLabels(sessionID) { + target[k] = v + } +} + +// MergeCustomLabels sets labels from src to dst. +// If a key in src has [LabelBase] prefix returns an error. +// If dst is nil returns an error. +func MergeCustomLabels(dst, src map[string]string) error { + if dst == nil { + return errors.New("destination map is nil") + } + for key, value := range src { + if strings.HasPrefix(key, LabelBase) { + return fmt.Errorf("key %q has %q prefix", key, LabelBase) + } + dst[key] = value } + return nil } diff --git a/internal/core/labels_test.go b/internal/core/labels_test.go new file mode 100644 index 0000000000..e382a0ad48 --- /dev/null +++ b/internal/core/labels_test.go @@ -0,0 +1,34 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMergeCustomLabels(t *testing.T) { + t.Run("success", func(t *testing.T) { + dst := map[string]string{"A": "1", "B": "2"} + src := map[string]string{"B": "X", "C": "3"} + + err := MergeCustomLabels(dst, src) + require.NoError(t, err) + require.Equal(t, map[string]string{"A": "1", "B": "X", "C": "3"}, dst) + }) + + t.Run("invalid-prefix", func(t *testing.T) { + dst := map[string]string{"A": "1", "B": "2"} + src := map[string]string{"B": "X", LabelLang: "go"} + + err := MergeCustomLabels(dst, src) + + require.EqualError(t, err, `key "org.testcontainers.lang" has "org.testcontainers" prefix`) + require.Equal(t, map[string]string{"A": "1", "B": "X"}, dst) + }) + + t.Run("nil-destination", func(t *testing.T) { + src := map[string]string{"A": "1"} + err := MergeCustomLabels(nil, src) + require.Error(t, err) + }) +} diff --git a/internal/core/network/network.go b/internal/core/network/network.go index d74aeee877..787065aec9 100644 --- a/internal/core/network/network.go +++ b/internal/core/network/network.go @@ -4,15 +4,32 @@ import ( "context" "fmt" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" "github.com/testcontainers/testcontainers-go/internal/core" ) +const ( + // FilterByID uses to filter network by identifier. + FilterByID = "id" + + // FilterByName uses to filter network by name. + FilterByName = "name" +) + // Get returns a network by its ID. -func Get(ctx context.Context, id string) (types.NetworkResource, error) { - var nw types.NetworkResource // initialize to the zero value +func Get(ctx context.Context, id string) (network.Inspect, error) { + return get(ctx, FilterByID, id) +} + +// GetByName returns a network by its name. +func GetByName(ctx context.Context, name string) (network.Inspect, error) { + return get(ctx, FilterByName, name) +} + +func get(ctx context.Context, filter string, value string) (network.Inspect, error) { + var nw network.Inspect // initialize to the zero value cli, err := core.NewClient(ctx) if err != nil { @@ -20,16 +37,15 @@ func Get(ctx context.Context, id string) (types.NetworkResource, error) { } defer cli.Close() - filters := filters.NewArgs() - filters.Add("id", id) - - list, err := cli.NetworkList(ctx, types.NetworkListOptions{Filters: filters}) + list, err := cli.NetworkList(ctx, network.ListOptions{ + Filters: filters.NewArgs(filters.Arg(filter, value)), + }) if err != nil { return nw, fmt.Errorf("failed to list networks: %w", err) } if len(list) == 0 { - return nw, fmt.Errorf("network %s not found", id) + return nw, fmt.Errorf("network %s not found (filtering by %s)", value, filter) } return list[0], nil diff --git a/internal/version.go b/internal/version.go index cfe3a043ab..6e8cb510c0 100644 --- a/internal/version.go +++ b/internal/version.go @@ -1,4 +1,4 @@ package internal // Version is the next development version of the application -const Version = "0.31.0" +const Version = "0.35.0" diff --git a/lifecycle.go b/lifecycle.go index 578773b9f5..63446f715d 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -5,8 +5,11 @@ import ( "errors" "fmt" "io" + "reflect" "strings" + "time" + "github.com/cenkalti/backoff/v4" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" @@ -31,12 +34,14 @@ type ContainerRequestHook func(ctx context.Context, req ContainerRequest) error // - Terminating // - Terminated // For that, it will receive a Container, modify it and return an error if needed. -type ContainerHook func(ctx context.Context, container Container) error +type ContainerHook func(ctx context.Context, ctr Container) error // ContainerLifecycleHooks is a struct that contains all the hooks that can be used // to modify the container lifecycle. All the container lifecycle hooks except the PreCreates hooks // will be passed to the container once it's created type ContainerLifecycleHooks struct { + PreBuilds []ContainerRequestHook + PostBuilds []ContainerRequestHook PreCreates []ContainerRequestHook PostCreates []ContainerHook PreStarts []ContainerHook @@ -55,6 +60,18 @@ var DefaultLoggingHook = func(logger Logging) ContainerLifecycleHooks { } return ContainerLifecycleHooks{ + PreBuilds: []ContainerRequestHook{ + func(ctx context.Context, req ContainerRequest) error { + logger.Printf("đŸŗ Building image %s:%s", req.GetRepo(), req.GetTag()) + return nil + }, + }, + PostBuilds: []ContainerRequestHook{ + func(ctx context.Context, req ContainerRequest) error { + logger.Printf("✅ Built image %s", req.Image) + return nil + }, + }, PreCreates: []ContainerRequestHook{ func(ctx context.Context, req ContainerRequest) error { logger.Printf("đŸŗ Creating container for image %s", req.Image) @@ -113,7 +130,7 @@ var DefaultLoggingHook = func(logger Logging) ContainerLifecycleHooks { } // defaultPreCreateHook is a hook that will apply the default configuration to the container -var defaultPreCreateHook = func(ctx context.Context, p *DockerProvider, req ContainerRequest, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) ContainerLifecycleHooks { +var defaultPreCreateHook = func(p *DockerProvider, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PreCreates: []ContainerRequestHook{ func(ctx context.Context, req ContainerRequest) error { @@ -163,43 +180,102 @@ var defaultCopyFileToContainerHook = func(files []ContainerFile) ContainerLifecy var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ - // first post-start hook is to produce logs and start log consumers + // Produce logs sending details to the log consumers. + // See combineContainerHooks for the order of execution. func(ctx context.Context, c Container) error { - dockerContainer := c.(*DockerContainer) - - if cfg == nil { + if cfg == nil || len(cfg.Consumers) == 0 { return nil } + dockerContainer := c.(*DockerContainer) + dockerContainer.consumers = dockerContainer.consumers[:0] for _, consumer := range cfg.Consumers { dockerContainer.followOutput(consumer) } - if len(cfg.Consumers) > 0 { - return dockerContainer.startLogProduction(ctx, cfg.Opts...) - } - return nil + return dockerContainer.startLogProduction(ctx, cfg.Opts...) }, }, - PreTerminates: []ContainerHook{ - // first pre-terminate hook is to stop the log production + PostStops: []ContainerHook{ + // Stop the log production. + // See combineContainerHooks for the order of execution. func(ctx context.Context, c Container) error { if cfg == nil || len(cfg.Consumers) == 0 { return nil } dockerContainer := c.(*DockerContainer) - return dockerContainer.stopLogProduction() }, }, } } +func checkPortsMapped(exposedAndMappedPorts nat.PortMap, exposedPorts []string) error { + portMap, _, err := nat.ParsePortSpecs(exposedPorts) + if err != nil { + return fmt.Errorf("parse exposed ports: %w", err) + } + + for exposedPort := range portMap { + // having entries in exposedAndMappedPorts, where the key is the exposed port, + // and the value is the mapped port, means that the port has been already mapped. + if _, ok := exposedAndMappedPorts[exposedPort]; ok { + continue + } + + // check if the port is mapped with the protocol (default is TCP) + if strings.Contains(string(exposedPort), "/") { + return fmt.Errorf("port %s is not mapped yet", exposedPort) + } + + // Port didn't have a type, default to tcp and retry. + exposedPort += "/tcp" + if _, ok := exposedAndMappedPorts[exposedPort]; !ok { + return fmt.Errorf("port %s is not mapped yet", exposedPort) + } + } + + return nil +} + // defaultReadinessHook is a hook that will wait for the container to be ready var defaultReadinessHook = func() ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ + func(ctx context.Context, c Container) error { + // wait until all the exposed ports are mapped: + // it will be ready when all the exposed ports are mapped, + // checking every 50ms, up to 1s, and failing if all the + // exposed ports are not mapped in 5s. + dockerContainer := c.(*DockerContainer) + + b := backoff.NewExponentialBackOff() + + b.InitialInterval = 50 * time.Millisecond + b.MaxElapsedTime = 5 * time.Second + b.MaxInterval = time.Duration(float64(time.Second) * backoff.DefaultRandomizationFactor) + + err := backoff.RetryNotify( + func() error { + jsonRaw, err := dockerContainer.inspectRawContainer(ctx) + if err != nil { + return err + } + + return checkPortsMapped(jsonRaw.NetworkSettings.Ports, dockerContainer.exposedPorts) + }, + b, + func(err error, duration time.Duration) { + dockerContainer.logger.Printf("All requested ports were not exposed: %v", err) + }, + ) + if err != nil { + return fmt.Errorf("all exposed ports, %s, were not mapped in 5s: %w", dockerContainer.exposedPorts, err) + } + + return nil + }, // wait for the container to be ready func(ctx context.Context, c Container) error { dockerContainer := c.(*DockerContainer) @@ -207,11 +283,11 @@ var defaultReadinessHook = func() ContainerLifecycleHooks { // if a Wait Strategy has been specified, wait before returning if dockerContainer.WaitingFor != nil { dockerContainer.logger.Printf( - "🚧 Waiting for container id %s image: %s. Waiting for: %+v", + "âŗ Waiting for container id %s image: %s. Waiting for: %+v", dockerContainer.ID[:12], dockerContainer.Image, dockerContainer.WaitingFor, ) if err := dockerContainer.WaitingFor.WaitUntilReady(ctx, c); err != nil { - return err + return fmt.Errorf("wait until ready: %w", err) } } @@ -223,11 +299,34 @@ var defaultReadinessHook = func() ContainerLifecycleHooks { } } +// buildingHook is a hook that will be called before a container image is built. +func (req ContainerRequest) buildingHook(ctx context.Context) error { + return req.applyLifecycleHooks(func(lifecycleHooks ContainerLifecycleHooks) error { + return lifecycleHooks.Building(ctx)(req) + }) +} + +// builtHook is a hook that will be called after a container image is built. +func (req ContainerRequest) builtHook(ctx context.Context) error { + return req.applyLifecycleHooks(func(lifecycleHooks ContainerLifecycleHooks) error { + return lifecycleHooks.Built(ctx)(req) + }) +} + // creatingHook is a hook that will be called before a container is created. func (req ContainerRequest) creatingHook(ctx context.Context) error { - errs := make([]error, len(req.LifecycleHooks)) - for i, lifecycleHooks := range req.LifecycleHooks { - errs[i] = lifecycleHooks.Creating(ctx)(req) + return req.applyLifecycleHooks(func(lifecycleHooks ContainerLifecycleHooks) error { + return lifecycleHooks.Creating(ctx)(req) + }) +} + +// applyLifecycleHooks calls hook on all LifecycleHooks. +func (req ContainerRequest) applyLifecycleHooks(hook func(lifecycleHooks ContainerLifecycleHooks) error) error { + var errs []error + for _, lifecycleHooks := range req.LifecycleHooks { + if err := hook(lifecycleHooks); err != nil { + errs = append(errs, err) + } } return errors.Join(errs...) @@ -309,14 +408,24 @@ func (c *DockerContainer) terminatedHook(ctx context.Context) error { // applyLifecycleHooks applies all lifecycle hooks reporting the container logs on error if logError is true. func (c *DockerContainer) applyLifecycleHooks(ctx context.Context, logError bool, hooks func(lifecycleHooks ContainerLifecycleHooks) []ContainerHook) error { - errs := make([]error, len(c.lifecycleHooks)) - for i, lifecycleHooks := range c.lifecycleHooks { - errs[i] = containerHookFn(ctx, hooks(lifecycleHooks))(c) + var errs []error + for _, lifecycleHooks := range c.lifecycleHooks { + if err := containerHookFn(ctx, hooks(lifecycleHooks))(c); err != nil { + errs = append(errs, err) + } } if err := errors.Join(errs...); err != nil { if logError { - c.printLogs(ctx, err) + select { + case <-ctx.Done(): + // Context has timed out so need a new context to get logs. + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + c.printLogs(ctx, err) + default: + c.printLogs(ctx, err) + } } return err @@ -325,10 +434,26 @@ func (c *DockerContainer) applyLifecycleHooks(ctx context.Context, logError bool return nil } +// Building is a hook that will be called before a container image is built. +func (c ContainerLifecycleHooks) Building(ctx context.Context) func(req ContainerRequest) error { + return containerRequestHook(ctx, c.PreBuilds) +} + +// Building is a hook that will be called before a container image is built. +func (c ContainerLifecycleHooks) Built(ctx context.Context) func(req ContainerRequest) error { + return containerRequestHook(ctx, c.PostBuilds) +} + // Creating is a hook that will be called before a container is created. func (c ContainerLifecycleHooks) Creating(ctx context.Context) func(req ContainerRequest) error { + return containerRequestHook(ctx, c.PreCreates) +} + +// containerRequestHook returns a function that will iterate over all +// the hooks and call them one by one until there is an error. +func containerRequestHook(ctx context.Context, hooks []ContainerRequestHook) func(req ContainerRequest) error { return func(req ContainerRequest) error { - for _, hook := range c.PreCreates { + for _, hook := range hooks { if err := hook(ctx, req); err != nil { return err } @@ -341,10 +466,12 @@ func (c ContainerLifecycleHooks) Creating(ctx context.Context) func(req Containe // containerHookFn is a helper function that will create a function to be returned by all the different // container lifecycle hooks. The created function will iterate over all the hooks and call them one by one. func containerHookFn(ctx context.Context, containerHook []ContainerHook) func(container Container) error { - return func(container Container) error { - errs := make([]error, len(containerHook)) - for i, hook := range containerHook { - errs[i] = hook(ctx, container) + return func(ctr Container) error { + var errs []error + for _, hook := range containerHook { + if err := hook(ctx, ctr); err != nil { + errs = append(errs, err) + } } return errors.Join(errs...) @@ -463,65 +590,50 @@ func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req Contain return nil } -// combineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining -// the default hooks with the user-defined hooks. The function will loop over all the default hooks, -// storing each of the hooks in a slice, and then it will loop over all the user-defined hooks, -// appending or prepending them to the slice of hooks. The order of hooks is the following: -// - for Pre-hooks, always run the default hooks first, then append the user-defined hooks -// - for Post-hooks, always run the user-defined hooks first, then the default hooks +// combineContainerHooks returns a ContainerLifecycle hook as the result +// of combining the default hooks with the user-defined hooks. +// +// The order of hooks is the following: +// - Pre-hooks run the default hooks first then the user-defined hooks +// - Post-hooks run the user-defined hooks first then the default hooks func combineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks { - preCreates := []ContainerRequestHook{} - postCreates := []ContainerHook{} - preStarts := []ContainerHook{} - postStarts := []ContainerHook{} - postReadies := []ContainerHook{} - preStops := []ContainerHook{} - postStops := []ContainerHook{} - preTerminates := []ContainerHook{} - postTerminates := []ContainerHook{} - + // We use reflection here to ensure that any new hooks are handled. + var hooks ContainerLifecycleHooks + hooksVal := reflect.ValueOf(&hooks).Elem() + hooksType := reflect.TypeOf(hooks) for _, defaultHook := range defaultHooks { - preCreates = append(preCreates, defaultHook.PreCreates...) - preStarts = append(preStarts, defaultHook.PreStarts...) - preStops = append(preStops, defaultHook.PreStops...) - preTerminates = append(preTerminates, defaultHook.PreTerminates...) + defaultVal := reflect.ValueOf(defaultHook) + for i := 0; i < hooksType.NumField(); i++ { + if strings.HasPrefix(hooksType.Field(i).Name, "Pre") { + field := hooksVal.Field(i) + field.Set(reflect.AppendSlice(field, defaultVal.Field(i))) + } + } } - // append the user-defined hooks after the default pre-hooks - // and because the post hooks are still empty, the user-defined post-hooks - // will be the first ones to be executed + // Append the user-defined hooks after the default pre-hooks + // and because the post hooks are still empty, the user-defined + // post-hooks will be the first ones to be executed. for _, userDefinedHook := range userDefinedHooks { - preCreates = append(preCreates, userDefinedHook.PreCreates...) - postCreates = append(postCreates, userDefinedHook.PostCreates...) - preStarts = append(preStarts, userDefinedHook.PreStarts...) - postStarts = append(postStarts, userDefinedHook.PostStarts...) - postReadies = append(postReadies, userDefinedHook.PostReadies...) - preStops = append(preStops, userDefinedHook.PreStops...) - postStops = append(postStops, userDefinedHook.PostStops...) - preTerminates = append(preTerminates, userDefinedHook.PreTerminates...) - postTerminates = append(postTerminates, userDefinedHook.PostTerminates...) + userVal := reflect.ValueOf(userDefinedHook) + for i := 0; i < hooksType.NumField(); i++ { + field := hooksVal.Field(i) + field.Set(reflect.AppendSlice(field, userVal.Field(i))) + } } - // finally, append the default post-hooks + // Finally, append the default post-hooks. for _, defaultHook := range defaultHooks { - postCreates = append(postCreates, defaultHook.PostCreates...) - postStarts = append(postStarts, defaultHook.PostStarts...) - postReadies = append(postReadies, defaultHook.PostReadies...) - postStops = append(postStops, defaultHook.PostStops...) - postTerminates = append(postTerminates, defaultHook.PostTerminates...) + defaultVal := reflect.ValueOf(defaultHook) + for i := 0; i < hooksType.NumField(); i++ { + if strings.HasPrefix(hooksType.Field(i).Name, "Post") { + field := hooksVal.Field(i) + field.Set(reflect.AppendSlice(field, defaultVal.Field(i))) + } + } } - return ContainerLifecycleHooks{ - PreCreates: preCreates, - PostCreates: postCreates, - PreStarts: preStarts, - PostStarts: postStarts, - PostReadies: postReadies, - PreStops: preStops, - PostStops: postStops, - PreTerminates: preTerminates, - PostTerminates: postTerminates, - } + return hooks } func mergePortBindings(configPortMap, exposedPortMap nat.PortMap, exposedPorts []string) nat.PortMap { diff --git a/lifecycle_test.go b/lifecycle_test.go index 95ee4c5f5a..91102ccf82 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -3,7 +3,10 @@ package testcontainers import ( "bufio" "context" + "errors" "fmt" + "io" + "reflect" "strings" "testing" "time" @@ -210,12 +213,7 @@ func TestPreCreateModifierHook(t *testing.T) { Name: networkName, }) require.NoError(t, err) - defer func() { - err := net.Remove(ctx) - if err != nil { - t.Logf("failed to remove network %s: %s\n", networkName, err) - } - }() + CleanupNetwork(t, net) dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ Name: networkName, @@ -262,12 +260,7 @@ func TestPreCreateModifierHook(t *testing.T) { Name: networkName, }) require.NoError(t, err) - defer func() { - err := net.Remove(ctx) - if err != nil { - t.Logf("failed to remove network %s: %s\n", networkName, err) - } - }() + CleanupNetwork(t, net) dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ Name: networkName, @@ -291,7 +284,7 @@ func TestPreCreateModifierHook(t *testing.T) { // assertions - assert.Empty( + require.Empty( t, inputNetworkingConfig.EndpointsConfig[networkName].Aliases, "Networking config's aliases should be empty", @@ -456,6 +449,73 @@ func TestMergePortBindings(t *testing.T) { } } +func TestPortMappingCheck(t *testing.T) { + makePortMap := func(ports ...string) nat.PortMap { + out := make(nat.PortMap) + for _, port := range ports { + // We don't care about the actual binding in this test + out[nat.Port(port)] = nil + } + return out + } + + tests := map[string]struct { + exposedAndMappedPorts nat.PortMap + exposedPorts []string + expectError bool + }{ + "no-protocol": { + exposedAndMappedPorts: makePortMap("1024/tcp"), + exposedPorts: []string{"1024"}, + }, + "protocol": { + exposedAndMappedPorts: makePortMap("1024/tcp"), + exposedPorts: []string{"1024/tcp"}, + }, + "protocol-target-port": { + exposedAndMappedPorts: makePortMap("1024/tcp"), + exposedPorts: []string{"1024:1024/tcp"}, + }, + "target-port": { + exposedAndMappedPorts: makePortMap("1024/tcp"), + exposedPorts: []string{"1024:1024"}, + }, + "multiple-ports": { + exposedAndMappedPorts: makePortMap("1024/tcp", "1025/tcp", "1026/tcp"), + exposedPorts: []string{"1024", "25:1025/tcp", "1026:1026"}, + }, + "only-ipv4": { + exposedAndMappedPorts: makePortMap("1024/tcp"), + exposedPorts: []string{"0.0.0.0::1024/tcp"}, + }, + "no-mapped-ports": { + exposedAndMappedPorts: makePortMap(), + exposedPorts: []string{"1024"}, + expectError: true, + }, + "wrong-mapped-port": { + exposedAndMappedPorts: makePortMap("1023/tcp"), + exposedPorts: []string{"1024"}, + expectError: true, + }, + "subset-mapped-ports": { + exposedAndMappedPorts: makePortMap("1024/tcp", "1025/tcp"), + exposedPorts: []string{"1024", "1025", "1026"}, + expectError: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + err := checkPortsMapped(tt.exposedAndMappedPorts, tt.exposedPorts) + if tt.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + func TestLifecycleHooks(t *testing.T) { tests := []struct { name string @@ -482,91 +542,91 @@ func TestLifecycleHooks(t *testing.T) { { PreCreates: []ContainerRequestHook{ func(ctx context.Context, req ContainerRequest) error { - prints = append(prints, fmt.Sprintf("pre-create hook 1: %#v", req)) + prints = append(prints, "pre-create hook 1") return nil }, func(ctx context.Context, req ContainerRequest) error { - prints = append(prints, fmt.Sprintf("pre-create hook 2: %#v", req)) + prints = append(prints, "pre-create hook 2") return nil }, }, PostCreates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-create hook 1: %#v", c)) + prints = append(prints, "post-create hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-create hook 2: %#v", c)) + prints = append(prints, "post-create hook 2") return nil }, }, PreStarts: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-start hook 1: %#v", c)) + prints = append(prints, "pre-start hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-start hook 2: %#v", c)) + prints = append(prints, "pre-start hook 2") return nil }, }, PostStarts: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-start hook 1: %#v", c)) + prints = append(prints, "post-start hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-start hook 2: %#v", c)) + prints = append(prints, "post-start hook 2") return nil }, }, PostReadies: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-ready hook 1: %#v", c)) + prints = append(prints, "post-ready hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-ready hook 2: %#v", c)) + prints = append(prints, "post-ready hook 2") return nil }, }, PreStops: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-stop hook 1: %#v", c)) + prints = append(prints, "pre-stop hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-stop hook 2: %#v", c)) + prints = append(prints, "pre-stop hook 2") return nil }, }, PostStops: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-stop hook 1: %#v", c)) + prints = append(prints, "post-stop hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-stop hook 2: %#v", c)) + prints = append(prints, "post-stop hook 2") return nil }, }, PreTerminates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-terminate hook 1: %#v", c)) + prints = append(prints, "pre-terminate hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-terminate hook 2: %#v", c)) + prints = append(prints, "pre-terminate hook 2") return nil }, }, PostTerminates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-terminate hook 1: %#v", c)) + prints = append(prints, "post-terminate hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-terminate hook 2: %#v", c)) + prints = append(prints, "post-terminate hook 2") return nil }, }, @@ -584,6 +644,7 @@ func TestLifecycleHooks(t *testing.T) { Reuse: tt.reuse, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -597,7 +658,7 @@ func TestLifecycleHooks(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - lifecycleHooksIsHonouredFn(t, ctx, prints) + lifecycleHooksIsHonouredFn(t, prints) }) } } @@ -631,6 +692,7 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -644,7 +706,8 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - require.Len(t, dl.data, 12) + // Includes two additional entries for stop when terminate is called. + require.Len(t, dl.data, 14) } func TestCombineLifecycleHooks(t *testing.T) { @@ -721,7 +784,7 @@ func TestCombineLifecycleHooks(t *testing.T) { // There are 5 lifecycles (create, start, ready, stop, terminate), // but ready has only half of the hooks (it only has post), so we have 90 hooks in total. - assert.Len(t, prints, 90) + require.Len(t, prints, 90) // The order of the hooks is: // - pre-X hooks: first default (2*2), then user-defined (3*2) @@ -797,6 +860,7 @@ func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -810,7 +874,8 @@ func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - require.Len(t, dl.data, 24) + // Includes four additional entries for stop (twice) when terminate is called. + require.Len(t, dl.data, 28) } type linesTestLogger struct { @@ -825,7 +890,7 @@ func TestPrintContainerLogsOnError(t *testing.T) { ctx := context.Background() req := ContainerRequest{ - Image: "docker.io/alpine", + Image: "alpine", Cmd: []string{"echo", "-n", "I am expecting this"}, WaitingFor: wait.ForLog("I was expecting that").WithStartupTimeout(5 * time.Second), } @@ -834,35 +899,28 @@ func TestPrintContainerLogsOnError(t *testing.T) { data: []string{}, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Logger: &arrayOfLinesLogger, Started: true, }) + CleanupContainer(t, ctr) // it should fail because the waiting for condition is not met - if err == nil { - t.Fatal(err) - } - terminateContainerOnEnd(t, ctx, container) + require.Error(t, err) - containerLogs, err := container.Logs(ctx) - if err != nil { - t.Fatal(err) - } + containerLogs, err := ctr.Logs(ctx) + require.NoError(t, err) defer containerLogs.Close() // read container logs line by line, checking that each line is present in the stdout rd := bufio.NewReader(containerLogs) for { line, err := rd.ReadString('\n') - if err != nil { - if err.Error() == "EOF" { - break - } - - t.Fatal("Read Error:", err) + if errors.Is(err, io.EOF) { + break } + require.NoErrorf(t, err, "Read Error") // the last line of the array should contain the line of interest, // but we are checking all the lines to make sure that is present @@ -877,42 +935,142 @@ func TestPrintContainerLogsOnError(t *testing.T) { } } -func lifecycleHooksIsHonouredFn(t *testing.T, ctx context.Context, prints []string) { - require.Len(t, prints, 24) - - assert.True(t, strings.HasPrefix(prints[0], "pre-create hook 1: ")) - assert.True(t, strings.HasPrefix(prints[1], "pre-create hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[2], "post-create hook 1: ")) - assert.True(t, strings.HasPrefix(prints[3], "post-create hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[4], "pre-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[5], "pre-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[6], "post-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[7], "post-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[8], "post-ready hook 1: ")) - assert.True(t, strings.HasPrefix(prints[9], "post-ready hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[10], "pre-stop hook 1: ")) - assert.True(t, strings.HasPrefix(prints[11], "pre-stop hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[12], "post-stop hook 1: ")) - assert.True(t, strings.HasPrefix(prints[13], "post-stop hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[14], "pre-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[15], "pre-start hook 2: ")) +func lifecycleHooksIsHonouredFn(t *testing.T, prints []string) { + t.Helper() + + expects := []string{ + "pre-create hook 1", + "pre-create hook 2", + "post-create hook 1", + "post-create hook 2", + "pre-start hook 1", + "pre-start hook 2", + "post-start hook 1", + "post-start hook 2", + "post-ready hook 1", + "post-ready hook 2", + "pre-stop hook 1", + "pre-stop hook 2", + "post-stop hook 1", + "post-stop hook 2", + "pre-start hook 1", + "pre-start hook 2", + "post-start hook 1", + "post-start hook 2", + "post-ready hook 1", + "post-ready hook 2", + // Terminate currently calls stop to ensure that child containers are stopped. + "pre-stop hook 1", + "pre-stop hook 2", + "post-stop hook 1", + "post-stop hook 2", + "pre-terminate hook 1", + "pre-terminate hook 2", + "post-terminate hook 1", + "post-terminate hook 2", + } - assert.True(t, strings.HasPrefix(prints[16], "post-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[17], "post-start hook 2: ")) + require.Equal(t, expects, prints) +} - assert.True(t, strings.HasPrefix(prints[18], "post-ready hook 1: ")) - assert.True(t, strings.HasPrefix(prints[19], "post-ready hook 2: ")) +func Test_combineContainerHooks(t *testing.T) { + var funcID string + defaultContainerRequestHook := func(ctx context.Context, req ContainerRequest) error { + funcID = "defaultContainerRequestHook" + return nil + } + userContainerRequestHook := func(ctx context.Context, req ContainerRequest) error { + funcID = "userContainerRequestHook" + return nil + } + defaultContainerHook := func(ctx context.Context, container Container) error { + funcID = "defaultContainerHook" + return nil + } + userContainerHook := func(ctx context.Context, container Container) error { + funcID = "userContainerHook" + return nil + } - assert.True(t, strings.HasPrefix(prints[20], "pre-terminate hook 1: ")) - assert.True(t, strings.HasPrefix(prints[21], "pre-terminate hook 2: ")) + defaultHooks := []ContainerLifecycleHooks{ + { + PreBuilds: []ContainerRequestHook{defaultContainerRequestHook}, + PostBuilds: []ContainerRequestHook{defaultContainerRequestHook}, + PreCreates: []ContainerRequestHook{defaultContainerRequestHook}, + PostCreates: []ContainerHook{defaultContainerHook}, + PreStarts: []ContainerHook{defaultContainerHook}, + PostStarts: []ContainerHook{defaultContainerHook}, + PostReadies: []ContainerHook{defaultContainerHook}, + PreStops: []ContainerHook{defaultContainerHook}, + PostStops: []ContainerHook{defaultContainerHook}, + PreTerminates: []ContainerHook{defaultContainerHook}, + PostTerminates: []ContainerHook{defaultContainerHook}, + }, + } + userDefinedHooks := []ContainerLifecycleHooks{ + { + PreBuilds: []ContainerRequestHook{userContainerRequestHook}, + PostBuilds: []ContainerRequestHook{userContainerRequestHook}, + PreCreates: []ContainerRequestHook{userContainerRequestHook}, + PostCreates: []ContainerHook{userContainerHook}, + PreStarts: []ContainerHook{userContainerHook}, + PostStarts: []ContainerHook{userContainerHook}, + PostReadies: []ContainerHook{userContainerHook}, + PreStops: []ContainerHook{userContainerHook}, + PostStops: []ContainerHook{userContainerHook}, + PreTerminates: []ContainerHook{userContainerHook}, + PostTerminates: []ContainerHook{userContainerHook}, + }, + } + expects := ContainerLifecycleHooks{ + PreBuilds: []ContainerRequestHook{defaultContainerRequestHook, userContainerRequestHook}, + PostBuilds: []ContainerRequestHook{userContainerRequestHook, defaultContainerRequestHook}, + PreCreates: []ContainerRequestHook{defaultContainerRequestHook, userContainerRequestHook}, + PostCreates: []ContainerHook{userContainerHook, defaultContainerHook}, + PreStarts: []ContainerHook{defaultContainerHook, userContainerHook}, + PostStarts: []ContainerHook{userContainerHook, defaultContainerHook}, + PostReadies: []ContainerHook{userContainerHook, defaultContainerHook}, + PreStops: []ContainerHook{defaultContainerHook, userContainerHook}, + PostStops: []ContainerHook{userContainerHook, defaultContainerHook}, + PreTerminates: []ContainerHook{defaultContainerHook, userContainerHook}, + PostTerminates: []ContainerHook{userContainerHook, defaultContainerHook}, + } - assert.True(t, strings.HasPrefix(prints[22], "post-terminate hook 1: ")) - assert.True(t, strings.HasPrefix(prints[23], "post-terminate hook 2: ")) + ctx := context.Background() + ctxVal := reflect.ValueOf(ctx) + var req ContainerRequest + reqVal := reflect.ValueOf(req) + container := &DockerContainer{} + containerVal := reflect.ValueOf(container) + + got := combineContainerHooks(defaultHooks, userDefinedHooks) + + // Compare for equal. This can't be done with deep equals as functions + // are not comparable so we us the unique value stored in funcID when + // the function is called to determine if they are the same. + gotVal := reflect.ValueOf(got) + gotType := reflect.TypeOf(got) + expectedVal := reflect.ValueOf(expects) + for i := 0; i < gotVal.NumField(); i++ { + fieldName := gotType.Field(i).Name + gotField := gotVal.Field(i) + expectedField := expectedVal.Field(i) + require.Equalf(t, expectedField.Len(), 2, "field %q not setup len expected %d got %d", fieldName, 2, expectedField.Len()) //nolint:testifylint // False positive. + require.Equalf(t, expectedField.Len(), gotField.Len(), "field %q len expected %d got %d", fieldName, gotField.Len(), expectedField.Len()) + for j := 0; j < gotField.Len(); j++ { + gotIndex := gotField.Index(j) + expectedIndex := expectedField.Index(j) + var gotID string + if gotIndex.Type().Name() == "ContainerRequestHook" { + gotIndex.Call([]reflect.Value{ctxVal, reqVal}) + gotID = funcID + expectedIndex.Call([]reflect.Value{ctxVal, reqVal}) + } else { + gotIndex.Call([]reflect.Value{ctxVal, containerVal}) + gotID = funcID + expectedIndex.Call([]reflect.Value{ctxVal, containerVal}) + } + require.Equalf(t, funcID, gotID, "field %q[%d] func expected %s got %s", fieldName, j, funcID, gotID) + } + } } diff --git a/logconsumer_test.go b/logconsumer_test.go index 192a00f954..dae1ea0b5a 100644 --- a/logconsumer_test.go +++ b/logconsumer_test.go @@ -92,6 +92,7 @@ func Test_LogConsumerGetsCalled(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -112,9 +113,7 @@ func Test_LogConsumerGetsCalled(t *testing.T) { t.Fatal("never received final log message") } - assert.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) - - terminateContainerOnEnd(t, ctx, c) + require.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) } type TestLogTypeConsumer struct { @@ -157,8 +156,8 @@ func Test_ShouldRecognizeLogTypes(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) ep, err := c.Endpoint(ctx, "http") require.NoError(t, err) @@ -212,6 +211,7 @@ func Test_MultipleLogConsumers(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -226,9 +226,9 @@ func Test_MultipleLogConsumers(t *testing.T) { <-first.Done <-second.Done - assert.Equal(t, []string{"ready\n", "echo mlem\n"}, first.Msgs()) - assert.Equal(t, []string{"ready\n", "echo mlem\n"}, second.Msgs()) - require.NoError(t, c.Terminate(ctx)) + expected := []string{"ready\n", "echo mlem\n"} + require.Equal(t, expected, first.Msgs()) + require.Equal(t, expected, second.Msgs()) } func TestContainerLogWithErrClosed(t *testing.T) { @@ -251,16 +251,15 @@ func TestContainerLogWithErrClosed(t *testing.T) { dind, err := GenericContainer(ctx, GenericContainerRequest{ Started: true, ContainerRequest: ContainerRequest{ - Image: "docker.io/docker:dind", + Image: "docker:dind", ExposedPorts: []string{"2375/tcp"}, Env: map[string]string{"DOCKER_TLS_CERTDIR": ""}, WaitingFor: wait.ForListeningPort("2375/tcp"), Privileged: true, }, }) - + CleanupContainer(t, dind) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, dind) var remoteDocker string @@ -276,24 +275,20 @@ func TestContainerLogWithErrClosed(t *testing.T) { if errors.Is(err, context.DeadlineExceeded) { break } - time.Sleep(100 * time.Microsecond) + time.Sleep(10 * time.Millisecond) t.Log("retrying get endpoint") } - if err != nil { - t.Fatal("get endpoint:", err) - } + require.NoErrorf(t, err, "get endpoint") opts := []client.Opt{client.WithHost(remoteDocker), client.WithAPIVersionNegotiation()} - client, err := NewDockerClientWithOpts(ctx, opts...) - if err != nil { - t.Fatal(err) - } - defer client.Close() + dockerClient, err := NewDockerClientWithOpts(ctx, opts...) + require.NoError(t, err) + defer dockerClient.Close() provider := &DockerProvider{ - client: client, - config: ReadConfig(), + client: dockerClient, + config: config.Read(), DockerProviderOptions: &DockerProviderOptions{ GenericProviderOptions: &GenericProviderOptions{ Logger: TestLogger(t), @@ -314,18 +309,13 @@ func TestContainerLogWithErrClosed(t *testing.T) { Consumers: []LogConsumer{&consumer}, }, }) - if err != nil { - t.Fatal(err) - } - if err := nginx.Start(ctx); err != nil { - t.Fatal(err) - } - terminateContainerOnEnd(t, ctx, nginx) + require.NoError(t, err) + err = nginx.Start(ctx) + require.NoError(t, err) + CleanupContainer(t, nginx) port, err := nginx.MappedPort(ctx, "80/tcp") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Gather the initial container logs time.Sleep(time.Second * 1) @@ -333,17 +323,14 @@ func TestContainerLogWithErrClosed(t *testing.T) { hitNginx := func() { i, _, err := dind.Exec(ctx, []string{"wget", "--spider", "localhost:" + port.Port()}) - if err != nil || i > 0 { - t.Fatalf("Can't make request to nginx container from dind container") - } + require.NoError(t, err, "Can't make request to nginx container from dind container") + require.Zerof(t, i, "Can't make request to nginx container from dind container") } hitNginx() time.Sleep(time.Second * 1) msgs := consumer.Msgs() - if len(msgs)-existingLogs != 1 { - t.Fatalf("logConsumer should have 1 new log message, instead has: %v", msgs[existingLogs:]) - } + require.Equalf(t, 1, len(msgs)-existingLogs, "logConsumer should have 1 new log message, instead has: %v", msgs[existingLogs:]) existingLogs = len(consumer.Msgs()) iptableArgs := []string{ @@ -352,25 +339,21 @@ func TestContainerLogWithErrClosed(t *testing.T) { } // Simulate a transient closed connection to the docker daemon i, _, err := dind.Exec(ctx, append([]string{"iptables", "-A"}, iptableArgs...)) - if err != nil || i > 0 { - t.Fatalf("Failed to close connection to dind daemon: i(%d), err %v", i, err) - } + require.NoErrorf(t, err, "Failed to close connection to dind daemon: i(%d), err %v", i, err) + require.Zerof(t, i, "Failed to close connection to dind daemon: i(%d), err %v", i, err) i, _, err = dind.Exec(ctx, append([]string{"iptables", "-D"}, iptableArgs...)) - if err != nil || i > 0 { - t.Fatalf("Failed to re-open connection to dind daemon: i(%d), err %v", i, err) - } + require.NoErrorf(t, err, "Failed to re-open connection to dind daemon: i(%d), err %v", i, err) + require.Zerof(t, i, "Failed to re-open connection to dind daemon: i(%d), err %v", i, err) time.Sleep(time.Second * 3) hitNginx() hitNginx() time.Sleep(time.Second * 1) msgs = consumer.Msgs() - if len(msgs)-existingLogs != 2 { - t.Fatalf( - "LogConsumer should have 2 new log messages after detecting closed connection and"+ - " re-requesting logs. Instead has:\n%s", msgs[existingLogs:], - ) - } + require.Equalf(t, 2, len(msgs)-existingLogs, + "LogConsumer should have 2 new log messages after detecting closed connection and"+ + " re-requesting logs. Instead has:\n%s", msgs[existingLogs:], + ) } func TestContainerLogsShouldBeWithoutStreamHeader(t *testing.T) { @@ -380,23 +363,18 @@ func TestContainerLogsShouldBeWithoutStreamHeader(t *testing.T) { Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatal(err) - } - terminateContainerOnEnd(t, ctx, container) - r, err := container.Logs(ctx) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, ctr) + require.NoError(t, err) + + r, err := ctr.Logs(ctx) + require.NoError(t, err) defer r.Close() b, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.Equal(t, "0", strings.TrimSpace(string(b))) } @@ -429,6 +407,7 @@ func TestContainerLogsEnableAtStart(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -448,9 +427,7 @@ func TestContainerLogsEnableAtStart(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("never received final log message") } - assert.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) - - terminateContainerOnEnd(t, ctx, c) + require.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) } func Test_StartLogProductionStillStartsWithTooLowTimeout(t *testing.T) { @@ -481,8 +458,8 @@ func Test_StartLogProductionStillStartsWithTooLowTimeout(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func Test_StartLogProductionStillStartsWithTooHighTimeout(t *testing.T) { @@ -513,22 +490,44 @@ func Test_StartLogProductionStillStartsWithTooHighTimeout(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) - // because the log production timeout is too high, the container should have already been terminated - // so no need to terminate it again with "terminateContainerOnEnd(t, ctx, c)" dc := c.(*DockerContainer) require.NoError(t, dc.stopLogProduction()) +} - terminateContainerOnEnd(t, ctx, c) +// bufLogger is a Logging implementation that writes to a bytes.Buffer. +type bufLogger struct { + mtx sync.Mutex + buf bytes.Buffer +} + +// Printf implements Logging. +func (l *bufLogger) Printf(format string, v ...any) { + l.mtx.Lock() + defer l.mtx.Unlock() + + fmt.Fprintf(&l.buf, format, v...) +} + +// String returns the contents of the buffer as a string. +func (l *bufLogger) String() string { + l.mtx.Lock() + defer l.mtx.Unlock() + + return l.buf.String() } func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { - // Redirect stderr to a buffer - oldStderr := os.Stderr - r, w, _ := os.Pipe() - os.Stderr = w + // Capture global logger. + logger := &bufLogger{} + Logger = logger + oldLogger := Logger + t.Cleanup(func() { + Logger = oldLogger + }) // Context with cancellation functionality for simulating user interruption ctx, cancel := context.WithCancel(context.Background()) @@ -558,6 +557,7 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { } c, err := GenericContainer(ctx, genericReq1) + CleanupContainer(t, c) require.NoError(t, err) ep1, err := c.Endpoint(ctx, "http") @@ -593,6 +593,7 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { } c2, err := GenericContainer(ctx, genericReq2) + CleanupContainer(t, c2) require.NoError(t, err) ep2, err := c2.Endpoint(ctx, "http") @@ -604,37 +605,98 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { _, err = http.Get(ep2 + "/stdout?echo=there2") require.NoError(t, err) - // Handling the termination of the containers - defer func() { - shutdownCtx, shutdownCancel := context.WithTimeout( - context.Background(), 10*time.Second, - ) - defer shutdownCancel() - _ = c.Terminate(shutdownCtx) - _ = c2.Terminate(shutdownCtx) - }() - // Deliberately calling context cancel cancel() // We check log size due to context cancellation causing // varying message counts, leading to test failure. - assert.GreaterOrEqual(t, len(first.Msgs()), 2) - assert.GreaterOrEqual(t, len(second.Msgs()), 2) + require.GreaterOrEqual(t, len(first.Msgs()), 2) + require.GreaterOrEqual(t, len(second.Msgs()), 2) - // Restore stderr - w.Close() - os.Stderr = oldStderr + require.NotContains(t, logger.String(), "Unexpected error reading logs") +} + +// FooLogConsumer is a test log consumer that accepts logs from the +// "hello-world" Docker image, which prints out the "Hello from Docker!" +// log message. +type FooLogConsumer struct { + LogChannel chan string + t *testing.T +} + +// Accept receives a log message and sends it to the log channel if it +// contains the "Hello from Docker!" message. +func (c FooLogConsumer) Accept(rawLog Log) { + log := string(rawLog.Content) + if strings.Contains(log, "Hello from Docker!") { + select { + case c.LogChannel <- log: + default: + } + } +} + +// AssertRead waits for a log message to be received. +func (c FooLogConsumer) AssertRead() { + select { + case <-c.LogChannel: + case <-time.After(5 * time.Second): + c.t.Fatal("receive timeout") + } +} + +// SlurpOne reads a value from the channel if it is available. +func (c FooLogConsumer) SlurpOne() { + select { + case <-c.LogChannel: + default: + } +} + +func NewFooLogConsumer(t *testing.T) *FooLogConsumer { + t.Helper() - // Read the stderr output from the buffer - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) + return &FooLogConsumer{ + t: t, + LogChannel: make(chan string, 2), + } +} + +func TestRestartContainerWithLogConsumer(t *testing.T) { + logConsumer := NewFooLogConsumer(t) - // Check the stderr message - actual := buf.String() + ctx := context.Background() + ctr, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: "hello-world", + AlwaysPullImage: true, + LogConsumerCfg: &LogConsumerConfig{ + Consumers: []LogConsumer{logConsumer}, + }, + }, + Started: false, + }) + CleanupContainer(t, ctr) + require.NoError(t, err) + + // Start and confirm that the log consumer receives the log message. + err = ctr.Start(ctx) + require.NoError(t, err) + + logConsumer.AssertRead() + + // Stop the container and clear any pending message. + d := 5 * time.Second + err = ctr.Stop(ctx, &d) + require.NoError(t, err) + + logConsumer.SlurpOne() + + // Restart the container and confirm that the log consumer receives new log messages. + err = ctr.Start(ctx) + require.NoError(t, err) - // The context cancel shouldn't cause the system to throw a - // logStoppedForOutOfSyncMessage, as it hangs the system with - // the multiple containers. - assert.False(t, strings.Contains(actual, logStoppedForOutOfSyncMessage)) + // First message is from the first start. + logConsumer.AssertRead() + logConsumer.AssertRead() } diff --git a/logger.go b/logger.go index 4f77ce7b53..1a5ae5dcdb 100644 --- a/logger.go +++ b/logger.go @@ -4,13 +4,26 @@ import ( "context" "log" "os" + "strings" "testing" "github.com/docker/docker/client" ) // Logger is the default log instance -var Logger Logging = log.New(os.Stderr, "", log.LstdFlags) +var Logger Logging = &noopLogger{} + +func init() { + // Enable default logger in the testing with a verbose flag. + if testing.Testing() { + // Parse manually because testing.Verbose() panics unless flag.Parse() has done. + for _, arg := range os.Args { + if strings.EqualFold(arg, "-test.v=true") || strings.EqualFold(arg, "-v") { + Logger = log.New(os.Stderr, "", log.LstdFlags) + } + } + } +} // Validate our types implement the required interfaces. var ( @@ -25,6 +38,13 @@ type Logging interface { Printf(format string, v ...interface{}) } +type noopLogger struct{} + +// Printf implements Logging. +func (n noopLogger) Printf(format string, v ...interface{}) { + // NOOP +} + // Deprecated: this function will be removed in a future release // LogDockerServerInfo logs the docker server info using the provided logger and Docker client func LogDockerServerInfo(ctx context.Context, client client.APIClient, logger Logging) { diff --git a/mkdocs.yml b/mkdocs.yml index 6e72971c1e..47044423dc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,8 +33,8 @@ markdown_extensions: permalink: true - attr_list - pymdownx.emoji: - emoji_generator: !!python/name:materialx.emoji.to_svg - emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji nav: - Home: index.md - Quickstart: quickstart.md @@ -46,6 +46,7 @@ nav: - features/creating_networks.md - features/networking.md - features/tls.md + - features/test_session_semantics.md - features/garbage_collector.md - features/build_from_dockerfile.md - features/docker_auth.md @@ -56,24 +57,32 @@ nav: - Introduction: features/wait/introduction.md - Exec: features/wait/exec.md - Exit: features/wait/exit.md + - File: features/wait/file.md - Health: features/wait/health.md - HostPort: features/wait/host_port.md - HTTP: features/wait/http.md - Log: features/wait/log.md - Multi: features/wait/multi.md - SQL: features/wait/sql.md + - TLS: features/wait/tls.md + - Walk: features/wait/walk.md - Modules: - modules/index.md - modules/artemis.md + - modules/azurite.md - modules/cassandra.md - modules/chroma.md - modules/clickhouse.md - modules/cockroachdb.md - modules/consul.md - modules/couchbase.md + - modules/databend.md - modules/dolt.md + - modules/dynamodb.md - modules/elasticsearch.md + - modules/etcd.md - modules/gcloud.md + - modules/grafana-lgtm.md - modules/inbucket.md - modules/influxdb.md - modules/k3s.md @@ -81,6 +90,7 @@ nav: - modules/kafka.md - modules/localstack.md - modules/mariadb.md + - modules/meilisearch.md - modules/milvus.md - modules/minio.md - modules/mockserver.md @@ -101,8 +111,11 @@ nav: - modules/redpanda.md - modules/registry.md - modules/surrealdb.md + - modules/valkey.md - modules/vault.md + - modules/vearch.md - modules/weaviate.md + - modules/yugabytedb.md - Examples: - examples/index.md - examples/nginx.md @@ -123,10 +136,8 @@ nav: - system_requirements/using_colima.md - system_requirements/using_podman.md - system_requirements/rancher.md - - Contributing: - - contributing.md - - contributing_docs.md + - Contributing: contributing.md - Getting help: getting_help.md edit_uri: edit/main/docs/ extra: - latest_version: v0.30.0 + latest_version: v0.34.0 diff --git a/modulegen/_template/ci.yml.tmpl b/modulegen/_template/ci.yml.tmpl index 616dd2bee6..46fc3e3906 100644 --- a/modulegen/_template/ci.yml.tmpl +++ b/modulegen/_template/ci.yml.tmpl @@ -26,7 +26,7 @@ jobs: test: strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest, macos-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -44,7 +44,7 @@ jobs: name: "Test with reaper off" strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] uses: ./.github/workflows/ci-test-go.yml with: go-version: {{ "${{ matrix.go-version }}" }} @@ -61,7 +61,7 @@ jobs: name: "Test with Rootless Docker" strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -76,7 +76,7 @@ jobs: test-module-generator: strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml with: @@ -92,7 +92,7 @@ jobs: needs: test strategy: matrix: - go-version: [1.21.x, 1.x] + go-version: [1.22.x, 1.x] platform: [ubuntu-latest] module: [{{ .Modules }}] uses: ./.github/workflows/ci-test-go.yml @@ -112,7 +112,7 @@ jobs: module: [{{ .Examples }}] uses: ./.github/workflows/ci-test-go.yml with: - go-version: "1.21.x" + go-version: "1.22.x" fail-fast: true platform: 'ubuntu-latest' project-directory: {{ "examples/${{ matrix.module }}" }} @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: # Disabling shallow clone is recommended for improving relevancy of reporting fetch-depth: 0 diff --git a/modulegen/_template/examples_test.go.tmpl b/modulegen/_template/examples_test.go.tmpl index d386959330..ca55c61e44 100644 --- a/modulegen/_template/examples_test.go.tmpl +++ b/modulegen/_template/examples_test.go.tmpl @@ -1,4 +1,4 @@ -{{ $entrypoint := Entrypoint }}{{ $image := Image }}{{ $lower := ToLower }}{{ $title := Title }}package {{ $lower }}_test +{{ $entrypoint := Entrypoint }}{{ $image := Image }}{{ $lower := ToLower }}package {{ $lower }}_test import ( "context" @@ -10,25 +10,24 @@ import ( ) func Example{{ $entrypoint }}() { - // run{{ $title }}Container { ctx := context.Background() - {{ $lower }}Container, err := {{ $lower }}.{{ $entrypoint }}(ctx, testcontainers.WithImage("{{ $image }}")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + {{ $lower }}Container, err := {{ $lower }}.{{ $entrypoint }}(ctx, "{{ $image }}") defer func() { - if err := {{ $lower }}Container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer({{ $lower }}Container); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := {{ $lower }}Container.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modulegen/_template/module.go.tmpl b/modulegen/_template/module.go.tmpl index 2a08b01942..585e853fba 100644 --- a/modulegen/_template/module.go.tmpl +++ b/modulegen/_template/module.go.tmpl @@ -2,19 +2,20 @@ import ( "context" + "fmt" "github.com/testcontainers/testcontainers-go" ) -// {{ $containerName }} represents the {{ $title }} container type used in the module -type {{ $containerName }} struct { +// Container represents the {{ $title }} container type used in the module +type Container struct { testcontainers.Container } // {{ $entrypoint }} creates an instance of the {{ $title }} container type -func {{ $entrypoint }}(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*{{ $containerName }}, error) { +func {{ $entrypoint }}(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { req := testcontainers.ContainerRequest{ - Image: "{{ .Image }}", + Image: img, } genericContainerReq := testcontainers.GenericContainerRequest{ @@ -29,9 +30,14 @@ func {{ $entrypoint }}(ctx context.Context, opts ...testcontainers.ContainerCust } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Container + if container != nil { + c = &Container{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &{{ $containerName }}{Container: container}, nil + return c, nil } diff --git a/modulegen/_template/module.md.tmpl b/modulegen/_template/module.md.tmpl index 93825da5c3..91945bd254 100644 --- a/modulegen/_template/module.md.tmpl +++ b/modulegen/_template/module.md.tmpl @@ -1,4 +1,4 @@ -{{ $lower := ToLower }}{{ $title := Title }}# {{ $title }} +{{ $entrypoint := Entrypoint }}{{ $lower := ToLower }}{{ $title := Title }}# {{ $title }} Not available until the next release of testcontainers-go :material-tag: main @@ -17,18 +17,26 @@ go get github.com/testcontainers/testcontainers-go/{{ ParentDir }}/{{ $lower }} ## Usage example -[Creating a {{ $title }} container](../../{{ ParentDir }}/{{ $lower }}/examples_test.go) inside_block:run{{ $title }}Container +[Creating a {{ $title }} container](../../{{ ParentDir }}/{{ $lower }}/examples_test.go) inside_block:Example{{ $entrypoint }} -## Module reference +## Module Reference -The {{ $title }} module exposes one entrypoint function to create the {{ $title }} container, and this function receives two parameters: +### Run function + +- Not available until the next release of testcontainers-go :material-tag: main + +!!!info + The `RunContainer(ctx, opts...)` function is deprecated and will be removed in the next major release of _Testcontainers for Go_. + +The {{ $title }} module exposes one entrypoint function to create the {{ $title }} container, and this function receives three parameters: ```golang -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*{{ $title }}Container, error) +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*{{ $title }}Container, error) ``` - `context.Context`, the Go context. +- `string`, the Docker image to use. - `testcontainers.ContainerCustomizer`, a variadic argument for passing options. ### Container Options @@ -37,8 +45,8 @@ When starting the {{ $title }} container, you can pass options in a variadic way #### Image -If you need to set a different {{ $title }} Docker image, you can use `testcontainers.WithImage` with a valid Docker image -for {{ $title }}. E.g. `testcontainers.WithImage("{{ .Image }}")`. +If you need to set a different {{ $title }} Docker image, you can set a valid Docker image as the second argument in the `Run` function. +E.g. `Run(context.Background(), "{{ .Image }}")`. {% include "../features/common_functional_options.md" %} diff --git a/modulegen/_template/module_test.go.tmpl b/modulegen/_template/module_test.go.tmpl index 9a0f5391d5..1850e568c9 100644 --- a/modulegen/_template/module_test.go.tmpl +++ b/modulegen/_template/module_test.go.tmpl @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/{{ ParentDir }}/{{ $lower }}" ) @@ -11,17 +13,9 @@ import ( func Test{{ $title }}(t *testing.T) { ctx := context.Background() - container, err := {{ $lower }}.{{ $entrypoint }}(ctx, testcontainers.WithImage("{{ $image }}")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := {{ $lower }}.{{ $entrypoint }}(ctx, "{{ $image }}") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions } diff --git a/modulegen/context_test.go b/modulegen/context_test.go index fc56ea3c5d..4023e2ed88 100644 --- a/modulegen/context_test.go +++ b/modulegen/context_test.go @@ -11,6 +11,7 @@ import ( ) func getTestRootContext(t *testing.T) context.Context { + t.Helper() current, err := os.Getwd() require.NoError(t, err) return context.New(filepath.Dir(current)) diff --git a/modulegen/go.mod b/modulegen/go.mod index 613a7c2791..0e66fb16d7 100644 --- a/modulegen/go.mod +++ b/modulegen/go.mod @@ -1,6 +1,6 @@ module github.com/testcontainers/testcontainers-go/modulegen -go 1.21 +go 1.22 require ( github.com/spf13/cobra v1.8.0 diff --git a/modulegen/internal/context/types.go b/modulegen/internal/context/types.go index 6264579ed2..61d0e6217e 100644 --- a/modulegen/internal/context/types.go +++ b/modulegen/internal/context/types.go @@ -4,8 +4,6 @@ import ( "fmt" "regexp" "strings" - "unicode" - "unicode/utf8" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -22,26 +20,17 @@ type TestcontainersModule struct { // ContainerName returns the name of the container, which is the lower-cased title of the example // If the title is set, it will be used instead of the name func (m *TestcontainersModule) ContainerName() string { - name := m.Lower() - - if m.IsModule { - name = m.Title() - } else if m.TitleName != "" { - r, n := utf8.DecodeRuneInString(m.TitleName) - name = string(unicode.ToLower(r)) + m.TitleName[n:] - } - - return name + "Container" + return "Container" } // Entrypoint returns the name of the entrypoint function, which is the lower-cased title of the example -// If the example is a module, the entrypoint will be "RunContainer" +// If the example is a module, the entrypoint will be "Run" func (m *TestcontainersModule) Entrypoint() string { if m.IsModule { - return "RunContainer" + return "Run" } - return "runContainer" + return "run" } func (m *TestcontainersModule) Lower() string { diff --git a/modulegen/internal/mkdocs/types.go b/modulegen/internal/mkdocs/types.go index d6145c3fd6..a1131d5b12 100644 --- a/modulegen/internal/mkdocs/types.go +++ b/modulegen/internal/mkdocs/types.go @@ -35,7 +35,7 @@ type Config struct { Examples []string `yaml:"Examples,omitempty"` Modules []string `yaml:"Modules,omitempty"` SystemRequirements []interface{} `yaml:"System Requirements,omitempty"` - Contributing []string `yaml:"Contributing,omitempty"` + Contributing string `yaml:"Contributing,omitempty"` GettingHelp string `yaml:"Getting help,omitempty"` } `yaml:"nav"` EditURI string `yaml:"edit_uri"` diff --git a/modulegen/internal/mkdocs/writer.go b/modulegen/internal/mkdocs/writer.go index 4d6a685af6..e440509c2f 100644 --- a/modulegen/internal/mkdocs/writer.go +++ b/modulegen/internal/mkdocs/writer.go @@ -31,6 +31,6 @@ func overrideData(data []byte) []byte { func setEmoji(content string, key string, value string) string { old := "emoji_" + key + `: ""` - new := "emoji_" + key + ": !!python/name:materialx.emoji." + value + new := "emoji_" + key + ": !!python/name:material.extensions.emoji." + value return strings.ReplaceAll(content, old, new) } diff --git a/modulegen/main_test.go b/modulegen/main_test.go index 819cfa9ad0..d90c0da5be 100644 --- a/modulegen/main_test.go +++ b/modulegen/main_test.go @@ -17,11 +17,10 @@ import ( func TestModule(t *testing.T) { tests := []struct { - name string - module context.TestcontainersModule - expectedContainerName string - expectedEntrypoint string - expectedTitle string + name string + module context.TestcontainersModule + expectedEntrypoint string + expectedTitle string }{ { name: "Module with title", @@ -31,9 +30,8 @@ func TestModule(t *testing.T) { Image: "mongodb:latest", TitleName: "MongoDB", }, - expectedContainerName: "MongoDBContainer", - expectedEntrypoint: "RunContainer", - expectedTitle: "MongoDB", + expectedEntrypoint: "Run", + expectedTitle: "MongoDB", }, { name: "Module without title", @@ -42,9 +40,8 @@ func TestModule(t *testing.T) { IsModule: true, Image: "mongodb:latest", }, - expectedContainerName: "MongodbContainer", - expectedEntrypoint: "RunContainer", - expectedTitle: "Mongodb", + expectedEntrypoint: "Run", + expectedTitle: "Mongodb", }, { name: "Example with title", @@ -54,9 +51,8 @@ func TestModule(t *testing.T) { Image: "mongodb:latest", TitleName: "MongoDB", }, - expectedContainerName: "mongoDBContainer", - expectedEntrypoint: "runContainer", - expectedTitle: "MongoDB", + expectedEntrypoint: "run", + expectedTitle: "MongoDB", }, { name: "Example without title", @@ -65,9 +61,9 @@ func TestModule(t *testing.T) { IsModule: false, Image: "mongodb:latest", }, - expectedContainerName: "mongodbContainer", - expectedEntrypoint: "runContainer", - expectedTitle: "Mongodb", + + expectedEntrypoint: "run", + expectedTitle: "Mongodb", }, } @@ -77,7 +73,7 @@ func TestModule(t *testing.T) { assert.Equal(t, "mongodb", module.Lower()) assert.Equal(t, test.expectedTitle, module.Title()) - assert.Equal(t, test.expectedContainerName, module.ContainerName()) + assert.Equal(t, "Container", module.ContainerName()) assert.Equal(t, test.expectedEntrypoint, module.Entrypoint()) }) } @@ -148,7 +144,11 @@ func TestModule_Validate(outer *testing.T) { for _, test := range tests { outer.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expectedErr, test.module.Validate()) + if test.expectedErr != nil { + require.EqualError(t, test.module.Validate(), test.expectedErr.Error()) + } else { + require.NoError(t, test.module.Validate()) + } }) } } @@ -187,7 +187,7 @@ func TestGenerateWrongModuleName(t *testing.T) { for _, test := range tests { module := context.TestcontainersModule{ Name: test.name, - Image: "docker.io/example/" + test.name + ":latest", + Image: "example/" + test.name + ":latest", } err = internal.GenerateFiles(tmpCtx, module) @@ -231,7 +231,7 @@ func TestGenerateWrongModuleTitle(t *testing.T) { module := context.TestcontainersModule{ Name: "foo", TitleName: test.title, - Image: "docker.io/example/foo:latest", + Image: "example/foo:latest", } err = internal.GenerateFiles(tmpCtx, module) @@ -266,7 +266,7 @@ func TestGenerate(t *testing.T) { Name: "foodb4tw", TitleName: "FooDB4TheWin", IsModule: false, - Image: "docker.io/example/foodb:latest", + Image: "example/foodb:latest", } moduleNameLower := module.Lower() @@ -277,7 +277,7 @@ func TestGenerate(t *testing.T) { moduleDirFileInfo, err := os.Stat(moduleDirPath) require.NoError(t, err) // error nil implies the file exist - assert.True(t, moduleDirFileInfo.IsDir()) + require.True(t, moduleDirFileInfo.IsDir()) moduleDocFile := filepath.Join(examplesDocTmp, moduleNameLower+".md") _, err = os.Stat(moduleDocFile) @@ -322,7 +322,7 @@ func TestGenerateModule(t *testing.T) { Name: "foodb", TitleName: "FooDB", IsModule: true, - Image: "docker.io/example/foodb:latest", + Image: "example/foodb:latest", } moduleNameLower := module.Lower() @@ -333,7 +333,7 @@ func TestGenerateModule(t *testing.T) { moduleDirFileInfo, err := os.Stat(moduleDirPath) require.NoError(t, err) // error nil implies the file exist - assert.True(t, moduleDirFileInfo.IsDir()) + require.True(t, moduleDirFileInfo.IsDir()) moduleDocFile := filepath.Join(modulesDocTmp, moduleNameLower+".md") _, err = os.Stat(moduleDocFile) @@ -357,62 +357,66 @@ func TestGenerateModule(t *testing.T) { // assert content module file in the docs func assertModuleDocContent(t *testing.T, module context.TestcontainersModule, moduleDocFile string) { + t.Helper() content, err := os.ReadFile(moduleDocFile) require.NoError(t, err) lower := module.Lower() title := module.Title() + entrypoint := module.Entrypoint() data := sanitiseContent(content) - assert.Equal(t, data[0], "# "+title) + assert.Equal(t, "# "+title, data[0]) assert.Equal(t, `Not available until the next release of testcontainers-go :material-tag: main`, data[2]) assert.Equal(t, "## Introduction", data[4]) - assert.Equal(t, data[6], "The Testcontainers module for "+title+".") + assert.Equal(t, "The Testcontainers module for "+title+".", data[6]) assert.Equal(t, "## Adding this module to your project dependencies", data[8]) - assert.Equal(t, data[10], "Please run the following command to add the "+title+" module to your Go dependencies:") - assert.Equal(t, data[13], "go get github.com/testcontainers/testcontainers-go/"+module.ParentDir()+"/"+lower) + assert.Equal(t, "Please run the following command to add the "+title+" module to your Go dependencies:", data[10]) + assert.Equal(t, "go get github.com/testcontainers/testcontainers-go/"+module.ParentDir()+"/"+lower, data[13]) assert.Equal(t, "", data[18]) - assert.Equal(t, data[19], "[Creating a "+title+" container](../../"+module.ParentDir()+"/"+lower+"/examples_test.go) inside_block:run"+title+"Container") + assert.Equal(t, "[Creating a "+title+" container](../../"+module.ParentDir()+"/"+lower+"/examples_test.go) inside_block:Example"+entrypoint, data[19]) assert.Equal(t, "", data[20]) - assert.Equal(t, data[24], "The "+title+" module exposes one entrypoint function to create the "+title+" container, and this function receives two parameters:") - assert.True(t, strings.HasSuffix(data[27], "(*"+title+"Container, error)")) - assert.Equal(t, "for "+title+". E.g. `testcontainers.WithImage(\""+module.Image+"\")`.", data[40]) + assert.Equal(t, "The "+title+" module exposes one entrypoint function to create the "+title+" container, and this function receives three parameters:", data[31]) + assert.True(t, strings.HasSuffix(data[34], "(*"+title+"Container, error)")) + assert.Equal(t, "If you need to set a different "+title+" Docker image, you can set a valid Docker image as the second argument in the `Run` function.", data[47]) + assert.Equal(t, "E.g. `Run(context.Background(), \""+module.Image+"\")`.", data[48]) } // assert content module test func assertExamplesTestContent(t *testing.T, module context.TestcontainersModule, examplesTestFile string) { + t.Helper() content, err := os.ReadFile(examplesTestFile) require.NoError(t, err) lower := module.Lower() entrypoint := module.Entrypoint() - title := module.Title() data := sanitiseContent(content) assert.Equal(t, "package "+lower+"_test", data[0]) assert.Equal(t, "\t\"github.com/testcontainers/testcontainers-go\"", data[7]) assert.Equal(t, "\t\"github.com/testcontainers/testcontainers-go/modules/"+lower+"\"", data[8]) assert.Equal(t, "func Example"+entrypoint+"() {", data[11]) - assert.Equal(t, "\t// run"+title+"Container {", data[12]) - assert.Equal(t, "\t"+lower+"Container, err := "+lower+"."+entrypoint+"(ctx, testcontainers.WithImage(\""+module.Image+"\"))", data[15]) - assert.Equal(t, "\tfmt.Println(state.Running)", data[33]) - assert.Equal(t, "\t// Output:", data[35]) - assert.Equal(t, "\t// true", data[36]) + assert.Equal(t, "\t"+lower+"Container, err := "+lower+"."+entrypoint+"(ctx, \""+module.Image+"\")", data[14]) + assert.Equal(t, "\tfmt.Println(state.Running)", data[32]) + assert.Equal(t, "\t// Output:", data[34]) + assert.Equal(t, "\t// true", data[35]) } // assert content module test func assertModuleTestContent(t *testing.T, module context.TestcontainersModule, exampleTestFile string) { + t.Helper() content, err := os.ReadFile(exampleTestFile) require.NoError(t, err) data := sanitiseContent(content) assert.Equal(t, "package "+module.Lower()+"_test", data[0]) - assert.Equal(t, "func Test"+module.Title()+"(t *testing.T) {", data[10]) - assert.Equal(t, "\tcontainer, err := "+module.Lower()+"."+module.Entrypoint()+"(ctx, testcontainers.WithImage(\""+module.Image+"\"))", data[13]) + assert.Equal(t, "func Test"+module.Title()+"(t *testing.T) {", data[12]) + assert.Equal(t, "\tctr, err := "+module.Lower()+"."+module.Entrypoint()+"(ctx, \""+module.Image+"\")", data[15]) } // assert content module func assertModuleContent(t *testing.T, module context.TestcontainersModule, exampleFile string) { + t.Helper() content, err := os.ReadFile(exampleFile) require.NoError(t, err) @@ -422,19 +426,22 @@ func assertModuleContent(t *testing.T, module context.TestcontainersModule, exam entrypoint := module.Entrypoint() data := sanitiseContent(content) - assert.Equal(t, data[0], "package "+lower) - assert.Equal(t, data[8], "// "+containerName+" represents the "+exampleName+" container type used in the module") - assert.Equal(t, data[9], "type "+containerName+" struct {") - assert.Equal(t, data[13], "// "+entrypoint+" creates an instance of the "+exampleName+" container type") - assert.Equal(t, data[14], "func "+entrypoint+"(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*"+containerName+", error) {") - assert.Equal(t, data[16], "\t\tImage: \""+module.Image+"\",") - assert.Equal(t, data[25], "\t\tif err := opt.Customize(&genericContainerReq); err != nil {") - assert.Equal(t, data[26], "\t\t\treturn nil, fmt.Errorf(\"customize: %w\", err)") - assert.Equal(t, data[35], "\treturn &"+containerName+"{Container: container}, nil") + require.Equal(t, "package "+lower, data[0]) + require.Equal(t, "// Container represents the "+exampleName+" container type used in the module", data[9]) + require.Equal(t, "type "+containerName+" struct {", data[10]) + require.Equal(t, "// "+entrypoint+" creates an instance of the "+exampleName+" container type", data[14]) + require.Equal(t, "func "+entrypoint+"(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*"+containerName+", error) {", data[15]) + require.Equal(t, "\t\tImage: img,", data[17]) + require.Equal(t, "\t\tif err := opt.Customize(&genericContainerReq); err != nil {", data[26]) + require.Equal(t, "\t\t\treturn nil, fmt.Errorf(\"customize: %w\", err)", data[27]) + require.Equal(t, "\tvar c *"+containerName, data[32]) + require.Equal(t, "\t\tc = &"+containerName+"{Container: container}", data[34]) + require.Equal(t, "\treturn c, nil", data[41]) } // assert content GitHub workflow for the module func assertModuleGithubWorkflowContent(t *testing.T, moduleWorkflowFile string) { + t.Helper() content, err := os.ReadFile(moduleWorkflowFile) require.NoError(t, err) @@ -452,6 +459,7 @@ func assertModuleGithubWorkflowContent(t *testing.T, moduleWorkflowFile string) // assert content go.mod func assertGoModContent(t *testing.T, module context.TestcontainersModule, tcVersion string, goModFile string) { + t.Helper() content, err := os.ReadFile(goModFile) require.NoError(t, err) @@ -463,6 +471,7 @@ func assertGoModContent(t *testing.T, module context.TestcontainersModule, tcVer // assert content Makefile func assertMakefileContent(t *testing.T, module context.TestcontainersModule, makefile string) { + t.Helper() content, err := os.ReadFile(makefile) require.NoError(t, err) @@ -472,6 +481,7 @@ func assertMakefileContent(t *testing.T, module context.TestcontainersModule, ma // assert content in the nav items from mkdocs.yml func assertMkdocsNavItems(t *testing.T, module context.TestcontainersModule, originalConfig *mkdocs.Config, tmpCtx context.Context) { + t.Helper() config, err := mkdocs.ReadConfig(tmpCtx.MkdocsConfigFile()) require.NoError(t, err) @@ -484,7 +494,7 @@ func assertMkdocsNavItems(t *testing.T, module context.TestcontainersModule, ori expectedEntries = originalConfig.Nav[3].Modules } - assert.Len(t, navItems, len(expectedEntries)+1) + require.Len(t, navItems, len(expectedEntries)+1) // the module should be in the nav found := false diff --git a/modulegen/mkdocs_test.go b/modulegen/mkdocs_test.go index 5fcf7c93ba..96391769de 100644 --- a/modulegen/mkdocs_test.go +++ b/modulegen/mkdocs_test.go @@ -40,9 +40,9 @@ func TestReadMkDocsConfig(t *testing.T) { require.NoError(t, err) require.NotNil(t, config) - assert.Equal(t, "Testcontainers for Go", config.SiteName) - assert.Equal(t, "https://github.com/testcontainers/testcontainers-go", config.RepoURL) - assert.Equal(t, "edit/main/docs/", config.EditURI) + require.Equal(t, "Testcontainers for Go", config.SiteName) + require.Equal(t, "https://github.com/testcontainers/testcontainers-go", config.RepoURL) + require.Equal(t, "edit/main/docs/", config.EditURI) // theme theme := config.Theme @@ -51,9 +51,9 @@ func TestReadMkDocsConfig(t *testing.T) { // nav bar nav := config.Nav assert.Equal(t, "index.md", nav[0].Home) - assert.NotEmpty(t, nav[2].Features) - assert.NotEmpty(t, nav[3].Modules) - assert.NotEmpty(t, nav[4].Examples) + require.NotEmpty(t, nav[2].Features) + require.NotEmpty(t, nav[3].Modules) + require.NotEmpty(t, nav[4].Examples) } func TestNavItems(t *testing.T) { @@ -64,7 +64,7 @@ func TestNavItems(t *testing.T) { require.NoError(t, err) // we have to remove the index.md file from the examples docs - assert.Len(t, examples, len(examplesDocs)-1) + require.Len(t, examples, len(examplesDocs)-1) // all example modules exist in the documentation for _, example := range examples { @@ -82,6 +82,7 @@ func TestNavItems(t *testing.T) { } func copyInitialMkdocsConfig(t *testing.T, tmpCtx context.Context) error { + t.Helper() ctx := getTestRootContext(t) return mkdocs.CopyConfig(ctx.MkdocsConfigFile(), tmpCtx.MkdocsConfigFile()) } diff --git a/modules/Makefile b/modules/Makefile index 9f1e1d165d..450e84a882 100644 --- a/modules/Makefile +++ b/modules/Makefile @@ -10,4 +10,4 @@ lint-modules: .PHONY: tidy-modules tidy-modules: - @find . -type f -name Makefile -execdir make tools-tidy \; + @find . -type f -name Makefile -execdir make tidy \; diff --git a/modules/artemis/artemis.go b/modules/artemis/artemis.go index 33e03583cc..e74b542cd7 100644 --- a/modules/artemis/artemis.go +++ b/modules/artemis/artemis.go @@ -78,11 +78,17 @@ func WithExtraArgs(args string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead. // RunContainer creates an instance of the Artemis container type. func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) { + return Run(ctx, "apache/activemq-artemis:2.30.0-alpine", opts...) +} + +// Run creates an instance of the Artemis container type with a given image +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/apache/activemq-artemis:2.30.0-alpine", + Image: img, Env: map[string]string{ "ARTEMIS_USER": "artemis", "ARTEMIS_PASSWORD": "artemis", @@ -103,12 +109,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, req) + var c *Container + if container != nil { + c = &Container{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - user := req.Env["ARTEMIS_USER"] - password := req.Env["ARTEMIS_PASSWORD"] + c.user = req.Env["ARTEMIS_USER"] + c.password = req.Env["ARTEMIS_PASSWORD"] - return &Container{Container: container, user: user, password: password}, nil + return c, nil } diff --git a/modules/artemis/artemis_test.go b/modules/artemis/artemis_test.go index 4cf70db556..70dcab9440 100644 --- a/modules/artemis/artemis_test.go +++ b/modules/artemis/artemis_test.go @@ -57,6 +57,7 @@ func TestArtemis(t *testing.T) { user: "artemis", pass: "artemis", hook: func(t *testing.T, container *artemis.Container) { + t.Helper() expectQueue(t, container, "ArgsTestQueue") }, }, @@ -64,30 +65,30 @@ func TestArtemis(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - container, err := artemis.RunContainer(ctx, test.opts...) + ctr, err := artemis.Run(ctx, "apache/activemq-artemis:2.30.0-alpine", test.opts...) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, container.Terminate(ctx), "failed to terminate container") }) // consoleURL { - u, err := container.ConsoleURL(ctx) + u, err := ctr.ConsoleURL(ctx) // } require.NoError(t, err) res, err := http.Get(u) require.NoError(t, err, "failed to access console") res.Body.Close() - assert.Equal(t, http.StatusOK, res.StatusCode, "failed to access console") + require.Equal(t, http.StatusOK, res.StatusCode, "failed to access console") if test.user != "" { - assert.Equal(t, test.user, container.User(), "unexpected user") + assert.Equal(t, test.user, ctr.User(), "unexpected user") } if test.pass != "" { - assert.Equal(t, test.pass, container.Password(), "unexpected password") + assert.Equal(t, test.pass, ctr.Password(), "unexpected password") } // brokerEndpoint { - host, err := container.BrokerEndpoint(ctx) + host, err := ctr.BrokerEndpoint(ctx) // } require.NoError(t, err) @@ -116,7 +117,7 @@ func TestArtemis(t *testing.T) { } if test.hook != nil { - test.hook(t, container) + test.hook(t, ctr) } }) } diff --git a/modules/artemis/examples_test.go b/modules/artemis/examples_test.go index 2b17776bb1..04e973a013 100644 --- a/modules/artemis/examples_test.go +++ b/modules/artemis/examples_test.go @@ -11,27 +11,29 @@ import ( "github.com/testcontainers/testcontainers-go/modules/artemis" ) -func ExampleRunContainer() { +func ExampleRun() { // runArtemisContainer { ctx := context.Background() - artemisContainer, err := artemis.RunContainer(ctx, - testcontainers.WithImage("docker.io/apache/activemq-artemis:2.30.0"), + artemisContainer, err := artemis.Run(ctx, + "apache/activemq-artemis:2.30.0", artemis.WithCredentials("test", "test"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := artemisContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(artemisContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := artemisContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -40,7 +42,8 @@ func ExampleRunContainer() { // Get broker endpoint. host, err := artemisContainer.BrokerEndpoint(ctx) if err != nil { - log.Fatalf("failed to get broker endpoint: %s", err) + log.Printf("failed to get broker endpoint: %s", err) + return } // containerUser { @@ -53,11 +56,12 @@ func ExampleRunContainer() { // Connect to Artemis via STOMP. conn, err := stomp.Dial("tcp", host, stomp.ConnOpt.Login(user, pass)) if err != nil { - log.Fatalf("failed to connect to Artemis: %s", err) + log.Printf("failed to connect to Artemis: %s", err) + return } defer func() { if err := conn.Disconnect(); err != nil { - log.Fatalf("failed to disconnect from Artemis: %s", err) + log.Printf("failed to disconnect from Artemis: %s", err) } }() // } diff --git a/modules/artemis/go.mod b/modules/artemis/go.mod index f1a3290564..68ed4a2090 100644 --- a/modules/artemis/go.mod +++ b/modules/artemis/go.mod @@ -1,37 +1,37 @@ module github.com/testcontainers/testcontainers-go/modules/artemis -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/go-stomp/stomp/v3 v3.0.5 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -52,13 +52,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/artemis/go.sum b/modules/artemis/go.sum index eeb835cc8f..8c89aa58bb 100644 --- a/modules/artemis/go.sum +++ b/modules/artemis/go.sum @@ -4,27 +4,27 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -44,8 +44,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -56,8 +54,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -68,6 +66,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -135,26 +137,22 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -168,35 +166,32 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -205,5 +200,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/azurite/Makefile b/modules/azurite/Makefile new file mode 100644 index 0000000000..f7cdfe4179 --- /dev/null +++ b/modules/azurite/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-azurite diff --git a/modules/azurite/azurite.go b/modules/azurite/azurite.go new file mode 100644 index 0000000000..c3172fd58a --- /dev/null +++ b/modules/azurite/azurite.go @@ -0,0 +1,134 @@ +package azurite + +import ( + "context" + "fmt" + + "github.com/docker/go-connections/nat" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + // BlobPort is the default port used by Azurite + BlobPort = "10000/tcp" + // QueuePort is the default port used by Azurite + QueuePort = "10001/tcp" + // TablePort is the default port used by Azurite + TablePort = "10002/tcp" + + // defaultCredentials { + // AccountName is the default testing account name used by Azurite + AccountName string = "devstoreaccount1" + + // AccountKey is the default testing account key used by Azurite + AccountKey string = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + // } +) + +// AzuriteContainer represents the Azurite container type used in the module +type AzuriteContainer struct { + testcontainers.Container + Settings options +} + +func (c *AzuriteContainer) ServiceURL(ctx context.Context, srv Service) (string, error) { + hostname, err := c.Host(ctx) + if err != nil { + return "", err + } + + var port nat.Port + switch srv { + case BlobService: + port = BlobPort + case QueueService: + port = QueuePort + case TableService: + port = TablePort + default: + return "", fmt.Errorf("unknown service: %s", srv) + } + + mappedPort, err := c.MappedPort(ctx, port) + if err != nil { + return "", err + } + + return fmt.Sprintf("http://%s:%d", hostname, mappedPort.Int()), nil +} + +func (c *AzuriteContainer) MustServiceURL(ctx context.Context, srv Service) string { + url, err := c.ServiceURL(ctx, srv) + if err != nil { + panic(err) + } + + return url +} + +// Deprecated: use Run instead +// RunContainer creates an instance of the Azurite container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*AzuriteContainer, error) { + return Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.28.0", opts...) +} + +// Run creates an instance of the Azurite container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*AzuriteContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{BlobPort, QueuePort, TablePort}, + Env: map[string]string{}, + Entrypoint: []string{"azurite"}, + Cmd: []string{}, + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + // 1. Gather all config options (defaults and then apply provided options) + settings := defaultOptions() + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + // 2. evaluate the enabled services to apply the right wait strategy and Cmd options + enabledServices := settings.EnabledServices + if len(enabledServices) > 0 { + waitingFor := make([]wait.Strategy, 0) + for _, srv := range enabledServices { + switch srv { + case BlobService: + genericContainerReq.Cmd = append(genericContainerReq.Cmd, "--blobHost", "0.0.0.0") + waitingFor = append(waitingFor, wait.ForLog("Blob service is successfully listening")) + case QueueService: + genericContainerReq.Cmd = append(genericContainerReq.Cmd, "--queueHost", "0.0.0.0") + waitingFor = append(waitingFor, wait.ForLog("Queue service is successfully listening")) + case TableService: + genericContainerReq.Cmd = append(genericContainerReq.Cmd, "--tableHost", "0.0.0.0") + waitingFor = append(waitingFor, wait.ForLog("Table service is successfully listening")) + } + } + + if len(waitingFor) > 0 { + genericContainerReq.WaitingFor = wait.ForAll(waitingFor...) + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *AzuriteContainer + if container != nil { + c = &AzuriteContainer{Container: container, Settings: settings} + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} diff --git a/modules/azurite/azurite_test.go b/modules/azurite/azurite_test.go new file mode 100644 index 0000000000..618fc28b0b --- /dev/null +++ b/modules/azurite/azurite_test.go @@ -0,0 +1,21 @@ +package azurite_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/azurite" +) + +func TestAzurite(t *testing.T) { + ctx := context.Background() + + ctr, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.23.0") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // perform assertions +} diff --git a/modules/azurite/examples_test.go b/modules/azurite/examples_test.go new file mode 100644 index 0000000000..567685891f --- /dev/null +++ b/modules/azurite/examples_test.go @@ -0,0 +1,318 @@ +package azurite_test + +import ( + "context" + "fmt" + "io" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/azurite" +) + +func ExampleRun() { + // runAzuriteContainer { + ctx := context.Background() + + azuriteContainer, err := azurite.Run( + ctx, + "mcr.microsoft.com/azure-storage/azurite:3.28.0", + ) + defer func() { + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := azuriteContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} + +// This example demonstrates how to create a container, upload a blob, list blobs, and delete the container. +// Inspired by https://github.com/Azure/azure-sdk-for-go/blob/718000938221915fb2f3c7522d4fd09f7d74cafb/sdk/storage/azblob/examples_test.go#L36 +func ExampleRun_blobOperations() { + // blobOperations { + ctx := context.Background() + + azuriteContainer, err := azurite.Run( + ctx, + "mcr.microsoft.com/azure-storage/azurite:3.28.0", + azurite.WithInMemoryPersistence(64), + ) + defer func() { + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // using the built-in shared key credential type + cred, err := azblob.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) + if err != nil { + log.Printf("failed to create shared key credential: %s", err) + return + } + + // create an azblob.Client for the specified storage account that uses the above credentials + blobServiceURL := fmt.Sprintf("%s/%s", azuriteContainer.MustServiceURL(ctx, azurite.BlobService), azurite.AccountName) + + client, err := azblob.NewClientWithSharedKeyCredential(blobServiceURL, cred, nil) + if err != nil { + log.Printf("failed to create client: %s", err) + return + } + + // ===== 1. Create a container ===== + containerName := "testcontainer" + _, err = client.CreateContainer(context.TODO(), containerName, nil) + if err != nil { + log.Printf("failed to create container: %s", err) + return + } + + // ===== 2. Upload and Download a block blob ===== + blobData := "Hello world!" + blobName := "HelloWorld.txt" + + _, err = client.UploadStream(context.TODO(), + containerName, + blobName, + strings.NewReader(blobData), + &azblob.UploadStreamOptions{ + Metadata: map[string]*string{"Foo": to.Ptr("Bar")}, + Tags: map[string]string{"Year": "2022"}, + }) + if err != nil { + log.Printf("failed to upload blob: %s", err) + return + } + + // Download the blob's contents and ensure that the download worked properly + blobDownloadResponse, err := client.DownloadStream(context.TODO(), containerName, blobName, nil) + if err != nil { + log.Printf("failed to download blob: %s", err) + return + } + + // Use the bytes.Buffer object to read the downloaded data. + // RetryReaderOptions has a lot of in-depth tuning abilities, but for the sake of simplicity, we'll omit those here. + reader := blobDownloadResponse.Body + downloadData, err := io.ReadAll(reader) + if err != nil { + log.Printf("failed to read downloaded data: %s", err) + return + } + + fmt.Println(string(downloadData)) + + err = reader.Close() + if err != nil { + return + } + + // ===== 3. List blobs ===== + // List methods returns a pager object which can be used to iterate over the results of a paging operation. + // To iterate over a page use the NextPage(context.Context) to fetch the next page of results. + // PageResponse() can be used to iterate over the results of the specific page. + pager := client.NewListBlobsFlatPager(containerName, nil) + for pager.More() { + resp, err := pager.NextPage(context.TODO()) + if err != nil { + log.Printf("failed to list blobs: %s", err) + return + } + + fmt.Println(len(resp.Segment.BlobItems)) + } + + // Delete the blob. + _, err = client.DeleteBlob(context.TODO(), containerName, blobName, nil) + if err != nil { + log.Printf("failed to delete blob: %s", err) + return + } + + // Delete the container. + _, err = client.DeleteContainer(context.TODO(), containerName, nil) + if err != nil { + log.Printf("failed to delete container: %s", err) + return + } + + // } + + // Output: + // Hello world! + // 1 +} + +// This example demonstrates how to create, list and delete queues. +// Inspired by https://github.com/Azure/azure-sdk-for-go/blob/718000938221915fb2f3c7522d4fd09f7d74cafb/sdk/storage/azqueue/samples_test.go#L1 +func ExampleRun_queueOperations() { + // queueOperations { + ctx := context.Background() + + azuriteContainer, err := azurite.Run( + ctx, + "mcr.microsoft.com/azure-storage/azurite:3.28.0", + azurite.WithInMemoryPersistence(64), + ) + defer func() { + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // using the built-in shared key credential type + cred, err := azqueue.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) + if err != nil { + log.Printf("failed to create shared key credential: %s", err) + return + } + + // create an azqueue.Client for the specified storage account that uses the above credentials + queueServiceURL := fmt.Sprintf("%s/%s", azuriteContainer.MustServiceURL(ctx, azurite.QueueService), azurite.AccountName) + + client, err := azqueue.NewServiceClientWithSharedKeyCredential(queueServiceURL, cred, nil) + if err != nil { + log.Printf("failed to create client: %s", err) + return + } + + queueName := "testqueue" + + _, err = client.CreateQueue(context.TODO(), queueName, &azqueue.CreateOptions{ + Metadata: map[string]*string{"hello": to.Ptr("world")}, + }) + if err != nil { + log.Printf("failed to create queue: %s", err) + return + } + + pager := client.NewListQueuesPager(&azqueue.ListQueuesOptions{ + Include: azqueue.ListQueuesInclude{Metadata: true}, + }) + + // list pre-existing queues + for pager.More() { + resp, err := pager.NextPage(context.Background()) + if err != nil { + log.Printf("failed to list queues: %s", err) + return + } + + fmt.Println(len(resp.Queues)) + fmt.Println(*resp.Queues[0].Name) + } + + // delete the queue + _, err = client.DeleteQueue(context.TODO(), queueName, &azqueue.DeleteOptions{}) + if err != nil { + log.Printf("failed to delete queue: %s", err) + return + } + + // } + + // Output: + // 1 + // testqueue +} + +// This example demonstrates how to create, list and delete tables. +// Inspired by https://github.com/Azure/azure-sdk-for-go/blob/718000938221915fb2f3c7522d4fd09f7d74cafb/sdk/data/aztables/example_test.go#L1 +func ExampleRun_tableOperations() { + // tableOperations { + ctx := context.Background() + + azuriteContainer, err := azurite.Run( + ctx, + "mcr.microsoft.com/azure-storage/azurite:3.28.0", + azurite.WithInMemoryPersistence(64), + ) + defer func() { + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // using the built-in shared key credential type + cred, err := aztables.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) + if err != nil { + log.Printf("failed to create shared key credential: %s", err) + return + } + + // create an aztables.Client for the specified storage account that uses the above credentials + tablesServiceURL := fmt.Sprintf("%s/%s", azuriteContainer.MustServiceURL(ctx, azurite.TableService), azurite.AccountName) + + client, err := aztables.NewServiceClientWithSharedKey(tablesServiceURL, cred, nil) + if err != nil { + log.Printf("failed to create client: %s", err) + return + } + + tableName := "fromServiceClient" + // Create a table + _, err = client.CreateTable(context.TODO(), tableName, nil) + if err != nil { + log.Printf("failed to create table: %s", err) + return + } + + // List tables + pager := client.NewListTablesPager(nil) + for pager.More() { + resp, err := pager.NextPage(context.Background()) + if err != nil { + log.Printf("failed to list tables: %s", err) + return + } + + fmt.Println(len(resp.Tables)) + fmt.Println(*resp.Tables[0].Name) + } + + // Delete a table + _, err = client.DeleteTable(context.TODO(), tableName, nil) + if err != nil { + fmt.Println(err) + return + } + + // } + + // Output: + // 1 + // fromServiceClient +} diff --git a/modules/azurite/go.mod b/modules/azurite/go.mod new file mode 100644 index 0000000000..451d3fef9c --- /dev/null +++ b/modules/azurite/go.mod @@ -0,0 +1,69 @@ +module github.com/testcontainers/testcontainers-go/modules/azurite + +go 1.22 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 + github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 + github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 + github.com/docker/go-connections v0.5.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.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/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/azurite/go.sum b/modules/azurite/go.sum new file mode 100644 index 0000000000..abcbc8cec7 --- /dev/null +++ b/modules/azurite/go.sum @@ -0,0 +1,217 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.2.0 h1:aJG+Jxd9/rrLwf8R1Ko0RlOBTJASs/lGQJ8b9AdlKTc= +github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.2.0/go.mod h1:41ONblJrPxDcnVr+voS+3xXWy/KnZLh+7zY5s6woAlQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E= +github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 h1:lJwNFV+xYjHREUTHJKx/ZF6CJSt9znxmLw9DqSTvyRU= +github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0/go.mod h1:GfT0aGew8Qj5yiQVqOO5v7N8fanbJGyUoHqXg56qcVY= +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/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/azurite/options.go b/modules/azurite/options.go new file mode 100644 index 0000000000..8e93721360 --- /dev/null +++ b/modules/azurite/options.go @@ -0,0 +1,34 @@ +package azurite + +import ( + "fmt" + + "github.com/testcontainers/testcontainers-go" +) + +type options struct { + // EnabledServices is a list of services that should be enabled + EnabledServices []Service +} + +func defaultOptions() options { + return options{ + EnabledServices: []Service{BlobService, QueueService, TableService}, + } +} + +// WithInMemoryPersistence is a custom option to enable in-memory persistence for Azurite. +// This option is only available for Azurite v3.28.0 and later. +func WithInMemoryPersistence(megabytes float64) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + cmd := []string{"--inMemoryPersistence"} + + if megabytes > 0 { + cmd = append(cmd, "--extentMemoryLimit", fmt.Sprintf("%f", megabytes)) + } + + req.Cmd = append(req.Cmd, cmd...) + + return nil + } +} diff --git a/modules/azurite/services.go b/modules/azurite/services.go new file mode 100644 index 0000000000..a832a031f5 --- /dev/null +++ b/modules/azurite/services.go @@ -0,0 +1,9 @@ +package azurite + +const ( + BlobService Service = "blob" + QueueService Service = "queue" + TableService Service = "table" +) + +type Service string diff --git a/modules/cassandra/cassandra.go b/modules/cassandra/cassandra.go index 10df6e991c..e63d1c7e97 100644 --- a/modules/cassandra/cassandra.go +++ b/modules/cassandra/cassandra.go @@ -2,6 +2,7 @@ package cassandra import ( "context" + "fmt" "io" "path/filepath" "strings" @@ -74,10 +75,16 @@ func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the Cassandra container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CassandraContainer, error) { + return Run(ctx, "cassandra:4.1.3", opts...) +} + +// Run creates an instance of the Cassandra container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CassandraContainer, error) { req := testcontainers.ContainerRequest{ - Image: "cassandra:4.1.3", + Image: img, ExposedPorts: []string{string(port)}, Env: map[string]string{ "CASSANDRA_SNITCH": "GossipingPropertyFileSnitch", @@ -108,9 +115,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *CassandraContainer + if container != nil { + c = &CassandraContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &CassandraContainer{Container: container}, nil + return c, nil } diff --git a/modules/cassandra/cassandra_test.go b/modules/cassandra/cassandra_test.go index 831ce01d32..f4979dff5f 100644 --- a/modules/cassandra/cassandra_test.go +++ b/modules/cassandra/cassandra_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cassandra" ) @@ -20,26 +21,18 @@ type Test struct { func TestCassandra(t *testing.T) { ctx := context.Background() - container, err := cassandra.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer session.Close() // perform assertions @@ -60,24 +53,16 @@ func TestCassandra(t *testing.T) { func TestCassandraWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := cassandra.RunContainer(ctx, cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml"))) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer session.Close() var result string @@ -91,27 +76,19 @@ func TestCassandraWithInitScripts(t *testing.T) { ctx := context.Background() // withInitScripts { - container, err := cassandra.RunContainer(ctx, cassandra.WithInitScripts(filepath.Join("testdata", "init.cql"))) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.cql"))) // } - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionHost { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer session.Close() var test Test @@ -123,24 +100,16 @@ func TestCassandraWithInitScripts(t *testing.T) { t.Run("with init bash script", func(t *testing.T) { ctx := context.Background() - container, err := cassandra.RunContainer(ctx, cassandra.WithInitScripts(filepath.Join("testdata", "init.sh"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.sh"))) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer session.Close() var test Test diff --git a/modules/cassandra/examples_test.go b/modules/cassandra/examples_test.go index 97d1e6a9ec..68a80589ea 100644 --- a/modules/cassandra/examples_test.go +++ b/modules/cassandra/examples_test.go @@ -12,50 +12,53 @@ import ( "github.com/testcontainers/testcontainers-go/modules/cassandra" ) -func ExampleRunContainer() { +func ExampleRun() { // runCassandraContainer { ctx := context.Background() - cassandraContainer, err := cassandra.RunContainer(ctx, - testcontainers.WithImage("cassandra:4.1.3"), + cassandraContainer, err := cassandra.Run(ctx, + "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.cql")), cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := cassandraContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(cassandraContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := cassandraContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionHost, err := cassandraContainer.ConnectionHost(ctx) if err != nil { - log.Fatalf("failed to get connection host: %s", err) + log.Printf("failed to get connection host: %s", err) + return } cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() if err != nil { - log.Fatalf("failed to create session: %s", err) + log.Printf("failed to create session: %s", err) + return } defer session.Close() var version string err = session.Query("SELECT release_version FROM system.local").Scan(&version) if err != nil { - log.Fatalf("failed to query: %s", err) + log.Printf("failed to query: %s", err) + return } fmt.Println(version) diff --git a/modules/cassandra/go.mod b/modules/cassandra/go.mod index 2547b23c19..8b0cf9a058 100644 --- a/modules/cassandra/go.mod +++ b/modules/cassandra/go.mod @@ -1,39 +1,39 @@ module github.com/testcontainers/testcontainers-go/modules/cassandra -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/gocql/gocql v1.6.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -54,13 +54,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/cassandra/go.sum b/modules/cassandra/go.sum index 7e736011d1..4937474970 100644 --- a/modules/cassandra/go.sum +++ b/modules/cassandra/go.sum @@ -4,31 +4,31 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -46,8 +46,6 @@ github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -62,8 +60,8 @@ github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -74,6 +72,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -107,6 +107,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -141,23 +143,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -168,33 +166,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -205,5 +200,5 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/chroma/chroma.go b/modules/chroma/chroma.go index 16488b1cb5..e1c3d6e3bc 100644 --- a/modules/chroma/chroma.go +++ b/modules/chroma/chroma.go @@ -2,6 +2,7 @@ package chroma import ( "context" + "errors" "fmt" "github.com/testcontainers/testcontainers-go" @@ -13,10 +14,16 @@ type ChromaContainer struct { testcontainers.Container } +// Deprecated: use Run instead // RunContainer creates an instance of the Chroma container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ChromaContainer, error) { + return Run(ctx, "chromadb/chroma:0.4.24", opts...) +} + +// Run creates an instance of the Chroma container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ChromaContainer, error) { req := testcontainers.ContainerRequest{ - Image: "chromadb/chroma:0.4.24", + Image: img, ExposedPorts: []string{"8000/tcp"}, WaitingFor: wait.ForAll( wait.ForListeningPort("8000/tcp"), @@ -39,11 +46,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ChromaContainer + if container != nil { + c = &ChromaContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &ChromaContainer{Container: container}, nil + return c, nil } // RESTEndpoint returns the REST endpoint of the Chroma container @@ -55,7 +67,7 @@ func (c *ChromaContainer) RESTEndpoint(ctx context.Context) (string, error) { host, err := c.Host(ctx) if err != nil { - return "", fmt.Errorf("failed to get container host") + return "", errors.New("failed to get container host") } return fmt.Sprintf("http://%s:%s", host, containerPort.Port()), nil diff --git a/modules/chroma/chroma_test.go b/modules/chroma/chroma_test.go index deea10be23..5d975c3602 100644 --- a/modules/chroma/chroma_test.go +++ b/modules/chroma/chroma_test.go @@ -15,49 +15,31 @@ import ( func TestChroma(t *testing.T) { ctx := context.Background() - container, err := chroma.RunContainer(ctx, testcontainers.WithImage("chromadb/chroma:0.4.24")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("REST Endpoint retrieve docs site", func(tt *testing.T) { // restEndpoint { - restEndpoint, err := container.RESTEndpoint(ctx) + restEndpoint, err := ctr.RESTEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoErrorf(tt, err, "failed to get REST endpoint") cli := &http.Client{} resp, err := cli.Get(restEndpoint + "/docs") - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoErrorf(tt, err, "failed to perform GET request") defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equalf(tt, http.StatusOK, resp.StatusCode, "unexpected status code: %d", resp.StatusCode) }) t.Run("GetClient", func(tt *testing.T) { // restEndpoint { - endpoint, err := container.RESTEndpoint(context.Background()) - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic - } + endpoint, err := ctr.RESTEndpoint(context.Background()) + require.NoErrorf(tt, err, "failed to get REST endpoint") chromaClient, err := chromago.NewClient(endpoint) // } - if err != nil { - tt.Fatalf("failed to create client: %s", err) - } + require.NoErrorf(tt, err, "failed to create client") hb, err := chromaClient.Heartbeat(context.TODO()) require.NoError(tt, err) diff --git a/modules/chroma/examples_test.go b/modules/chroma/examples_test.go index 07bf04813c..a44125b242 100644 --- a/modules/chroma/examples_test.go +++ b/modules/chroma/examples_test.go @@ -12,26 +12,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/chroma" ) -func ExampleRunContainer() { +func ExampleRun() { // runChromaContainer { ctx := context.Background() - chromaContainer, err := chroma.RunContainer(ctx, testcontainers.WithImage("chromadb/chroma:0.4.24")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := chromaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -44,25 +44,26 @@ func ExampleChromaContainer_connectWithClient() { // createClient { ctx := context.Background() - chromaContainer, err := chroma.RunContainer(ctx, testcontainers.WithImage("chromadb/chroma:0.4.24")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := chromaContainer.RESTEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get REST endpoint: %s", err) + return } chromaClient, err := chromago.NewClient(endpoint) if err != nil { - log.Fatalf("failed to get client: %s", err) // nolint:gocritic + log.Printf("failed to get client: %s", err) + return } hbs, errHb := chromaClient.Heartbeat(context.Background()) @@ -81,33 +82,36 @@ func ExampleChromaContainer_connectWithClient() { func ExampleChromaContainer_collections() { ctx := context.Background() - chromaContainer, err := chroma.RunContainer(ctx, testcontainers.WithImage("chromadb/chroma:0.4.24"), testcontainers.WithEnv(map[string]string{"ALLOW_RESET": "true"})) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - + chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24", testcontainers.WithEnv(map[string]string{"ALLOW_RESET": "true"})) defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // getClient { // create the client connection and confirm that we can access the server with it endpoint, err := chromaContainer.RESTEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get REST endpoint: %s", err) + return } chromaClient, err := chromago.NewClient(endpoint) // } if err != nil { - log.Fatalf("failed to get client: %s", err) // nolint:gocritic + log.Printf("failed to get client: %s", err) + return } // reset { reset, err := chromaClient.Reset(context.Background()) // } if err != nil { - log.Fatalf("failed to reset: %s", err) // nolint:gocritic + log.Printf("failed to reset: %s", err) + return } fmt.Printf("Reset successful: %v\n", reset) @@ -116,7 +120,8 @@ func ExampleChromaContainer_collections() { col, err := chromaClient.CreateCollection(context.Background(), "test-collection", map[string]any{}, true, types.NewConsistentHashEmbeddingFunction(), types.L2) // } if err != nil { - log.Fatalf("failed to create collection: %s", err) // nolint:gocritic + log.Printf("failed to create collection: %s", err) + return } fmt.Println("Collection created:", col.Name) @@ -132,7 +137,8 @@ func ExampleChromaContainer_collections() { ) // } if err != nil { - log.Fatalf("failed to add data to collection: %s", err) // nolint:gocritic + log.Printf("failed to add data to collection: %s", err) + return } fmt.Println(col1.Count(context.Background())) @@ -147,7 +153,8 @@ func ExampleChromaContainer_collections() { ) // } if err != nil { - log.Fatalf("failed to query collection: %s", err) // nolint:gocritic + log.Printf("failed to query collection: %s", err) + return } fmt.Printf("Result of query: %v\n", queryResults) @@ -156,7 +163,8 @@ func ExampleChromaContainer_collections() { cols, err := chromaClient.ListCollections(context.Background()) // } if err != nil { - log.Fatalf("failed to list collections: %s", err) // nolint:gocritic + log.Printf("failed to list collections: %s", err) + return } fmt.Println(len(cols)) @@ -165,7 +173,8 @@ func ExampleChromaContainer_collections() { _, err = chromaClient.DeleteCollection(context.Background(), "test-collection") // } if err != nil { - log.Fatalf("failed to delete collection: %s", err) // nolint:gocritic + log.Printf("failed to delete collection: %s", err) + return } fmt.Println(err) diff --git a/modules/chroma/go.mod b/modules/chroma/go.mod index 008908546d..aece8e62e9 100644 --- a/modules/chroma/go.mod +++ b/modules/chroma/go.mod @@ -1,26 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/chroma -go 1.21 +go 1.22 require ( github.com/amikos-tech/chroma-go v0.1.2 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -28,12 +28,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -55,13 +55,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/chroma/go.sum b/modules/chroma/go.sum index dc3bd5f8fc..c7325de7ae 100644 --- a/modules/chroma/go.sum +++ b/modules/chroma/go.sum @@ -6,30 +6,30 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/amikos-tech/chroma-go v0.1.2 h1:ECiJ4Gn0AuJaj/jLo+FiqrKRHBVDkrDaUQVRBsEMmEQ= github.com/amikos-tech/chroma-go v0.1.2/go.mod h1:R/RUp0aaqCWdSXWyIUTfjuNymwqBGLYFgXNZEmisphY= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -45,8 +45,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -57,8 +55,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= @@ -67,6 +65,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -103,6 +103,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -136,23 +138,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -163,33 +161,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -198,5 +193,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/clickhouse/clickhouse.go b/modules/clickhouse/clickhouse.go index 5c74ffa488..43b41110b8 100644 --- a/modules/clickhouse/clickhouse.go +++ b/modules/clickhouse/clickhouse.go @@ -24,8 +24,6 @@ const ( defaultDatabaseName = "clickhouse" ) -const defaultImage = "clickhouse/clickhouse-server:23.3.8.21-alpine" - const ( // containerPorts { httpPort = nat.Port("8123/tcp") @@ -216,10 +214,16 @@ func WithUsername(user string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the ClickHouse container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ClickHouseContainer, error) { + return Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", opts...) +} + +// Run creates an instance of the ClickHouse container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ClickHouseContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, Env: map[string]string{ "CLICKHOUSE_USER": defaultUser, "CLICKHOUSE_PASSWORD": defaultUser, @@ -245,13 +249,17 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ClickHouseContainer + if container != nil { + c = &ClickHouseContainer{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - user := req.Env["CLICKHOUSE_USER"] - password := req.Env["CLICKHOUSE_PASSWORD"] - dbName := req.Env["CLICKHOUSE_DB"] + c.User = req.Env["CLICKHOUSE_USER"] + c.Password = req.Env["CLICKHOUSE_PASSWORD"] + c.DbName = req.Env["CLICKHOUSE_DB"] - return &ClickHouseContainer{Container: container, DbName: dbName, Password: password, User: user}, nil + return c, nil } diff --git a/modules/clickhouse/clickhouse_test.go b/modules/clickhouse/clickhouse_test.go index 970106a471..69d05a2abc 100644 --- a/modules/clickhouse/clickhouse_test.go +++ b/modules/clickhouse/clickhouse_test.go @@ -4,12 +4,12 @@ import ( "context" "path/filepath" "testing" + "time" ch "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/cenkalti/backoff/v4" "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -30,29 +30,23 @@ type Test struct { func TestClickHouseDefaultConfig(t *testing.T) { ctx := context.Background() - container, err := clickhouse.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ Addr: []string{connectionHost}, Auth: ch.Auth{ - Database: container.DbName, - Username: container.User, - Password: container.Password, + Database: ctr.DbName, + Username: ctr.User, + Password: ctr.Password, }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() err = conn.Ping(context.Background()) @@ -62,22 +56,17 @@ func TestClickHouseDefaultConfig(t *testing.T) { func TestClickHouseConnectionHost(t *testing.T) { ctx := context.Background() - container, err := clickhouse.RunContainer(ctx, + ctr, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionHost { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) @@ -90,68 +79,63 @@ func TestClickHouseConnectionHost(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions - data, err := performCRUD(conn) + data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseDSN(t *testing.T) { ctx := context.Background() - container, err := clickhouse.RunContainer(ctx, clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname)) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", + clickhouse.WithUsername(user), + clickhouse.WithPassword(password), + clickhouse.WithDatabase(dbname), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionString, err := container.ConnectionString(ctx, "debug=true") + connectionString, err := ctr.ConnectionString(ctx, "debug=true") // } require.NoError(t, err) opts, err := ch.ParseDSN(connectionString) require.NoError(t, err) + opts.Debugf = t.Logf conn, err := ch.Open(opts) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions - data, err := performCRUD(conn) + data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseWithInitScripts(t *testing.T) { ctx := context.Background() // withInitScripts { - container, err := clickhouse.RunContainer(ctx, + ctr, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), clickhouse.WithInitScripts(filepath.Join("testdata", "init-db.sh")), ) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) - - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -163,13 +147,13 @@ func TestClickHouseWithInitScripts(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := getAllRows(conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseWithConfigFile(t *testing.T) { @@ -184,22 +168,17 @@ func TestClickHouseWithConfigFile(t *testing.T) { } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { - container, err := clickhouse.RunContainer(ctx, + ctr, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(""), clickhouse.WithDatabase(dbname), tC.configOption, ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -211,13 +190,13 @@ func TestClickHouseWithConfigFile(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions - data, err := performCRUD(conn) + data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) }) } } @@ -236,33 +215,24 @@ func TestClickHouseWithZookeeper(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, zkcontainer) + require.NoError(t, err) ipaddr, err := zkcontainer.ContainerIP(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := clickhouse.RunContainer(ctx, + ctr, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), clickhouse.WithZookeeper(ipaddr, zkPort.Port()), ) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - require.NoError(t, zkcontainer.Terminate(ctx)) - }) - - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -274,79 +244,75 @@ func TestClickHouseWithZookeeper(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions - data, err := performReplicatedCRUD(conn) + data, err := performReplicatedCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } -func performReplicatedCRUD(conn driver.Conn) ([]Test, error) { - var ( - err error - res []Test - ) - - err = backoff.Retry(func() error { - err = conn.Exec(context.Background(), "CREATE TABLE replicated_test_table (id UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/mdb.data_transfer_cp_cdc', '{replica}') PRIMARY KEY (id) ORDER BY (id) SETTINGS index_granularity = 8192;") - if err != nil { - return err - } - - err = conn.Exec(context.Background(), "INSERT INTO replicated_test_table (id) VALUES (1);") - if err != nil { - return err - } - - rows, err := conn.Query(context.Background(), "SELECT * FROM replicated_test_table;") - if err != nil { - return err - } +func performReplicatedCRUD(t *testing.T, conn driver.Conn) ([]Test, error) { + t.Helper() + return backoff.RetryNotifyWithData( + func() ([]Test, error) { + err := conn.Exec(context.Background(), "CREATE TABLE replicated_test_table (id UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/mdb.data_transfer_cp_cdc', '{replica}') PRIMARY KEY (id) ORDER BY (id) SETTINGS index_granularity = 8192;") + if err != nil { + return nil, err + } - for rows.Next() { - var r Test + err = conn.Exec(context.Background(), "INSERT INTO replicated_test_table (id) VALUES (1);") + if err != nil { + return nil, err + } - err := rows.Scan(&r.Id) + rows, err := conn.Query(context.Background(), "SELECT * FROM replicated_test_table;") if err != nil { - return err + return nil, err } - res = append(res, r) - } - return nil - }, backoff.NewExponentialBackOff()) + var res []Test + for rows.Next() { + var r Test - return res, err -} + err := rows.Scan(&r.Id) + if err != nil { + return nil, err + } -func performCRUD(conn driver.Conn) ([]Test, error) { - var ( - err error - rows []Test + res = append(res, r) + } + return res, nil + }, + backoff.NewExponentialBackOff(), + func(err error, duration time.Duration) { + t.Log(err) + }, ) +} - err = backoff.Retry(func() error { - err = conn.Exec(context.Background(), "create table if not exists test_table (id UInt64) engine = MergeTree PRIMARY KEY (id) ORDER BY (id) SETTINGS index_granularity = 8192;") - if err != nil { - return err - } - - err = conn.Exec(context.Background(), "INSERT INTO test_table (id) VALUES (1);") - if err != nil { - return err - } - - rows, err = getAllRows(conn) - if err != nil { - return err - } +func performCRUD(t *testing.T, conn driver.Conn) ([]Test, error) { + t.Helper() + return backoff.RetryNotifyWithData( + func() ([]Test, error) { + err := conn.Exec(context.Background(), "create table if not exists test_table (id UInt64) engine = MergeTree PRIMARY KEY (id) ORDER BY (id) SETTINGS index_granularity = 8192;") + if err != nil { + return nil, err + } - return nil - }, backoff.NewExponentialBackOff()) + err = conn.Exec(context.Background(), "INSERT INTO test_table (id) VALUES (1);") + if err != nil { + return nil, err + } - return rows, err + return getAllRows(conn) + }, + backoff.NewExponentialBackOff(), + func(err error, duration time.Duration) { + t.Log(err) + }, + ) } func getAllRows(conn driver.Conn) ([]Test, error) { diff --git a/modules/clickhouse/examples_test.go b/modules/clickhouse/examples_test.go index 8eccfc7aa7..bc031f134d 100644 --- a/modules/clickhouse/examples_test.go +++ b/modules/clickhouse/examples_test.go @@ -13,7 +13,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/clickhouse" ) -func ExampleRunContainer() { +func ExampleRun() { // runClickHouseContainer { ctx := context.Background() @@ -21,39 +21,43 @@ func ExampleRunContainer() { password := "password" dbname := "testdb" - clickHouseContainer, err := clickhouse.RunContainer(ctx, - testcontainers.WithImage("clickhouse/clickhouse-server:23.3.8.21-alpine"), + clickHouseContainer, err := clickhouse.Run(ctx, + "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), clickhouse.WithInitScripts(filepath.Join("testdata", "init-db.sh")), clickhouse.WithConfigFile(filepath.Join("testdata", "config.xml")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := clickHouseContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(clickHouseContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := clickHouseContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionString, err := clickHouseContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } opts, err := ch.ParseDSN(connectionString) if err != nil { - log.Fatalf("failed to parse DSN: %s", err) + log.Printf("failed to parse DSN: %s", err) + return } fmt.Println(strings.HasPrefix(opts.ClientInfo.String(), "clickhouse-go/")) diff --git a/modules/clickhouse/go.mod b/modules/clickhouse/go.mod index a06e80cae4..2c14eff644 100644 --- a/modules/clickhouse/go.mod +++ b/modules/clickhouse/go.mod @@ -1,28 +1,28 @@ module github.com/testcontainers/testcontainers-go/modules/clickhouse -go 1.21 +go 1.22 require ( github.com/ClickHouse/clickhouse-go/v2 v2.20.0 github.com/cenkalti/backoff/v4 v4.2.1 github.com/docker/go-connections v0.5.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/ClickHouse/ch-go v0.61.3 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-faster/city v1.0.1 // indirect @@ -31,11 +31,11 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.7 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -60,13 +60,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/clickhouse/go.sum b/modules/clickhouse/go.sum index 79187a3155..f451caeaf6 100644 --- a/modules/clickhouse/go.sum +++ b/modules/clickhouse/go.sum @@ -8,29 +8,29 @@ github.com/ClickHouse/ch-go v0.61.3 h1:MmBwUhXrAOBZK7n/sWBzq6FdIQ01cuF2SaaO8KlDR github.com/ClickHouse/ch-go v0.61.3/go.mod h1:1PqXjMz/7S1ZUaKvwPA3i35W2bz2mAMFeCi6DIXgGwQ= github.com/ClickHouse/clickhouse-go/v2 v2.20.0 h1:bvlLQ31XJfl7MxIqAq2l1G6JhHYzqEXdvfpMeU6bkKc= github.com/ClickHouse/clickhouse-go/v2 v2.20.0/go.mod h1:VQfyA+tCwCRw2G7ogfY8V0fq/r0yJWzy8UDrjiP/Lbs= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -51,8 +51,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -79,6 +77,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -122,6 +122,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -163,25 +165,21 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -195,36 +193,33 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= @@ -236,5 +231,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/cockroachdb/certs.go b/modules/cockroachdb/certs.go deleted file mode 100644 index dab738192a..0000000000 --- a/modules/cockroachdb/certs.go +++ /dev/null @@ -1,67 +0,0 @@ -package cockroachdb - -import ( - "crypto/x509" - "fmt" - "net" - "time" - - "github.com/mdelapenya/tlscert" -) - -type TLSConfig struct { - CACert *x509.Certificate - NodeCert []byte - NodeKey []byte - ClientCert []byte - ClientKey []byte -} - -// 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, fmt.Errorf("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, fmt.Errorf("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, fmt.Errorf("failed to generate client certificate") - } - - return &TLSConfig{ - CACert: caCert.Cert, - NodeCert: nodeCert.Bytes, - NodeKey: nodeCert.KeyBytes, - ClientCert: clientCert.Bytes, - ClientKey: clientCert.KeyBytes, - }, nil -} diff --git a/modules/cockroachdb/cockroachdb.go b/modules/cockroachdb/cockroachdb.go index 7ab24b98fb..40da90fcd1 100644 --- a/modules/cockroachdb/cockroachdb.go +++ b/modules/cockroachdb/cockroachdb.go @@ -1,14 +1,14 @@ package cockroachdb import ( + "bytes" "context" "crypto/tls" - "crypto/x509" - "encoding/pem" + _ "embed" + "errors" "fmt" "net" "net/url" - "path/filepath" "github.com/docker/go-connections/nat" "github.com/jackc/pgx/v5" @@ -18,28 +18,74 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -var ErrTLSNotEnabled = fmt.Errorf("tls not enabled") +// ErrTLSNotEnabled is returned when trying to get a TLS config from a container that does not have TLS enabled. +var ErrTLSNotEnabled = errors.New("tls not enabled") const ( - certsDir = "/tmp" - defaultSQLPort = "26257/tcp" defaultAdminPort = "8080/tcp" - defaultImage = "cockroachdb/cockroach:latest-v23.1" defaultUser = "root" defaultPassword = "" defaultDatabase = "defaultdb" defaultStoreSize = "100%" + + // 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" + + // cert files. + certsDir = cockroachDir + "/certs" + fileCACert = certsDir + "/ca.crt" ) +//go:embed data/cluster_defaults.sql +var clusterDefaults []byte + +// defaultsReader is a reader for the default settings scripts +// so that they can be identified and removed from the request. +type defaultsReader struct { + *bytes.Reader +} + +// newDefaultsReader creates a new reader for the default cluster settings script. +func newDefaultsReader(data []byte) *defaultsReader { + return &defaultsReader{Reader: bytes.NewReader(data)} +} + // CockroachDBContainer represents the CockroachDB container type used in the module type CockroachDBContainer struct { testcontainers.Container - opts options + options +} + +// options represents the options for the CockroachDBContainer type. +type options struct { + database string + user string + password string + tlsStrategy *wait.TLSStrategy } -// MustConnectionString panics if the address cannot be determined. +// MustConnectionString returns a connection string to open a new connection to CockroachDB +// as described by [CockroachDBContainer.ConnectionString]. +// It panics if an error occurs. func (c *CockroachDBContainer) MustConnectionString(ctx context.Context) string { addr, err := c.ConnectionString(ctx) if err != nil { @@ -48,181 +94,167 @@ func (c *CockroachDBContainer) MustConnectionString(ctx context.Context) string return addr } -// ConnectionString returns the dial address to open a new connection to CockroachDB. +// ConnectionString returns a connection string to open a new connection to CockroachDB. +// The returned string is suitable for use by [sql.Open] but is not be compatible with +// [pgx.ParseConfig], so if you want to call [pgx.ConnectConfig] use the +// [CockroachDBContainer.ConnectionConfig] method instead. func (c *CockroachDBContainer) ConnectionString(ctx context.Context) (string, error) { + cfg, err := c.ConnectionConfig(ctx) + if err != nil { + return "", fmt.Errorf("connection config: %w", err) + } + + return stdlib.RegisterConnConfig(cfg), nil +} + +// ConnectionConfig returns a [pgx.ConnConfig] for the CockroachDB container. +// This can be passed to [pgx.ConnectConfig] to open a new connection. +func (c *CockroachDBContainer) ConnectionConfig(ctx context.Context) (*pgx.ConnConfig, error) { port, err := c.MappedPort(ctx, defaultSQLPort) if err != nil { - return "", err + return nil, fmt.Errorf("mapped port: %w", err) } host, err := c.Host(ctx) if err != nil { - return "", err + return nil, fmt.Errorf("host: %w", err) } - return connString(c.opts, host, port), nil + return c.connConfig(host, port) } // TLSConfig returns config necessary to connect to CockroachDB over TLS. +// Returns [ErrTLSNotEnabled] if TLS is not enabled. +// +// Deprecated: use [CockroachDBContainer.ConnectionString] or +// [CockroachDBContainer.ConnectionConfig] instead. func (c *CockroachDBContainer) TLSConfig() (*tls.Config, error) { - return connTLS(c.opts) + if cfg := c.tlsStrategy.TLSConfig(); cfg != nil { + return cfg, nil + } + + return nil, ErrTLSNotEnabled } +// Deprecated: use Run instead. // RunContainer creates an instance of the CockroachDB container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CockroachDBContainer, error) { - o := defaultOptions() + return Run(ctx, "cockroachdb/cockroach:latest-v23.1", opts...) +} + +// Run start an instance of the CockroachDB container type using the given image and options. +// +// By default, the container will configured with: +// - Cluster: Single node +// - Storage: 100% in-memory +// - User: root +// - Password: "" +// - Database: defaultdb +// - Exposed ports: 26257/tcp (SQL), 8080/tcp (Admin UI) +// - Init Scripts: `data/cluster_defaults.sql` +// +// This supports CockroachDB images v22.2.0 and later, earlier versions will only work with +// customised options, such as disabling TLS and removing the wait for `init_success` using +// a [testcontainers.ContainerCustomizer]. +// +// The init script `data/cluster_defaults.sql` configures the settings recommended +// by Cockroach Labs for [local testing clusters] unless data exists in the +// `/cockroach/cockroach-data` directory within the container. Use [WithNoClusterDefaults] +// to disable this behaviour and provide your own settings using [WithInitScripts]. +// +// For more information see starting a [local cluster in docker]. +// +// [local cluster in docker]: https://www.cockroachlabs.com/docs/stable/start-a-local-cluster-in-docker-linux +// [local testing clusters]: https://www.cockroachlabs.com/docs/stable/local-testing +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CockroachDBContainer, error) { + ctr := &CockroachDBContainer{ + options: options{ + database: defaultDatabase, + user: defaultUser, + password: defaultPassword, + }, + } req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{ defaultSQLPort, defaultAdminPort, }, - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ - { - PreStarts: []testcontainers.ContainerHook{ - func(ctx context.Context, container testcontainers.Container) error { - return addTLS(ctx, container, o) - }, - }, - }, + Env: map[string]string{ + "COCKROACH_DATABASE": defaultDatabase, + "COCKROACH_USER": defaultUser, + "COCKROACH_PASSWORD": defaultPassword, + }, + Files: []testcontainers.ContainerFile{{ + Reader: newDefaultsReader(clusterDefaults), + ContainerFilePath: clusterDefaultsContainerFile, + FileMode: 0o644, + }}, + Cmd: []string{ + "start-single-node", + memStorageFlag + defaultStoreSize, }, + WaitingFor: wait.ForAll( + wait.ForFile(cockroachDir+"/init_success"), + wait.ForHTTP("/health").WithPort(defaultAdminPort), + wait.ForTLSCert( + certsDir+"/client."+defaultUser+".crt", + certsDir+"/client."+defaultUser+".key", + ).WithRootCAs(fileCACert).WithServerName("127.0.0.1"), + wait.ForSQL(defaultSQLPort, "pgx/v5", func(host string, port nat.Port) string { + connStr, err := ctr.connString(host, port) + if err != nil { + panic(err) + } + return connStr + }), + ), }, Started: true, } - // apply options for _, opt := range opts { - if apply, ok := opt.(Option); ok { - apply(&o) - } if err := opt.Customize(&req); err != nil { - return nil, err + return nil, fmt.Errorf("customize request: %w", err) } } - // modify request - for _, fn := range []modiferFunc{ - addEnvs, - addCmd, - addWaitingFor, - } { - if err := fn(&req, o); err != nil { - return nil, err - } + if err := ctr.configure(&req); err != nil { + return nil, fmt.Errorf("set options: %w", err) } - container, err := testcontainers.GenericContainer(ctx, req) + var err error + ctr.Container, err = testcontainers.GenericContainer(ctx, req) if err != nil { - return nil, err + return ctr, fmt.Errorf("generic container: %w", err) } - return &CockroachDBContainer{Container: container, opts: o}, nil -} - -type modiferFunc func(*testcontainers.GenericContainerRequest, options) error -func addCmd(req *testcontainers.GenericContainerRequest, opts options) error { - req.Cmd = []string{ - "start-single-node", - "--store=type=mem,size=" + opts.StoreSize, - } - - // authN - if opts.TLS != nil { - if opts.User != defaultUser { - return fmt.Errorf("unsupported user %s with TLS, use %s", opts.User, defaultUser) - } - if opts.Password != "" { - return fmt.Errorf("cannot use password authentication with TLS") - } - } - - switch { - case opts.TLS != nil: - req.Cmd = append(req.Cmd, "--certs-dir="+certsDir) - case opts.Password != "": - req.Cmd = append(req.Cmd, "--accept-sql-without-tls") - default: - req.Cmd = append(req.Cmd, "--insecure") - } - return nil + return ctr, nil } -func addEnvs(req *testcontainers.GenericContainerRequest, opts options) error { - if req.Env == nil { - req.Env = make(map[string]string) +// connString returns a connection string for the given host, port and options. +func (c *CockroachDBContainer) connString(host string, port nat.Port) (string, error) { + cfg, err := c.connConfig(host, port) + if err != nil { + return "", fmt.Errorf("connection config: %w", err) } - req.Env["COCKROACH_DATABASE"] = opts.Database - req.Env["COCKROACH_USER"] = opts.User - req.Env["COCKROACH_PASSWORD"] = opts.Password - return nil + return stdlib.RegisterConnConfig(cfg), nil } -func addWaitingFor(req *testcontainers.GenericContainerRequest, opts options) error { - var tlsConfig *tls.Config - if opts.TLS != nil { - cfg, err := connTLS(opts) - if err != nil { - return err - } - tlsConfig = cfg - } - - req.WaitingFor = wait.ForAll( - wait.ForHTTP("/health").WithPort(defaultAdminPort), - wait.ForSQL(defaultSQLPort, "pgx/v5", func(host string, port nat.Port) string { - connStr := connString(opts, host, port) - if tlsConfig == nil { - return connStr - } - - // register TLS config with pgx driver - connCfg, err := pgx.ParseConfig(connStr) - if err != nil { - panic(err) - } - connCfg.TLSConfig = tlsConfig - - return stdlib.RegisterConnConfig(connCfg) - }), - ) - - return nil -} - -func addTLS(ctx context.Context, container testcontainers.Container, opts options) error { - if opts.TLS == nil { - return nil - } - - caBytes := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: opts.TLS.CACert.Raw, - }) - files := map[string][]byte{ - "ca.crt": caBytes, - "node.crt": opts.TLS.NodeCert, - "node.key": opts.TLS.NodeKey, - "client.root.crt": opts.TLS.ClientCert, - "client.root.key": opts.TLS.ClientKey, - } - for filename, contents := range files { - if err := container.CopyToContainer(ctx, contents, filepath.Join(certsDir, filename), 0o600); err != nil { - return err - } - } - return nil -} - -func connString(opts options, host string, port nat.Port) string { - user := url.User(opts.User) - if opts.Password != "" { - user = url.UserPassword(opts.User, opts.Password) +// connConfig returns a [pgx.ConnConfig] for the given host, port and options. +func (c *CockroachDBContainer) connConfig(host string, port nat.Port) (*pgx.ConnConfig, error) { + var user *url.Userinfo + if c.password != "" { + user = url.UserPassword(c.user, c.password) + } else { + user = url.User(c.user) } sslMode := "disable" - if opts.TLS != nil { + tlsConfig := c.tlsStrategy.TLSConfig() + if tlsConfig != nil { sslMode = "verify-full" } params := url.Values{ @@ -233,29 +265,57 @@ func connString(opts options, host string, port nat.Port) string { Scheme: "postgres", User: user, Host: net.JoinHostPort(host, port.Port()), - Path: opts.Database, + Path: c.database, RawQuery: params.Encode(), } - return u.String() + cfg, err := pgx.ParseConfig(u.String()) + if err != nil { + return nil, fmt.Errorf("parse config: %w", err) + } + + cfg.TLSConfig = tlsConfig + + return cfg, nil } -func connTLS(opts options) (*tls.Config, error) { - if opts.TLS == nil { - return nil, ErrTLSNotEnabled +// configure sets the CockroachDBContainer options from the given request and updates the request +// wait strategies to match the options. +func (c *CockroachDBContainer) configure(req *testcontainers.GenericContainerRequest) error { + c.database = req.Env[envDatabase] + c.user = req.Env[envUser] + c.password = req.Env[envPassword] + + var insecure bool + for _, arg := range req.Cmd { + if arg == insecureFlag { + insecure = true + break + } } - keyPair, err := tls.X509KeyPair(opts.TLS.ClientCert, opts.TLS.ClientKey) - if err != nil { - return nil, err - } + // Walk the wait strategies to find the TLS strategy and either remove it or + // update the client certificate files to match the user and configure the + // container to use the TLS strategy. + if err := wait.Walk(&req.WaitingFor, func(strategy wait.Strategy) error { + if cert, ok := strategy.(*wait.TLSStrategy); ok { + if insecure { + // If insecure mode is enabled, the certificate strategy is removed. + return errors.Join(wait.VisitRemove, wait.VisitStop) + } - certPool := x509.NewCertPool() - certPool.AddCert(opts.TLS.CACert) + // Update the client certificate files to match the user which may have changed. + cert.WithCert(certsDir+"/client."+c.user+".crt", certsDir+"/client."+c.user+".key") - return &tls.Config{ - RootCAs: certPool, - Certificates: []tls.Certificate{keyPair}, - ServerName: "localhost", - }, nil + c.tlsStrategy = cert + + // Stop the walk as the certificate strategy has been found. + return wait.VisitStop + } + return nil + }); err != nil { + return fmt.Errorf("walk strategies: %w", err) + } + + return nil } diff --git a/modules/cockroachdb/cockroachdb_test.go b/modules/cockroachdb/cockroachdb_test.go index 38d4a1388c..e3a7bb1f12 100644 --- a/modules/cockroachdb/cockroachdb_test.go +++ b/modules/cockroachdb/cockroachdb_test.go @@ -2,172 +2,94 @@ package cockroachdb_test import ( "context" - "errors" - "net/url" - "strings" + "database/sql" "testing" "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" ) -func TestCockroach_Insecure(t *testing.T) { - suite.Run(t, &AuthNSuite{ - url: "postgres://root@localhost:xxxxx/defaultdb?sslmode=disable", - }) -} +const testImage = "cockroachdb/cockroach:latest-v23.1" -func TestCockroach_NotRoot(t *testing.T) { - suite.Run(t, &AuthNSuite{ - url: "postgres://test@localhost:xxxxx/defaultdb?sslmode=disable", - opts: []testcontainers.ContainerCustomizer{ - cockroachdb.WithUser("test"), - }, - }) +func TestRun(t *testing.T) { + testContainer(t) } -func TestCockroach_Password(t *testing.T) { - suite.Run(t, &AuthNSuite{ - url: "postgres://foo:bar@localhost:xxxxx/defaultdb?sslmode=disable", - 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{ - url: "postgres://root@localhost:xxxxx/defaultdb?sslmode=verify-full", - opts: []testcontainers.ContainerCustomizer{ - cockroachdb.WithTLS(tlsCfg), - }, +func TestRun_WithInsecure(t *testing.T) { + t.Run("valid", func(t *testing.T) { + testContainer(t, cockroachdb.WithInsecure()) }) -} -type AuthNSuite struct { - suite.Suite - url string - opts []testcontainers.ContainerCustomizer -} - -func (suite *AuthNSuite) TestConnectionString() { - ctx := context.Background() - - container, err := cockroachdb.RunContainer(ctx, suite.opts...) - suite.Require().NoError(err) - - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) + t.Run("invalid-password-insecure", func(t *testing.T) { + _, err := cockroachdb.Run(context.Background(), testImage, + cockroachdb.WithPassword("testPassword"), + cockroachdb.WithInsecure(), + ) + require.Error(t, err) }) - connStr, err := removePort(container.MustConnectionString(ctx)) - suite.Require().NoError(err) - - suite.Equal(suite.url, connStr) + t.Run("invalid-insecure-password", func(t *testing.T) { + _, err := cockroachdb.Run(context.Background(), testImage, + cockroachdb.WithInsecure(), + cockroachdb.WithPassword("testPassword"), + ) + require.Error(t, 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...) - - container, err := cockroachdb.RunContainer(ctx, opts...) - suite.Require().NoError(err) - - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) - }) - - conn, err := conn(ctx, container) - suite.Require().NoError(err) - defer conn.Close(ctx) - - err = conn.Ping(ctx) - suite.Require().NoError(err) - }) - } -} +// testContainer runs a CockroachDB container and validates its functionality. +func testContainer(t *testing.T, opts ...testcontainers.ContainerCustomizer) { + t.Helper() -func (suite *AuthNSuite) TestQuery() { ctx := context.Background() + ctr, err := cockroachdb.Run(ctx, testImage, opts...) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + require.NotNil(t, ctr) - container, err := cockroachdb.RunContainer(ctx, suite.opts...) - suite.Require().NoError(err) + // Check a raw connection with a ping. + cfg, err := ctr.ConnectionConfig(ctx) + require.NoError(t, err) - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) + conn, err := pgx.ConnectConfig(ctx, cfg) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, conn.Close(ctx)) }) - conn, err := conn(ctx, container) - suite.Require().NoError(err) - defer conn.Close(ctx) + err = conn.Ping(ctx) + require.NoError(t, err) - _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") - suite.Require().NoError(err) + // Check an SQL connection with a queries. + addr, err := ctr.ConnectionString(ctx) + require.NoError(t, err) - _, err = conn.Exec(ctx, "INSERT INTO test (id) VALUES (523123)") - suite.Require().NoError(err) + db, err := sql.Open("pgx/v5", addr) + require.NoError(t, err) - var id int - err = conn.QueryRow(ctx, "SELECT id FROM test").Scan(&id) - suite.Require().NoError(err) - suite.Equal(523123, id) -} + _, 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 := pgx.ParseConfig(container.MustConnectionString(ctx)) - if err != nil { - return nil, err - } - - tlsCfg, err := container.TLSConfig() - switch { - case err != nil: - if !errors.Is(err, cockroachdb.ErrTLSNotEnabled) { - return nil, err - } - default: - // apply TLS config - cfg.TLSConfig = tlsCfg - } - - return pgx.ConnectConfig(ctx, cfg) -} + _, err = db.ExecContext(ctx, "INSERT INTO test (id) VALUES (523123)") + require.NoError(t, err) -func removePort(s string) (string, error) { - u, err := url.Parse(s) - if err != nil { - return "", err - } - return strings.Replace(s, ":"+u.Port(), ":xxxxx", 1), nil + 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/data/cluster_defaults.sql b/modules/cockroachdb/data/cluster_defaults.sql new file mode 100644 index 0000000000..78502d115e --- /dev/null +++ b/modules/cockroachdb/data/cluster_defaults.sql @@ -0,0 +1,8 @@ +SET CLUSTER SETTING kv.range_merge.queue_interval = '50ms'; +SET CLUSTER SETTING jobs.registry.interval.gc = '30s'; +SET CLUSTER SETTING jobs.registry.interval.cancel = '180s'; +SET CLUSTER SETTING jobs.retention_time = '15s'; +SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false; +SET CLUSTER SETTING kv.range_split.by_load_merge_delay = '5s'; +ALTER RANGE default CONFIGURE ZONE USING "gc.ttlseconds" = 600; +ALTER DATABASE system CONFIGURE ZONE USING "gc.ttlseconds" = 600; diff --git a/modules/cockroachdb/examples_test.go b/modules/cockroachdb/examples_test.go index 97e1d7b2dd..a1259c218b 100644 --- a/modules/cockroachdb/examples_test.go +++ b/modules/cockroachdb/examples_test.go @@ -2,48 +2,130 @@ package cockroachdb_test import ( "context" + "database/sql" "fmt" "log" - "net/url" + "github.com/jackc/pgx/v5" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cockroachdb" ) -func ExampleRunContainer() { +func ExampleRun() { // runCockroachDBContainer { ctx := context.Background() - cockroachdbContainer, err := cockroachdb.RunContainer(ctx) + cockroachdbContainer, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1") + defer func() { + if err := testcontainers.TerminateContainer(cockroachdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err != nil { - log.Fatalf("failed to start container: %s", err) + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := cockroachdbContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + fmt.Println(state.Running) + + cfg, err := cockroachdbContainer.ConnectionConfig(ctx) + if err != nil { + log.Printf("failed to get connection string: %s", err) + return + } + + conn, err := pgx.ConnectConfig(ctx, cfg) + if err != nil { + log.Printf("failed to connect: %s", err) + return } - // Clean up the container defer func() { - if err := cockroachdbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := conn.Close(ctx); err != nil { + log.Printf("failed to close connection: %s", err) } }() - // } + + if err = conn.Ping(ctx); err != nil { + log.Printf("failed to ping: %s", err) + return + } + + // Output: + // true +} + +func ExampleRun_withInitOptions() { + ctx := context.Background() + + cockroachdbContainer, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", + cockroachdb.WithNoClusterDefaults(), + cockroachdb.WithInitScripts("testdata/__init.sql"), + ) + defer func() { + if err := testcontainers.TerminateContainer(cockroachdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := cockroachdbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) addr, err := cockroachdbContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } - u, err := url.Parse(addr) + + db, err := sql.Open("pgx/v5", addr) if err != nil { - log.Fatalf("failed to parse connection string: %s", err) + log.Printf("failed to open connection: %s", err) + return } - u.Host = fmt.Sprintf("%s:%s", u.Hostname(), "xxx") - fmt.Println(u.String()) + defer func() { + if err := db.Close(); err != nil { + log.Printf("failed to close connection: %s", err) + } + }() + + var interval string + if err := db.QueryRow("SHOW CLUSTER SETTING kv.range_merge.queue_interval").Scan(&interval); err != nil { + log.Printf("failed to scan row: %s", err) + return + } + fmt.Println(interval) + + if err := db.QueryRow("SHOW CLUSTER SETTING jobs.registry.interval.gc").Scan(&interval); err != nil { + log.Printf("failed to scan row: %s", err) + return + } + fmt.Println(interval) + + var statsCollectionEnabled bool + if err := db.QueryRow("SHOW CLUSTER SETTING sql.stats.automatic_collection.enabled").Scan(&statsCollectionEnabled); err != nil { + log.Printf("failed to scan row: %s", err) + return + } + fmt.Println(statsCollectionEnabled) // Output: // true - // postgres://root@localhost:xxx/defaultdb?sslmode=disable + // 00:00:05 + // 00:00:50 + // true } diff --git a/modules/cockroachdb/go.mod b/modules/cockroachdb/go.mod index 9b9baa826c..cf31a35616 100644 --- a/modules/cockroachdb/go.mod +++ b/modules/cockroachdb/go.mod @@ -1,42 +1,46 @@ module github.com/testcontainers/testcontainers-go/modules/cockroachdb -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/jackc/pgx/v5 v5.5.4 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + github.com/containerd/platforms v0.2.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + golang.org/x/net v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect 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 @@ -57,15 +61,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index 056c3bdd27..3877e20a9a 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -41,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -61,8 +59,8 @@ 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -71,8 +69,8 @@ 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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -106,6 +104,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -140,23 +140,21 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -167,33 +165,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -202,5 +197,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/cockroachdb/options.go b/modules/cockroachdb/options.go index a2211d77e7..9efac532c6 100644 --- a/modules/cockroachdb/options.go +++ b/modules/cockroachdb/options.go @@ -1,69 +1,119 @@ package cockroachdb -import "github.com/testcontainers/testcontainers-go" - -type options struct { - Database string - User string - Password string - StoreSize string - TLS *TLSConfig -} +import ( + "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") -func defaultOptions() options { - return options{ - User: defaultUser, - Password: defaultPassword, - Database: defaultDatabase, - StoreSize: defaultStoreSize, +// 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 { + req.Env[envDatabase] = strings.ToLower(database) + return nil } } -// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. -var _ testcontainers.ContainerCustomizer = (*Option)(nil) +// 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 { + req.Env[envUser] = strings.ToLower(user) + return nil + } +} -// Option is an option for the CockroachDB container. -type Option func(*options) +// 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. +// This will error if insecure mode is enabled. +func WithPassword(password string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + for _, arg := range req.Cmd { + if arg == insecureFlag { + return errInsecureWithPassword + } + } -// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. -func (o Option) Customize(*testcontainers.GenericContainerRequest) error { - // NOOP to satisfy interface. - return nil -} + req.Env[envPassword] = password -// WithDatabase sets the name of the database to use. -func WithDatabase(database string) Option { - return func(o *options) { - o.Database = database + return nil } } -// WithUser creates & sets the user to connect as. -func WithUser(user string) Option { - return func(o *options) { - o.User = user +// WithStoreSize sets the amount of available [in-memory storage]. +// +// [in-memory storage]: https://www.cockroachlabs.com/docs/stable/cockroach-start#store +func WithStoreSize(size string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + for i, cmd := range req.Cmd { + if strings.HasPrefix(cmd, memStorageFlag) { + req.Cmd[i] = memStorageFlag + size + return nil + } + } + + // Wasn't found, add it. + req.Cmd = append(req.Cmd, memStorageFlag+size) + + return nil } } -// WithPassword sets the password when using password authentication. -func WithPassword(password string) Option { - return func(o *options) { - o.Password = password +// WithNoClusterDefaults disables the default cluster settings script. +// +// Without this option Cockroach containers run `data/cluster-defaults.sql` on startup +// which configures the settings recommended by Cockroach Labs for [local testing clusters] +// unless data exists in the `/cockroach/cockroach-data` directory within the container. +// +// [local testing clusters]: https://www.cockroachlabs.com/docs/stable/local-testing +func WithNoClusterDefaults() testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + for i, file := range req.Files { + if _, ok := file.Reader.(*defaultsReader); ok && file.ContainerFilePath == clusterDefaultsContainerFile { + req.Files = append(req.Files[:i], req.Files[i+1:]...) + return nil + } + } + + return nil } } -// WithStoreSize sets the amount of available in-memory storage. -// See https://www.cockroachlabs.com/docs/stable/cockroach-start#store -func WithStoreSize(size string) Option { - return func(o *options) { - o.StoreSize = size +// 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. +func WithInitScripts(scripts ...string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + files := make([]testcontainers.ContainerFile, len(scripts)) + for i, script := range scripts { + files[i] = testcontainers.ContainerFile{ + HostFilePath: script, + ContainerFilePath: initDBPath + "/" + filepath.Base(script), + FileMode: 0o644, + } + } + req.Files = append(req.Files, files...) + + return nil } } -// WithTLS enables TLS on the CockroachDB container. -// Cert and key must be PEM-encoded. -func WithTLS(cfg *TLSConfig) Option { - return func(o *options) { - o.TLS = cfg +// WithInsecure enables insecure mode which 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/cockroachdb/testdata/__init.sql b/modules/cockroachdb/testdata/__init.sql new file mode 100644 index 0000000000..c2c82dd48a --- /dev/null +++ b/modules/cockroachdb/testdata/__init.sql @@ -0,0 +1 @@ +SET CLUSTER SETTING jobs.registry.interval.gc = '50s'; diff --git a/modules/compose/compose.go b/modules/compose/compose.go index d1b8eb6b95..be829f4575 100644 --- a/modules/compose/compose.go +++ b/modules/compose/compose.go @@ -32,6 +32,7 @@ type composeStackOptions struct { Paths []string temporaryPaths map[string]bool Logger testcontainers.Logging + Profiles []string } type ComposeStackOption interface { @@ -116,6 +117,11 @@ func WithStackReaders(readers ...io.Reader) ComposeStackOption { return ComposeStackReaders(readers) } +// WithProfiles allows to enable/disable services based on the profiles defined in the compose file. +func WithProfiles(profiles ...string) ComposeStackOption { + return ComposeProfiles(profiles) +} + func NewDockerCompose(filePaths ...string) (*dockerCompose, error) { return NewDockerComposeWith(WithStackFiles(filePaths...)) } @@ -125,11 +131,12 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { Identifier: uuid.New().String(), temporaryPaths: make(map[string]bool), Logger: testcontainers.Logger, + Profiles: nil, } for i := range opts { if err := opts[i].applyToComposeStack(&composeOptions); err != nil { - return nil, err + return nil, fmt.Errorf("apply compose stack option: %w", err) } } @@ -139,44 +146,34 @@ func NewDockerComposeWith(opts ...ComposeStackOption) (*dockerCompose, error) { dockerCli, err := command.NewDockerCli() if err != nil { - return nil, err + return nil, fmt.Errorf("new docker client: %w", err) } if err = dockerCli.Initialize(flags.NewClientOptions(), command.WithInitializeClient(makeClient)); err != nil { - return nil, err + return nil, fmt.Errorf("initialize docker client: %w", err) } - reaperProvider, err := testcontainers.NewDockerProvider() + provider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(composeOptions.Logger)) if err != nil { - return nil, fmt.Errorf("failed to create reaper provider for compose: %w", err) + return nil, fmt.Errorf("new docker provider: %w", err) } - tcConfig := reaperProvider.Config() - - var composeReaper *testcontainers.Reaper - if !tcConfig.RyukDisabled { - // NewReaper is deprecated: we need to find a way to create the reaper for compose - // bypassing the deprecation. - r, err := testcontainers.NewReaper(context.Background(), testcontainers.SessionID(), reaperProvider, "") - if err != nil { - return nil, fmt.Errorf("failed to create reaper for compose: %w", err) - } - - composeReaper = r - } + dockerClient := dockerCli.Client() + provider.SetClient(dockerClient) composeAPI := &dockerCompose{ name: composeOptions.Identifier, configs: composeOptions.Paths, temporaryConfigs: composeOptions.temporaryPaths, logger: composeOptions.Logger, + projectProfiles: composeOptions.Profiles, composeService: compose.NewComposeService(dockerCli), - dockerClient: dockerCli.Client(), + dockerClient: dockerClient, waitStrategies: make(map[string]wait.Strategy), containers: make(map[string]*testcontainers.DockerContainer), networks: make(map[string]*testcontainers.DockerNetwork), sessionID: testcontainers.SessionID(), - reaper: composeReaper, + provider: provider, } return composeAPI, nil diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index ac52d1b335..45dd72c6e0 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -2,6 +2,7 @@ package compose import ( "context" + "errors" "fmt" "io" "os" @@ -16,14 +17,14 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/pkg/api" - dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + dockernetwork "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "golang.org/x/sync/errgroup" - testcontainers "github.com/testcontainers/testcontainers-go" - wait "github.com/testcontainers/testcontainers-go/wait" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" ) type stackUpOptionFunc func(s *stackUpOptions) @@ -32,14 +33,6 @@ func (f stackUpOptionFunc) applyToStackUp(o *stackUpOptions) { f(o) } -//nolint:unused -type stackDownOptionFunc func(do *api.DownOptions) - -//nolint:unused -func (f stackDownOptionFunc) applyToStackDown(do *api.DownOptions) { - f(do) -} - // RunServices is comparable to 'docker compose run' as it only creates a subset of containers // instead of all services defined by the project func RunServices(serviceNames ...string) StackUpOption { @@ -126,21 +119,21 @@ func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) error { for i, reader := range r { tmp := os.TempDir() tmp = filepath.Join(tmp, strconv.FormatInt(time.Now().UnixNano(), 10)) - err := os.MkdirAll(tmp, 0755) + err := os.MkdirAll(tmp, 0o755) if err != nil { - return fmt.Errorf("failed to create temporary directory: %w", err) + return fmt.Errorf("create temporary directory: %w", err) } name := fmt.Sprintf(baseName, i) bs, err := io.ReadAll(reader) if err != nil { - fmt.Errorf("failed to read from reader: %w", err) + return fmt.Errorf("read from reader: %w", err) } - err = os.WriteFile(filepath.Join(tmp, name), bs, 0644) + err = os.WriteFile(filepath.Join(tmp, name), bs, 0o644) if err != nil { - fmt.Errorf("failed to write to temporary file: %w", err) + return fmt.Errorf("write to temporary file: %w", err) } f[i] = filepath.Join(tmp, name) @@ -149,7 +142,7 @@ func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) error { o.temporaryPaths[f[i]] = true } - o.Paths = f + o.Paths = append(o.Paths, f...) return nil } @@ -157,7 +150,14 @@ func (r ComposeStackReaders) applyToComposeStack(o *composeStackOptions) error { type ComposeStackFiles []string func (f ComposeStackFiles) applyToComposeStack(o *composeStackOptions) error { - o.Paths = f + o.Paths = append(o.Paths, f...) + return nil +} + +type ComposeProfiles []string + +func (p ComposeProfiles) applyToComposeStack(o *composeStackOptions) error { + o.Profiles = append(o.Profiles, p...) return nil } @@ -200,8 +200,8 @@ type dockerCompose struct { // only one strategy can be added to a service, to use multiple use wait.ForAll(...) waitStrategies map[string]wait.Strategy - // used to synchronise writes to the containers map - containersLock sync.RWMutex + // Used to synchronise writes to the containers. + containersLock sync.Mutex // cache for containers that are part of the stack // used in ServiceContainer(...) function to avoid calls to the Docker API @@ -220,6 +220,9 @@ type dockerCompose struct { // e.g. environment settings, ... projectOptions []cli.ProjectOptionsFn + // profiles applied to the compose project after compilation. + projectProfiles []string + // compiled compose project // can be nil if the stack wasn't started yet project *types.Project @@ -227,8 +230,8 @@ type dockerCompose struct { // sessionID is used to identify the reaper session sessionID string - // reaper is used to clean up containers after the stack is stopped - reaper *testcontainers.Reaper + // provider is used to docker operations. + provider *testcontainers.DockerProvider } func (d *dockerCompose) ServiceContainer(ctx context.Context, svcName string) (*testcontainers.DockerContainer, error) { @@ -267,12 +270,10 @@ func (d *dockerCompose) Down(ctx context.Context, opts ...StackDownOption) error return d.composeService.Down(ctx, d.name, options.DownOptions) } -func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { +func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) (err error) { d.lock.Lock() defer d.lock.Unlock() - var err error - d.project, err = d.compileProject(ctx) if err != nil { return err @@ -319,7 +320,7 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { }, }) if err != nil { - return err + return fmt.Errorf("compose up: %w", err) } err = d.lookupNetworks(ctx) @@ -327,27 +328,57 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { return err } - if d.reaper != nil { + var termSignals []chan bool + var reaper *testcontainers.Reaper + if !d.provider.Config().Config.RyukDisabled { + // NewReaper is deprecated: we need to find a way to create the reaper for compose + // bypassing the deprecation. + reaper, err = testcontainers.NewReaper(ctx, testcontainers.SessionID(), d.provider, "") + if err != nil { + return fmt.Errorf("create reaper: %w", err) + } + + // Cleanup on error, otherwise set termSignal to nil before successful return. + defer func() { + if len(termSignals) == 0 { + // Need to call Connect at least once to ensure the initial + // connection is cleaned up. + termSignal, errc := reaper.Connect() + if errc != nil { + err = errors.Join(err, fmt.Errorf("reaper connect: %w", errc)) + } else { + termSignal <- true + } + } + + if err == nil { + // No need to cleanup. + return + } + + for _, ts := range termSignals { + ts <- true + } + }() + + // Connect to the reaper and set the termination signal for each network. for _, n := range d.networks { - termSignal, err := d.reaper.Connect() + termSignal, err := reaper.Connect() if err != nil { - return fmt.Errorf("failed to connect to reaper: %w", err) + return fmt.Errorf("reaper connect: %w", err) } - n.SetTerminationSignal(termSignal) - // Cleanup on error, otherwise set termSignal to nil before successful return. - defer func() { - if termSignal != nil { - termSignal <- true - } - }() + n.SetTerminationSignal(termSignal) + termSignals = append(termSignals, termSignal) } } errGrpContainers, errGrpCtx := errgroup.WithContext(ctx) + // Lookup the containers for each service and connect them + // to the reaper if needed. + var termSignalsMtx sync.Mutex for _, srv := range d.project.Services { - // we are going to connect each container to the reaper srv := srv errGrpContainers.Go(func() error { dc, err := d.lookupContainer(errGrpCtx, srv.Name) @@ -355,23 +386,19 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { return err } - if d.reaper != nil { - termSignal, err := d.reaper.Connect() + if reaper != nil { + termSignal, err := reaper.Connect() if err != nil { - return fmt.Errorf("failed to connect to reaper: %w", err) + return fmt.Errorf("reaper connect: %w", err) } + dc.SetTerminationSignal(termSignal) - // Cleanup on error, otherwise set termSignal to nil before successful return. - defer func() { - if termSignal != nil { - termSignal <- true - } - }() + termSignalsMtx.Lock() + defer termSignalsMtx.Unlock() + termSignals = append(termSignals, termSignal) } - d.containers[srv.Name] = dc - return nil }) } @@ -397,14 +424,15 @@ func (d *dockerCompose) Up(ctx context.Context, opts ...StackUpOption) error { return err } - // cache all the containers on compose.up - d.containers[svc] = target - return strategy.WaitUntilReady(errGrpCtx, target) }) } - return errGrpWait.Wait() + if err := errGrpWait.Wait(); err != nil { + return fmt.Errorf("wait for services: %w", err) + } + + return nil } func (d *dockerCompose) WaitForService(s string, strategy wait.Strategy) ComposeStack { @@ -431,65 +459,60 @@ func (d *dockerCompose) WithOsEnv() ComposeStack { return d } -func (d *dockerCompose) lookupContainer(ctx context.Context, svcName string) (*testcontainers.DockerContainer, error) { +// cachedContainer returns the cached container for svcName or nil if it doesn't exist. +func (d *dockerCompose) cachedContainer(svcName string) *testcontainers.DockerContainer { d.containersLock.Lock() defer d.containersLock.Unlock() - if container, ok := d.containers[svcName]; ok { - return container, nil + return d.containers[svcName] +} + +// lookupContainer is used to retrieve the container instance from the cache or the Docker API. +// +// Safe for concurrent calls. +func (d *dockerCompose) lookupContainer(ctx context.Context, svcName string) (*testcontainers.DockerContainer, error) { + if c := d.cachedContainer(svcName); c != nil { + return c, nil } - listOptions := container.ListOptions{ + containers, err := d.dockerClient.ContainerList(ctx, container.ListOptions{ All: true, Filters: filters.NewArgs( filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, d.name)), filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, svcName)), ), - } - - containers, err := d.dockerClient.ContainerList(ctx, listOptions) + }) if err != nil { - return nil, err + return nil, fmt.Errorf("container list: %w", err) } if len(containers) == 0 { return nil, fmt.Errorf("no container found for service name %s", svcName) } - containerInstance := containers[0] - container := &testcontainers.DockerContainer{ - ID: containerInstance.ID, - Image: containerInstance.Image, - } - container.SetLogger(d.logger) - - dockerProvider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(d.logger)) + ctr, err := d.provider.ContainerFromType(ctx, containers[0]) if err != nil { - return nil, err + return nil, fmt.Errorf("container from type: %w", err) } - dockerProvider.SetClient(d.dockerClient) - - container.SetProvider(dockerProvider) - - d.containers[svcName] = container + d.containersLock.Lock() + defer d.containersLock.Unlock() + d.containers[svcName] = ctr - return container, nil + return ctr, nil } +// lookupNetworks is used to retrieve the networks that are part of the compose stack. +// +// Safe for concurrent calls. func (d *dockerCompose) lookupNetworks(ctx context.Context) error { - d.containersLock.Lock() - defer d.containersLock.Unlock() - - listOptions := dockertypes.NetworkListOptions{ + networks, err := d.dockerClient.NetworkList(ctx, dockernetwork.ListOptions{ Filters: filters.NewArgs( filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, d.name)), ), - } - - networks, err := d.dockerClient.NetworkList(ctx, listOptions) + }) if err != nil { - return err + return fmt.Errorf("network list: %w", err) } for _, n := range networks { @@ -514,12 +537,19 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err compiledOptions, err := cli.NewProjectOptions(d.configs, projectOptions...) if err != nil { - return nil, err + return nil, fmt.Errorf("new project options: %w", err) } proj, err := compiledOptions.LoadProject(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("load project: %w", err) + } + + if len(d.projectProfiles) > 0 { + proj, err = proj.WithProfiles(d.projectProfiles) + if err != nil { + return nil, fmt.Errorf("with profiles: %w", err) + } } for i, s := range proj.Services { @@ -532,9 +562,7 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err api.OneoffLabel: "False", // default, will be overridden by `run` command } - for k, label := range testcontainers.GenericLabels() { - s.CustomLabels[k] = label - } + testcontainers.AddGenericLabels(s.CustomLabels) for i, envFile := range compiledOptions.EnvFiles { // add a label for each env file, indexed by its position @@ -551,9 +579,7 @@ func (d *dockerCompose) compileProject(ctx context.Context) (*types.Project, err api.VersionLabel: api.ComposeVersion, } - for k, label := range testcontainers.GenericLabels() { - n.Labels[k] = label - } + testcontainers.AddGenericLabels(n.Labels) proj.Networks[key] = n } @@ -578,7 +604,7 @@ func withEnv(env map[string]string) func(*cli.ProjectOptions) error { func makeClient(*command.DockerCli) (client.APIClient, error) { dockerClient, err := testcontainers.NewDockerClientWithOpts(context.Background()) if err != nil { - return nil, err + return nil, fmt.Errorf("new docker client: %w", err) } return dockerClient, nil } diff --git a/modules/compose/compose_api_test.go b/modules/compose/compose_api_test.go index 1a203347ec..808433f513 100644 --- a/modules/compose/compose_api_test.go +++ b/modules/compose/compose_api_test.go @@ -2,7 +2,7 @@ package compose import ( "context" - "fmt" + "encoding/hex" "hash/fnv" "os" "path/filepath" @@ -27,14 +27,18 @@ func TestDockerComposeAPI(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - require.NoError(t, compose.Up(ctx, Wait(true)), "compose.Up()") + err = compose.Up(ctx, Wait(true)) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") + + for _, service := range compose.Services() { + container, err := compose.ServiceContainer(context.Background(), service) + require.NoError(t, err, "compose.ServiceContainer()") + require.True(t, container.IsRunning()) + } } func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { @@ -42,10 +46,6 @@ func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -53,13 +53,12 @@ func TestDockerComposeAPIStrategyForInvalidService(t *testing.T) { // Appending with _1 as given in the Java Test-Containers Example WaitForService("non-existent-srv-1", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) - - require.Error(t, err, "Expected error to be thrown because service with wait strategy is not running") - require.Equal(t, "no container found for service name non-existent-srv-1", err.Error()) + cleanup(t, compose) + require.EqualError(t, err, "wait for services: no container found for service name non-existent-srv-1") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -68,24 +67,20 @@ func TestDockerComposeAPIWithWaitLogStrategy(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose. WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithRunServices(t *testing.T) { @@ -93,17 +88,13 @@ func TestDockerComposeAPIWithRunServices(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose. WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true), RunServices("api-nginx")) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() @@ -111,33 +102,82 @@ func TestDockerComposeAPIWithRunServices(t *testing.T) { _, err = compose.ServiceContainer(context.Background(), "api-mysql") require.Error(t, err, "Make sure there is no mysql container") - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } +func TestDockerComposeAPIWithProfiles(t *testing.T) { + path := RenderComposeProfiles(t) + + testcases := map[string]struct { + withProfiles []string + wantServices []string + }{ + "nil profile": { + withProfiles: nil, + wantServices: []string{"starts-always"}, + }, + "no profiles": { + withProfiles: []string{}, + wantServices: []string{"starts-always"}, + }, + "dev profile": { + withProfiles: []string{"dev"}, + wantServices: []string{"starts-always", "only-dev", "dev-or-test"}, + }, + "test profile": { + withProfiles: []string{"test"}, + wantServices: []string{"starts-always", "dev-or-test"}, + }, + "wildcard profile": { + withProfiles: []string{"*"}, + wantServices: []string{"starts-always", "only-dev", "dev-or-test", "only-prod"}, + }, + "undefined profile": { + withProfiles: []string{"undefined-profile"}, + wantServices: []string{"starts-always"}, + }, + } + + for name, test := range testcases { + t.Run(name, func(t *testing.T) { + compose, err := NewDockerComposeWith(WithStackFiles(path), WithProfiles(test.withProfiles...)) + require.NoError(t, err, "NewDockerCompose()") + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + for _, service := range test.wantServices { + compose = compose.WaitForService(service, wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)).(*dockerCompose) + } + err = compose.Up(ctx, Wait(true)) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") + + assert.ElementsMatch(t, test.wantServices, compose.Services()) + }) + } +} + func TestDockerComposeAPI_TestcontainersLabelsArePresent(t *testing.T) { path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose. WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") // all the services in the compose has the Testcontainers Labels for _, serviceName := range serviceNames { @@ -173,14 +213,14 @@ func TestDockerComposeAPI_WithReaper(t *testing.T) { err = compose. WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPI_WithoutReaper(t *testing.T) { @@ -193,10 +233,6 @@ func TestDockerComposeAPI_WithoutReaper(t *testing.T) { path, _ := RenderComposeComplex(t) compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - // because reaper is disabled, we need to manually stop the containers - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -204,14 +240,14 @@ func TestDockerComposeAPI_WithoutReaper(t *testing.T) { err = compose. WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second).WithOccurrence(1)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithStopServices(t *testing.T) { @@ -221,20 +257,18 @@ func TestDockerComposeAPIWithStopServices(t *testing.T) { WithLogger(testcontainers.TestLogger(t))) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - require.NoError(t, compose.Up(ctx, Wait(true)), "compose.Up()") + err = compose.Up(ctx, Wait(true)) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") // close mysql container in purpose mysqlContainer, err := compose.ServiceContainer(context.Background(), "api-mysql") @@ -256,10 +290,6 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -269,12 +299,12 @@ func TestDockerComposeAPIWithWaitForService(t *testing.T) { }). WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -283,10 +313,6 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -296,12 +322,12 @@ func TestDockerComposeAPIWithWaitHTTPStrategy(t *testing.T) { }). WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -310,10 +336,6 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -323,12 +345,12 @@ func TestDockerComposeAPIWithContainerName(t *testing.T) { }). WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -337,22 +359,18 @@ func TestDockerComposeAPIWithWaitStrategy_NoExposedPorts(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose. WaitForService("api-nginx", wait.ForLog("Configuration complete; ready for start up")). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -361,10 +379,6 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -372,14 +386,14 @@ func TestDockerComposeAPIWithMultipleWaitStrategies(t *testing.T) { WaitForService("api-mysql", wait.NewLogStrategy("started").WithStartupTimeout(10*time.Second)). WaitForService("api-nginx", wait.NewHTTPStrategy("/").WithPort("80/tcp").WithStartupTimeout(10*time.Second)). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { @@ -387,10 +401,6 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -400,14 +410,14 @@ func TestDockerComposeAPIWithFailedStrategy(t *testing.T) { }). WaitForService("api-nginx_1", wait.NewHTTPStrategy("/").WithPort("8080/tcp").WithStartupTimeout(5*time.Second)). Up(ctx, Wait(true)) - + cleanup(t, compose) // Verify that an error is thrown and not nil // A specific error message matcher is not asserted since the docker library can change the return message, breaking this test require.Error(t, err, "Expected error to be thrown because of a wrong suplied wait strategy") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") } @@ -416,29 +426,27 @@ func TestDockerComposeAPIComplex(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - require.NoError(t, compose.Up(ctx, Wait(true)), "compose.Up()") + err = compose.Up(ctx, Wait(true)) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 2) - assert.Contains(t, serviceNames, "api-nginx") - assert.Contains(t, serviceNames, "api-mysql") + require.Len(t, serviceNames, 2) + require.Contains(t, serviceNames, "api-nginx") + require.Contains(t, serviceNames, "api-mysql") } func TestDockerComposeAPIWithStackReader(t *testing.T) { identifier := testNameHash(t.Name()) - composeContent := `version: '3.7' + composeContent := ` services: api-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine environment: bar: ${bar} foo: ${foo} @@ -456,14 +464,15 @@ services: "bar": "BAR", }). Up(ctx, Wait(true)) + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") + require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveVolumes(true), RemoveImagesLocal), "compose.Down()") // check files where removed f, err := os.Stat(compose.configs[0]) @@ -471,6 +480,51 @@ services: require.True(t, os.IsNotExist(err), "File should be removed") require.Nil(t, f, "File should be removed") } + +func TestDockerComposeAPIWithStackReaderAndComposeFile(t *testing.T) { + identifier := testNameHash(t.Name()) + simple, _ := RenderComposeSimple(t) + composeContent := ` +services: + api-postgres: + image: postgres:14 + environment: + POSTGRES_PASSWORD: s3cr3t +` + + compose, err := NewDockerComposeWith( + identifier, + WithStackFiles(simple), + WithStackReaders(strings.NewReader(composeContent)), + ) + require.NoError(t, err, "NewDockerCompose()") + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = compose. + WithEnv(map[string]string{ + "bar": "BAR", + "foo": "FOO", + }). + Up(ctx, Wait(true)) + cleanup(t, compose) + require.NoError(t, err, "compose.Up()") + + serviceNames := compose.Services() + + require.Len(t, serviceNames, 2) + assert.Contains(t, serviceNames, "api-nginx") + assert.Contains(t, serviceNames, "api-postgres") + + present := map[string]string{ + "bar": "BAR", + "foo": "FOO", + } + absent := map[string]string{} + assertContainerEnvironmentVariables(t, identifier.String(), "api-nginx", present, absent) +} + func TestDockerComposeAPIWithEnvironment(t *testing.T) { identifier := testNameHash(t.Name()) @@ -479,10 +533,6 @@ func TestDockerComposeAPIWithEnvironment(t *testing.T) { compose, err := NewDockerComposeWith(WithStackFiles(path), identifier) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -491,11 +541,12 @@ func TestDockerComposeAPIWithEnvironment(t *testing.T) { "bar": "BAR", }). Up(ctx, Wait(true)) + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 1) + require.Len(t, serviceNames, 1) assert.Contains(t, serviceNames, "api-nginx") present := map[string]string{ @@ -518,10 +569,6 @@ func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { compose, err := NewDockerComposeWith(composeFiles, identifier) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -531,11 +578,12 @@ func TestDockerComposeAPIWithMultipleComposeFiles(t *testing.T) { "foo": "FOO", }). Up(ctx, Wait(true)) + cleanup(t, compose) require.NoError(t, err, "compose.Up()") serviceNames := compose.Services() - assert.Len(t, serviceNames, 3) + require.Len(t, serviceNames, 3) assert.Contains(t, serviceNames, "api-nginx") assert.Contains(t, serviceNames, "api-mysql") assert.Contains(t, serviceNames, "api-postgres") @@ -553,9 +601,7 @@ func TestDockerComposeAPIWithVolume(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) + cleanup(t, compose) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -569,14 +615,11 @@ func TestDockerComposeAPIWithRecreate(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose.Up(ctx, WithRecreate(api.RecreateNever), WithRecreateDependencies(api.RecreateNever), Wait(true)) + cleanup(t, compose) require.NoError(t, err, "compose.Up()") } @@ -591,6 +634,7 @@ func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { t.Cleanup(cancel) err = compose.Up(ctx, Wait(true)) + cleanup(t, compose) require.NoError(t, err, "compose.Up()") err = compose.Down(context.Background(), RemoveOrphans(true), RemoveVolumes(true), RemoveImagesLocal) @@ -598,11 +642,11 @@ func TestDockerComposeAPIVolumesDeletedOnDown(t *testing.T) { volumeListFilters := filters.NewArgs() // the "mydata" identifier comes from the "testdata/docker-compose-volume.yml" file - volumeListFilters.Add("name", fmt.Sprintf("%s_mydata", identifier)) + volumeListFilters.Add("name", identifier+"_mydata") volumeList, err := compose.dockerClient.VolumeList(ctx, volume.ListOptions{Filters: volumeListFilters}) require.NoError(t, err, "compose.dockerClient.VolumeList()") - assert.Empty(t, volumeList.Volumes, "Volumes are not cleaned up") + require.Empty(t, volumeList.Volumes, "Volumes are not cleaned up") } func TestDockerComposeAPIWithBuild(t *testing.T) { @@ -612,17 +656,13 @@ func TestDockerComposeAPIWithBuild(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) err = compose. WaitForService("api-echo", wait.ForHTTP("/env").WithPort("8080/tcp")). Up(ctx, Wait(true)) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") } @@ -631,10 +671,6 @@ func TestDockerComposeApiWithWaitForShortLifespanService(t *testing.T) { compose, err := NewDockerCompose(path) require.NoError(t, err, "NewDockerCompose()") - t.Cleanup(func() { - require.NoError(t, compose.Down(context.Background(), RemoveOrphans(true), RemoveImagesLocal), "compose.Down()") - }) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -643,16 +679,29 @@ func TestDockerComposeApiWithWaitForShortLifespanService(t *testing.T) { WaitForService("tzatziki", wait.ForExit().WithExitTimeout(10*time.Second)). WaitForService("falafel", wait.ForExit().WithExitTimeout(10*time.Second)). Up(ctx) - + cleanup(t, compose) require.NoError(t, err, "compose.Up()") services := compose.Services() - assert.Len(t, services, 2) + require.Len(t, services, 2) assert.Contains(t, services, "falafel") assert.Contains(t, services, "tzatziki") } func testNameHash(name string) StackIdentifier { - return StackIdentifier(fmt.Sprintf("%x", fnv.New32a().Sum([]byte(name)))) + return StackIdentifier(hex.EncodeToString(fnv.New32a().Sum([]byte(name)))) +} + +// cleanup is a helper function that schedules the compose stack to be stopped when the test ends. +func cleanup(t *testing.T, compose *dockerCompose) { + t.Helper() + t.Cleanup(func() { + require.NoError(t, compose.Down( + context.Background(), + RemoveOrphans(true), + RemoveVolumes(true), + RemoveImagesLocal, + ), "compose.Down()") + }) } diff --git a/modules/compose/compose_builder_test.go b/modules/compose/compose_builder_test.go index fbfe37baa3..048bac241b 100644 --- a/modules/compose/compose_builder_test.go +++ b/modules/compose/compose_builder_test.go @@ -8,12 +8,20 @@ import ( "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" ) const ( testdataPackage = "testdata" ) +func RenderComposeProfiles(t *testing.T) string { + t.Helper() + + return writeTemplate(t, "docker-compose-profiles.yml") +} + func RenderComposeComplex(t *testing.T) (string, []int) { t.Helper() @@ -115,20 +123,17 @@ func getFreePort(t *testing.T) int { t.Helper() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - t.Fatalf("failed to resolve TCP address: %v", err) - } + require.NoErrorf(t, err, "failed to resolve TCP address") l, err := net.ListenTCP("tcp", addr) - if err != nil { - t.Fatalf("failed to listen on TCP address: %v", err) - } + require.NoErrorf(t, err, "failed to listen on TCP address") defer l.Close() return l.Addr().(*net.TCPAddr).Port } func writeTemplate(t *testing.T, templateFile string, port ...int) string { + t.Helper() return writeTemplateWithSrvType(t, templateFile, "api", port...) } @@ -139,9 +144,7 @@ func writeTemplateWithSrvType(t *testing.T, templateFile string, srvType string, composeFile := filepath.Join(tmpDir, "docker-compose.yml") tmpl, err := template.ParseFiles(filepath.Join(testdataPackage, templateFile)) - if err != nil { - t.Fatalf("parsing template file: %s", err) - } + require.NoErrorf(t, err, "parsing template file") values := map[string]interface{}{} for i, p := range port { @@ -151,19 +154,17 @@ func writeTemplateWithSrvType(t *testing.T, templateFile string, srvType string, values["ServiceType"] = srvType output, err := os.Create(composeFile) - if err != nil { - t.Fatalf("creating output file: %s", err) - } - defer output.Close() + require.NoErrorf(t, err, "creating output file") + defer func() { + require.NoError(t, output.Close()) + }() executeTemplateFile := func(templateFile *template.Template, wr io.Writer, data any) error { return templateFile.Execute(wr, data) } err = executeTemplateFile(tmpl, output, values) - if err != nil { - t.Fatalf("executing template file: %s", err) - } + require.NoErrorf(t, err, "executing template file") return composeFile } diff --git a/modules/compose/compose_local.go b/modules/compose/compose_local.go index 19b0ce379a..964547bae7 100644 --- a/modules/compose/compose_local.go +++ b/modules/compose/compose_local.go @@ -136,7 +136,7 @@ func (dc *LocalDockerCompose) containerNameFromServiceName(service, separator st func (dc *LocalDockerCompose) applyStrategyToRunningContainer() error { cli, err := testcontainers.NewDockerClientWithOpts(context.Background()) if err != nil { - return err + return fmt.Errorf("new docker client: %w", err) } defer cli.Close() @@ -150,22 +150,22 @@ func (dc *LocalDockerCompose) applyStrategyToRunningContainer() error { containerListOptions := container.ListOptions{Filters: f, All: true} containers, err := cli.ContainerList(context.Background(), containerListOptions) if err != nil { - return fmt.Errorf("error %w occurred while filtering the service %s: %d by name and published port", err, k.service, k.publishedPort) + return fmt.Errorf("container list service %q: %w", k.service, err) } if len(containers) == 0 { - return fmt.Errorf("service with name %s not found in list of running containers", k.service) + return fmt.Errorf("service with name %q not found in list of running containers", k.service) } // The length should always be a list of 1, since we are matching one service name at a time if l := len(containers); l > 1 { - return fmt.Errorf("expecting only one running container for %s but got %d", k.service, l) + return fmt.Errorf("expecting only one running container for %q but got %d", k.service, l) } container := containers[0] strategy := dc.WaitStrategyMap[k] dockerProvider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(dc.Logger)) if err != nil { - return fmt.Errorf("unable to create new Docker Provider: %w", err) + return fmt.Errorf("new docker provider: %w", err) } defer dockerProvider.Close() @@ -175,7 +175,7 @@ func (dc *LocalDockerCompose) applyStrategyToRunningContainer() error { err = strategy.WaitUntilReady(context.Background(), dockercontainer) if err != nil { - return fmt.Errorf("unable to apply wait strategy %v to service %s due to %w", strategy, k.service, err) + return fmt.Errorf("wait until ready %v to service %q due: %w", strategy, k.service, err) } } return nil @@ -223,7 +223,6 @@ func (dc *LocalDockerCompose) WithExposedService(service string, port int, strat // depending on the version services names are composed in a different way func (dc *LocalDockerCompose) determineVersion() error { execErr := executeCompose(dc, []string{"version", "--short"}) - if err := execErr.Error; err != nil { return err } @@ -235,7 +234,7 @@ func (dc *LocalDockerCompose) determineVersion() error { majorVersion, err := strconv.ParseInt(string(components[0]), 10, 8) if err != nil { - return err + return fmt.Errorf("parsing major version: %w", err) } switch majorVersion { @@ -263,11 +262,11 @@ func (dc *LocalDockerCompose) validate() error { yamlFile, err := os.ReadFile(abs) if err != nil { - return err + return fmt.Errorf("read compose file %q: %w", abs, err) } err = yaml.Unmarshal(yamlFile, &c) if err != nil { - return err + return fmt.Errorf("unmarshalling file %q: %w", abs, err) } if dc.Services == nil { @@ -307,14 +306,26 @@ func execute( cmd.Env = append(cmd.Env, key+"="+value) } - stdoutIn, _ := cmd.StdoutPipe() - stderrIn, _ := cmd.StderrPipe() + stdoutIn, err := cmd.StdoutPipe() + if err != nil { + return ExecError{ + Command: cmd.Args, + Error: fmt.Errorf("stdout: %w", err), + } + } + + stderrIn, err := cmd.StderrPipe() + if err != nil { + return ExecError{ + Command: cmd.Args, + Error: fmt.Errorf("stderr: %w", err), + } + } stdout := newCapturingPassThroughWriter(os.Stdout) stderr := newCapturingPassThroughWriter(os.Stderr) - err := cmd.Start() - if err != nil { + if err = cmd.Start(); err != nil { execCmd := []string{"Starting command", dirContext, binary} execCmd = append(execCmd, args...) @@ -436,7 +447,9 @@ func (w *capturingPassThroughWriter) Bytes() []byte { // Which checks if a binary is present in PATH func which(binary string) error { - _, err := exec.LookPath(binary) + if _, err := exec.LookPath(binary); err != nil { + return fmt.Errorf("lookup: %w", err) + } - return err + return nil } diff --git a/modules/compose/compose_test.go b/modules/compose/compose_test.go index 2453507b06..24c0c6c635 100644 --- a/modules/compose/compose_test.go +++ b/modules/compose/compose_test.go @@ -134,7 +134,7 @@ func TestLocalDockerComposeStrategyForInvalidService(t *testing.T) { Invoke() require.Error(t, err.Error, "Expected error to be thrown because service with wait strategy is not running") - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -157,7 +157,7 @@ func TestLocalDockerComposeWithWaitLogStrategy(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 2) + require.Len(t, compose.Services, 2) assert.Contains(t, compose.Services, "local-nginx") assert.Contains(t, compose.Services, "local-mysql") } @@ -183,7 +183,7 @@ func TestLocalDockerComposeWithWaitForService(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -207,7 +207,7 @@ func TestLocalDockerComposeWithWaitForShortLifespanService(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 2) + require.Len(t, compose.Services, 2) assert.Contains(t, compose.Services, "falafel") assert.Contains(t, compose.Services, "tzatziki") } @@ -233,7 +233,7 @@ func TestLocalDockerComposeWithWaitHTTPStrategy(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -258,7 +258,7 @@ func TestLocalDockerComposeWithContainerName(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -280,7 +280,7 @@ func TestLocalDockerComposeWithWaitStrategy_NoExposedPorts(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -303,7 +303,7 @@ func TestLocalDockerComposeWithMultipleWaitStrategies(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 2) + require.Len(t, compose.Services, 2) assert.Contains(t, compose.Services, "local-nginx") assert.Contains(t, compose.Services, "local-mysql") } @@ -331,7 +331,7 @@ func TestLocalDockerComposeWithFailedStrategy(t *testing.T) { // A specific error message matcher is not asserted since the docker library can change the return message, breaking this test require.Error(t, err.Error, "Expected error to be thrown because of a wrong suplied wait strategy") - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") } @@ -352,7 +352,7 @@ func TestLocalDockerComposeComplex(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 2) + require.Len(t, compose.Services, 2) assert.Contains(t, compose.Services, "local-nginx") assert.Contains(t, compose.Services, "local-mysql") } @@ -377,7 +377,7 @@ func TestLocalDockerComposeWithEnvironment(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 1) + require.Len(t, compose.Services, 1) assert.Contains(t, compose.Services, "local-nginx") present := map[string]string{ @@ -413,7 +413,7 @@ func TestLocalDockerComposeWithMultipleComposeFiles(t *testing.T) { Invoke() checkIfError(t, err) - assert.Len(t, compose.Services, 3) + require.Len(t, compose.Services, 3) assert.Contains(t, compose.Services, "local-nginx") assert.Contains(t, compose.Services, "local-mysql") assert.Contains(t, compose.Services, "local-postgres") @@ -446,23 +446,18 @@ func TestLocalDockerComposeWithVolume(t *testing.T) { } func assertVolumeDoesNotExist(tb testing.TB, volumeName string) { + tb.Helper() containerClient, err := testcontainers.NewDockerClientWithOpts(context.Background()) - if err != nil { - tb.Fatalf("Failed to get provider: %v", err) - } + require.NoErrorf(tb, err, "Failed to get provider") volumeList, err := containerClient.VolumeList(context.Background(), volume.ListOptions{Filters: filters.NewArgs(filters.Arg("name", volumeName))}) - if err != nil { - tb.Fatalf("Failed to list volumes: %v", err) - } + require.NoErrorf(tb, err, "Failed to list volumes") if len(volumeList.Warnings) > 0 { tb.Logf("Volume list warnings: %v", volumeList.Warnings) } - if len(volumeList.Volumes) > 0 { - tb.Fatalf("Volume list is not empty") - } + require.Emptyf(tb, volumeList.Volumes, "Volume list is not empty") } func assertContainerEnvironmentVariables( @@ -471,17 +466,13 @@ func assertContainerEnvironmentVariables( present map[string]string, absent map[string]string, ) { + tb.Helper() containerClient, err := testcontainers.NewDockerClientWithOpts(context.Background()) - if err != nil { - tb.Fatalf("Failed to get provider: %v", err) - } + require.NoErrorf(tb, err, "Failed to get provider") containers, err := containerClient.ContainerList(context.Background(), container.ListOptions{}) - if err != nil { - tb.Fatalf("Failed to list containers: %v", err) - } else if len(containers) == 0 { - tb.Fatalf("container list empty") - } + require.NoErrorf(tb, err, "Failed to list containers") + require.NotEmptyf(tb, containers, "container list empty") containerNameRegexp := regexp.MustCompile(fmt.Sprintf(`^\/?%s(_|-)%s(_|-)\d$`, composeIdentifier, serviceName)) var containerID string @@ -497,9 +488,7 @@ containerLoop: } details, err := containerClient.ContainerInspect(context.Background(), containerID) - if err != nil { - tb.Fatalf("Failed to inspect container: %v", err) - } + require.NoErrorf(tb, err, "Failed to inspect container") for k, v := range present { keyVal := k + "=" + v @@ -514,17 +503,11 @@ containerLoop: func checkIfError(t *testing.T, err ExecError) { t.Helper() - if err.Error != nil { - t.Fatalf("Failed when running %v: %v", err.Command, err.Error) - } + require.NoErrorf(t, err.Error, "Failed when running %v", err.Command) - if err.Stdout != nil { - t.Fatalf("An error in Stdout happened when running %v: %v", err.Command, err.Stdout) - } + require.NoErrorf(t, err.Stdout, "An error in Stdout happened when running %v", err.Command) - if err.Stderr != nil { - t.Fatalf("An error in Stderr happened when running %v: %v", err.Command, err.Stderr) - } + require.NoErrorf(t, err.Stderr, "An error in Stderr happened when running %v", err.Command) assert.NotNil(t, err.StdoutOutput) assert.NotNil(t, err.StderrOutput) diff --git a/modules/compose/go.mod b/modules/compose/go.mod index b07f1da56a..24ced86c72 100644 --- a/modules/compose/go.mod +++ b/modules/compose/go.mod @@ -1,18 +1,18 @@ module github.com/testcontainers/testcontainers-go/modules/compose -go 1.21 +go 1.22 replace github.com/testcontainers/testcontainers-go => ../.. require ( - github.com/compose-spec/compose-go/v2 v2.1.0 - github.com/docker/cli v26.1.0+incompatible - github.com/docker/compose/v2 v2.27.0 - github.com/docker/docker v26.1.0+incompatible + github.com/compose-spec/compose-go/v2 v2.1.3 + github.com/docker/cli v27.0.3+incompatible + github.com/docker/compose/v2 v2.28.1 + github.com/docker/docker v27.1.1+incompatible github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 - golang.org/x/sync v0.7.0 + github.com/testcontainers/testcontainers-go v0.34.0 + golang.org/x/sync v0.8.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -23,7 +23,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/hcsshim v0.11.5 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect @@ -43,15 +43,17 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/continuity v0.4.3 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/ttrpc v1.2.3 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/ttrpc v1.2.5 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/buildx v0.14.0 // indirect + github.com/docker/buildx v0.15.1 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect @@ -61,7 +63,7 @@ require ( github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsevents v0.1.1 // indirect + github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -69,6 +71,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -79,12 +82,11 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/in-toto/in-toto-golang v0.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -103,10 +105,8 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/miekg/pkcs11 v1.1.1 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.13.1 // indirect + github.com/moby/buildkit v0.14.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect @@ -141,15 +141,15 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/tonistiigi/fsutil v0.0.0-20240301111122-7525a1af2bb5 // indirect + github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect - github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 // indirect + github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -164,33 +164,31 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/grpc v1.59.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.29.2 // indirect k8s.io/apimachinery v0.29.2 // indirect - k8s.io/apiserver v0.29.2 // indirect k8s.io/client-go v0.29.2 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect @@ -198,5 +196,5 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect - tags.cncf.io/container-device-interface v0.6.2 // indirect + tags.cncf.io/container-device-interface v0.7.2 // indirect ) diff --git a/modules/compose/go.sum b/modules/compose/go.sum index 018ca3593d..c42e635113 100644 --- a/modules/compose/go.sum +++ b/modules/compose/go.sum @@ -1,7 +1,6 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= -cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= -cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -20,8 +19,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38= +github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -79,64 +78,65 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.1.0 h1:qdW2qISQlCQG8v1O2TChcdxgAWTUGgUX/CPSO+ES9+E= -github.com/compose-spec/compose-go/v2 v2.1.0/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.1.3 h1:bD67uqLuL/XgkAK6ir3xZvNLFPxPScEi1KW7R5esrLE= +github.com/compose-spec/compose-go/v2 v2.1.3/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 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/nydus-snapshotter v0.13.7 h1:x7DHvGnzJOu1ZPwPYkeOPk5MjZZYbdddygEjaSDoFTk= github.com/containerd/nydus-snapshotter v0.13.7/go.mod h1:VPVKQ3jmHFIcUIV2yiQ1kImZuBFS3GXDohKs9mRABVE= +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/containerd/stargz-snapshotter v0.15.1 h1:fpsP4kf/Z4n2EYnU0WT8ZCE3eiKDwikDhL6VwxIlgeA= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= -github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= +github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/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/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 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/buildx v0.14.0 h1:FxqcfE7xgeEC4oQlKLpuvfobRDVDXrHE3jByM+mdyqk= -github.com/docker/buildx v0.14.0/go.mod h1:Vy/2lC9QsJvo33+7KKkN/GDE5WxnVqW0/dpcN7ZqPJY= -github.com/docker/cli v26.1.0+incompatible h1:+nwRy8Ocd8cYNQ60mozDDICICD8aoFGtlPXifX/UQ3Y= -github.com/docker/cli v26.1.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/compose/v2 v2.27.0 h1:FKyClQdErCxUZULC2zo6Jn5ve+epFPe/Y0HaxjmUzNg= -github.com/docker/compose/v2 v2.27.0/go.mod h1:uaqwmY6haO8wXWHk+LAsqqDapX6boH4izRKqj/E7+Bo= +github.com/docker/buildx v0.15.1 h1:1cO6JIc0rOoC8tlxfXoh1HH1uxaNvYH1q7J7kv5enhw= +github.com/docker/buildx v0.15.1/go.mod h1:16DQgJqoggmadc1UhLaUTPqKtR+PlByN/kyXFdkhFCo= +github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= +github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/compose/v2 v2.28.1 h1:ORPfiVHrpnRQBDoC3F8JJyWAY8N5gWuo3FgwyivxFdM= +github.com/docker/compose/v2 v2.28.1/go.mod h1:wDtGQFHe99sPLCHXeVbCkc+Wsl4Y/2ZxiAJa/nga6rA= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.0+incompatible h1:W1G9MPNbskA6VZWL7b3ZljTh0pXI68FpINx0GKaOdaM= -github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -158,22 +158,17 @@ github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJ github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsevents v0.1.1 h1:/125uxJvvoSDDBPen6yUZbil8J9ydKZnnl3TWWmvnkw= -github.com/fsnotify/fsevents v0.1.1/go.mod h1:+d+hS27T6k5J8CRaPLKFgwKYcpS7GwW3Ule9+SC2ZRc= +github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= +github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -195,6 +190,8 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= @@ -204,25 +201,22 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -244,8 +238,6 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -257,8 +249,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -329,15 +321,11 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.1 h1:L8afOFhPq2RPJJSr/VyzbufwID7jquZVB7oFHbPRcPE= -github.com/moby/buildkit v0.13.1/go.mod h1:aNmNQKLBFYAOFuzQjR3VA27/FijlvtBD1pjNwTSN37k= +github.com/moby/buildkit v0.14.1 h1:2epLCZTkn4CikdImtsLtIa++7DzCimrrZCT1sway+oI= +github.com/moby/buildkit v0.14.1/go.mod h1:1XssG7cAqv5Bz1xcGMxJL123iCv5TYN4Z/qf647gfuk= 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/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -413,7 +401,6 @@ github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+L github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -449,7 +436,6 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= @@ -459,8 +445,8 @@ github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/u github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -473,6 +459,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -493,12 +481,12 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA 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/tonistiigi/fsutil v0.0.0-20240301111122-7525a1af2bb5 h1:oZS8KCqAg62sxJkEq/Ppzqrb6EooqzWtL8Oaex7bc5c= -github.com/tonistiigi/fsutil v0.0.0-20240301111122-7525a1af2bb5/go.mod h1:vbbYqJlnswsbJqWUcJN8fKtBhnEgldDrcagTgnBVKKM= +github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c h1:+6wg/4ORAbnSoGDzg2Q1i3CeMcT/jjhye/ZfnBHy7/M= +github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c/go.mod h1:vbbYqJlnswsbJqWUcJN8fKtBhnEgldDrcagTgnBVKKM= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= -github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs= -github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -535,8 +523,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqhe go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/exporters/prometheus v0.42.0 h1:jwV9iQdvp38fxXi8ZC+lNpxjK16MRcZlpDYvbuO1FiA= -go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= @@ -547,13 +533,10 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 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= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -562,27 +545,18 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -590,11 +564,10 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -602,16 +575,14 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -632,59 +603,46 @@ golang.org/x/sys v0.1.0/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= @@ -716,14 +674,10 @@ 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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= -k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= @@ -738,5 +692,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg= -tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE= +tags.cncf.io/container-device-interface v0.7.2 h1:MLqGnWfOr1wB7m08ieI4YJ3IoLKKozEnnNYBtacDPQU= +tags.cncf.io/container-device-interface v0.7.2/go.mod h1:Xb1PvXv2BhfNb3tla4r9JL129ck1Lxv9KuU6eVOfKto= diff --git a/modules/compose/testdata/docker-compose-build.yml b/modules/compose/testdata/docker-compose-build.yml index 88c6ccda40..d2210d3153 100644 --- a/modules/compose/testdata/docker-compose-build.yml +++ b/modules/compose/testdata/docker-compose-build.yml @@ -1,4 +1,3 @@ -version: '3' services: {{ .ServiceType }}-echo: build: @@ -8,4 +7,4 @@ services: ports: - target: 8080 published: {{ .Port_0 }} - protocol: tcp \ No newline at end of file + protocol: tcp diff --git a/modules/compose/testdata/docker-compose-complex.yml b/modules/compose/testdata/docker-compose-complex.yml index f2eacf026a..d84f39ec16 100644 --- a/modules/compose/testdata/docker-compose-complex.yml +++ b/modules/compose/testdata/docker-compose-complex.yml @@ -1,13 +1,12 @@ -version: '3' services: {{ .ServiceType }}-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine ports: - "{{ .Port_0 }}:80" {{ .ServiceType }}-mysql: - image: docker.io/mysql:8.0.36 + image: mysql:8.0.36 environment: - MYSQL_DATABASE=db - MYSQL_ROOT_PASSWORD=my-secret-pw ports: - - "{{ .Port_1 }}:3306" \ No newline at end of file + - "{{ .Port_1 }}:3306" diff --git a/modules/compose/testdata/docker-compose-container-name.yml b/modules/compose/testdata/docker-compose-container-name.yml index b7e497fd84..d36bf96c87 100644 --- a/modules/compose/testdata/docker-compose-container-name.yml +++ b/modules/compose/testdata/docker-compose-container-name.yml @@ -1,8 +1,7 @@ -version: '3' services: {{ .ServiceType }}-nginx: container_name: {{ .ServiceType }}-nginxy - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine environment: bar: ${bar} ports: diff --git a/modules/compose/testdata/docker-compose-no-exposed-ports.yml b/modules/compose/testdata/docker-compose-no-exposed-ports.yml index 58da1351f4..e59e1a6fe9 100644 --- a/modules/compose/testdata/docker-compose-no-exposed-ports.yml +++ b/modules/compose/testdata/docker-compose-no-exposed-ports.yml @@ -1,6 +1,5 @@ -version: '3' services: {{ .ServiceType }}-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine ports: - "80" diff --git a/modules/compose/testdata/docker-compose-override.yml b/modules/compose/testdata/docker-compose-override.yml index 8a44e78631..6112c8d595 100644 --- a/modules/compose/testdata/docker-compose-override.yml +++ b/modules/compose/testdata/docker-compose-override.yml @@ -1,9 +1,8 @@ -version: '3' services: {{ .ServiceType }}-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine {{ .ServiceType }}-mysql: - image: docker.io/mysql:8.0.36 + image: mysql:8.0.36 environment: MYSQL_RANDOM_ROOT_PASSWORD: Y ports: diff --git a/modules/compose/testdata/docker-compose-postgres.yml b/modules/compose/testdata/docker-compose-postgres.yml index 7988a43266..012c2e0fda 100644 --- a/modules/compose/testdata/docker-compose-postgres.yml +++ b/modules/compose/testdata/docker-compose-postgres.yml @@ -1,7 +1,6 @@ -version: '3' services: {{ .ServiceType }}-postgres: - image: docker.io/postgres:14 + image: postgres:14 environment: POSTGRES_PASSWORD: s3cr3t ports: diff --git a/modules/compose/testdata/docker-compose-profiles.yml b/modules/compose/testdata/docker-compose-profiles.yml new file mode 100644 index 0000000000..a58bf014d3 --- /dev/null +++ b/modules/compose/testdata/docker-compose-profiles.yml @@ -0,0 +1,25 @@ +services: + starts-always: + image: nginx:stable-alpine + ports: + - ":80" + # profiles: none defined, therefore always starts. + only-dev: + image: nginx:stable-alpine + ports: + - ":80" + profiles: + - dev + dev-or-test: + image: nginx:stable-alpine + ports: + - ":80" + profiles: + - dev + - test + only-prod: + image: nginx:stable-alpine + ports: + - ":80" + profiles: + - prod diff --git a/modules/compose/testdata/docker-compose-short-lifespan.yml b/modules/compose/testdata/docker-compose-short-lifespan.yml index 2deb847d85..19486a792b 100644 --- a/modules/compose/testdata/docker-compose-short-lifespan.yml +++ b/modules/compose/testdata/docker-compose-short-lifespan.yml @@ -1,8 +1,7 @@ -version: '3' services: tzatziki: - image: docker.io/alpine:latest + image: alpine:latest command: "sleep 5" falafel: - image: docker.io/alpine:latest + image: alpine:latest command: "echo 'World is your canvas'" diff --git a/modules/compose/testdata/docker-compose-simple.yml b/modules/compose/testdata/docker-compose-simple.yml index 90b9a4be87..a3aad440bc 100644 --- a/modules/compose/testdata/docker-compose-simple.yml +++ b/modules/compose/testdata/docker-compose-simple.yml @@ -1,7 +1,6 @@ -version: '3' services: {{ .ServiceType }}-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine environment: bar: ${bar} foo: ${foo} diff --git a/modules/compose/testdata/docker-compose-volume.yml b/modules/compose/testdata/docker-compose-volume.yml index 81add904d0..9f904d41d9 100644 --- a/modules/compose/testdata/docker-compose-volume.yml +++ b/modules/compose/testdata/docker-compose-volume.yml @@ -1,7 +1,6 @@ -version: '3' services: {{ .ServiceType }}-nginx: - image: docker.io/nginx:stable-alpine + image: nginx:stable-alpine volumes: - type: volume source: mydata diff --git a/modules/compose/testdata/echoserver.Dockerfile b/modules/compose/testdata/echoserver.Dockerfile index 546489ffac..aaf835f35a 100644 --- a/modules/compose/testdata/echoserver.Dockerfile +++ b/modules/compose/testdata/echoserver.Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.13-alpine +FROM golang:1.13-alpine WORKDIR /app diff --git a/modules/consul/consul.go b/modules/consul/consul.go index b1c79630b9..0084786afb 100644 --- a/modules/consul/consul.go +++ b/modules/consul/consul.go @@ -14,7 +14,8 @@ const ( ) const ( - DefaultBaseImage = "docker.io/hashicorp/consul:1.15" + // Deprecated: it will be removed in the next major version. + DefaultBaseImage = "hashicorp/consul:1.15" ) // ConsulContainer represents the Consul container type used in the module. @@ -61,11 +62,17 @@ func WithConfigFile(configPath string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the Consul container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ConsulContainer, error) { + return Run(ctx, "hashicorp/consul:1.15", opts...) +} + +// Run creates an instance of the Consul container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ConsulContainer, error) { containerReq := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: DefaultBaseImage, + Image: img, ExposedPorts: []string{ defaultHttpApiPort + "/tcp", defaultBrokerPort + "/tcp", @@ -87,9 +94,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, containerReq) + var c *ConsulContainer + if container != nil { + c = &ConsulContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &ConsulContainer{Container: container}, nil + return c, nil } diff --git a/modules/consul/consul_test.go b/modules/consul/consul_test.go index e9f6bfa860..6f359b7261 100644 --- a/modules/consul/consul_test.go +++ b/modules/consul/consul_test.go @@ -7,7 +7,6 @@ import ( "testing" capi "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -40,18 +39,18 @@ func TestConsul(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - container, err := consul.RunContainer(ctx, test.opts...) + ctr, err := consul.Run(ctx, "hashicorp/consul:1.15", test.opts...) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, container.Terminate(ctx), "failed to terminate container") }) // Check if API is up - host, err := container.ApiEndpoint(ctx) + host, err := ctr.ApiEndpoint(ctx) require.NoError(t, err) - assert.NotEmpty(t, len(host)) + require.NotEmpty(t, host) res, err := http.Get("http://" + host) require.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, http.StatusOK, res.StatusCode) cfg := capi.DefaultConfig() cfg.Address = host diff --git a/modules/consul/examples_test.go b/modules/consul/examples_test.go index 002c143c95..d833575880 100644 --- a/modules/consul/examples_test.go +++ b/modules/consul/examples_test.go @@ -11,28 +11,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/consul" ) -func ExampleRunContainer() { +func ExampleRun() { // runConsulContainer { ctx := context.Background() - consulContainer, err := consul.RunContainer(ctx, - testcontainers.WithImage("docker.io/hashicorp/consul:1.15"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + consulContainer, err := consul.Run(ctx, "hashicorp/consul:1.15") defer func() { - if err := consulContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(consulContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := consulContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -41,40 +39,40 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { // connectConsul { ctx := context.Background() - consulContainer, err := consul.RunContainer(ctx, - testcontainers.WithImage("docker.io/hashicorp/consul:1.15"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + consulContainer, err := consul.Run(ctx, "hashicorp/consul:1.15") defer func() { - if err := consulContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(consulContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := consulContainer.ApiEndpoint(ctx) if err != nil { - log.Fatalf("failed to get endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get endpoint: %s", err) + return } config := capi.DefaultConfig() config.Address = endpoint client, err := capi.NewClient(config) if err != nil { - log.Fatalf("failed to connect to Consul: %s", err) + log.Printf("failed to connect to Consul: %s", err) + return } // } node_name, err := client.Agent().NodeName() if err != nil { - log.Fatalf("failed to get node name: %s", err) // nolint:gocritic + log.Printf("failed to get node name: %s", err) + return } fmt.Println(len(node_name) > 0) diff --git a/modules/consul/go.mod b/modules/consul/go.mod index 081b0b50a2..21db7d081e 100644 --- a/modules/consul/go.mod +++ b/modules/consul/go.mod @@ -1,26 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/consul -go 1.21 +go 1.22 require ( github.com/hashicorp/consul/api v1.27.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.14.1 // indirect @@ -29,7 +29,6 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -39,13 +38,14 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.10.1 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -66,14 +66,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/consul/go.sum b/modules/consul/go.sum index cc17a80442..b261343e8c 100644 --- a/modules/consul/go.sum +++ b/modules/consul/go.sum @@ -5,10 +5,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h 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/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -28,22 +26,24 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -73,8 +73,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -138,8 +136,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -178,6 +176,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -283,14 +283,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -300,16 +298,14 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -339,37 +335,34 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -384,5 +377,5 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/couchbase/couchbase.go b/modules/couchbase/couchbase.go index d9b468edb0..e061ecf3a4 100644 --- a/modules/couchbase/couchbase.go +++ b/modules/couchbase/couchbase.go @@ -45,11 +45,6 @@ const ( // } ) -// defaultImage { -const defaultImage = "couchbase:6.5.1" - -// } - // initialServices is the list of services that are enabled by default var initialServices = []Service{kv, query, search, index} @@ -61,8 +56,14 @@ type CouchbaseContainer struct { config *Config } +// Deprecated: use Run instead // RunContainer creates an instance of the Couchbase container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*CouchbaseContainer, error) { + return Run(ctx, "couchbase:6.5.1", opts...) +} + +// Run creates an instance of the Couchbase container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*CouchbaseContainer, error) { config := &Config{ enabledServices: make([]Service, 0), username: "Administrator", @@ -71,7 +72,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{MGMT_PORT + "/tcp", MGMT_SSL_PORT + "/tcp"}, } @@ -112,21 +113,23 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var couchbaseContainer *CouchbaseContainer + if container != nil { + couchbaseContainer = &CouchbaseContainer{container, config} + } if err != nil { - return nil, err + return couchbaseContainer, err } - couchbaseContainer := CouchbaseContainer{container, config} - if err = couchbaseContainer.initCluster(ctx); err != nil { - return nil, err + return couchbaseContainer, fmt.Errorf("init cluster: %w", err) } if err = couchbaseContainer.createBuckets(ctx); err != nil { - return nil, err + return couchbaseContainer, fmt.Errorf("create buckets: %w", err) } - return &couchbaseContainer, nil + return couchbaseContainer, nil } // StartContainer creates an instance of the Couchbase container type @@ -145,7 +148,6 @@ func StartContainer(ctx context.Context, opts ...Option) (*CouchbaseContainer, e } customizers := []testcontainers.ContainerCustomizer{ - testcontainers.WithImage(config.imageName), WithAdminCredentials(config.username, config.password), WithIndexStorage(config.indexStorageMode), WithBuckets(config.buckets...), @@ -155,7 +157,7 @@ func StartContainer(ctx context.Context, opts ...Option) (*CouchbaseContainer, e customizers = append(customizers, withService(srv)) } - return RunContainer(ctx, customizers...) + return Run(ctx, config.imageName, customizers...) } // ConnectionString returns the connection string to connect to the Couchbase container instance. @@ -491,9 +493,14 @@ func (c *CouchbaseContainer) createPrimaryIndex(ctx context.Context, bucket buck body := map[string]string{ "statement": "CREATE PRIMARY INDEX on `" + bucket.name + "`", } - - _, err := c.doHttpRequest(ctx, QUERY_PORT, "/query/service", http.MethodPost, body, true) - + err := backoff.Retry(func() error { + response, err := c.doHttpRequest(ctx, QUERY_PORT, "/query/service", http.MethodPost, body, true) + firstError := gjson.Get(string(response), "errors.0.code").Int() + if firstError != 0 { + return errors.New("index creation failed") + } + return err + }, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) return err } diff --git a/modules/couchbase/couchbase_test.go b/modules/couchbase/couchbase_test.go index d416fa60ab..37f7a086a3 100644 --- a/modules/couchbase/couchbase_test.go +++ b/modules/couchbase/couchbase_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/couchbase/gocb/v2" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" tccouchbase "github.com/testcontainers/testcontainers-go/modules/couchbase" @@ -13,7 +14,7 @@ import ( const ( // dockerImages { - enterpriseEdition = "couchbase:enterprise-7.1.3" + enterpriseEdition = "couchbase:enterprise-7.6.1" communityEdition = "couchbase:community-7.1.1" // } ) @@ -30,23 +31,13 @@ func TestCouchbaseWithCommunityContainer(t *testing.T) { WithFlushEnabled(false). WithPrimaryIndex(true) - container, err := tccouchbase.RunContainer(ctx, testcontainers.WithImage(communityEdition), tccouchbase.WithBuckets(bucket)) - if err != nil { - t.Fatal(err) - } + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithBuckets(bucket)) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - cluster, err := connectCluster(ctx, container) - if err != nil { - t.Fatalf("could not connect couchbase: %s", err) - } + cluster, err := connectCluster(ctx, ctr) + require.NoError(t, err) testBucketUsage(t, cluster.Bucket(bucketName)) } @@ -55,22 +46,20 @@ func TestCouchbaseWithEnterpriseContainer(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - container, err := tccouchbase.RunContainer(ctx, testcontainers.WithImage(enterpriseEdition), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + bucket := tccouchbase.NewBucket(bucketName). + WithQuota(100). + WithReplicas(0). + WithFlushEnabled(true). + WithPrimaryIndex(true) + ctr, err := tccouchbase.Run(ctx, + enterpriseEdition, + tccouchbase.WithBuckets(bucket), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - cluster, err := connectCluster(ctx, container) - if err != nil { - t.Fatalf("could not connect couchbase: %s", err) - } + cluster, err := connectCluster(ctx, ctr) + require.NoError(t, err) testBucketUsage(t, cluster.Bucket(bucketName)) } @@ -79,86 +68,70 @@ func TestWithCredentials(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.RunContainer(ctx, - testcontainers.WithImage(communityEdition), + ctr, err := tccouchbase.Run(ctx, + communityEdition, tccouchbase.WithAdminCredentials("testcontainers", "testcontainers.IS.cool!"), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - if err != nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestWithCredentials_Password_LessThan_6(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.RunContainer(ctx, - testcontainers.WithImage(communityEdition), + ctr, err := tccouchbase.Run(ctx, + communityEdition, tccouchbase.WithAdminCredentials("testcontainers", "12345"), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func TestAnalyticsServiceWithCommunityContainer(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.RunContainer(ctx, - testcontainers.WithImage(communityEdition), + ctr, err := tccouchbase.Run(ctx, + communityEdition, tccouchbase.WithServiceAnalytics(), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func TestEventingServiceWithCommunityContainer(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.RunContainer(ctx, - testcontainers.WithImage(communityEdition), + ctr, err := tccouchbase.Run(ctx, + communityEdition, tccouchbase.WithServiceEventing(), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func testBucketUsage(t *testing.T, bucket *gocb.Bucket) { + t.Helper() err := bucket.WaitUntilReady(5*time.Second, nil) - if err != nil { - t.Fatalf("could not connect bucket: %s", err) - } + require.NoErrorf(t, err, "could not connect bucket") key := "foo" data := map[string]string{"key": "value"} collection := bucket.DefaultCollection() _, err = collection.Upsert(key, data, nil) - if err != nil { - t.Fatalf("could not upsert data: %s", err) - } + require.NoErrorf(t, err, "could not upsert data") result, err := collection.Get(key, nil) - if err != nil { - t.Fatalf("could not get data: %s", err) - } + require.NoErrorf(t, err, "could not get data") var resultData map[string]string err = result.Content(&resultData) - if err != nil { - t.Fatalf("could not assign content: %s", err) - } - - if resultData["key"] != "value" { - t.Errorf("Expected value to be [%s], got %s", "value", resultData["key"]) - } + require.NoErrorf(t, err, "could not assign content") + require.Contains(t, resultData, "key") + require.Equalf(t, "value", resultData["key"], "Expected value to be [%s], got %s", "value", resultData["key"]) } func connectCluster(ctx context.Context, container *tccouchbase.CouchbaseContainer) (*gocb.Cluster, error) { diff --git a/modules/couchbase/examples_test.go b/modules/couchbase/examples_test.go index e89363d3ac..cc1a09a9db 100644 --- a/modules/couchbase/examples_test.go +++ b/modules/couchbase/examples_test.go @@ -11,7 +11,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/couchbase" ) -func ExampleRunContainer() { +func ExampleRun() { // runCouchbaseContainer { ctx := context.Background() @@ -23,31 +23,34 @@ func ExampleRunContainer() { WithFlushEnabled(false). WithPrimaryIndex(true) - couchbaseContainer, err := couchbase.RunContainer(ctx, - testcontainers.WithImage("couchbase:community-7.1.1"), + couchbaseContainer, err := couchbase.Run(ctx, + "couchbase:community-7.1.1", couchbase.WithAdminCredentials("testcontainers", "testcontainers.IS.cool!"), couchbase.WithBuckets(bucket), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := couchbaseContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(couchbaseContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := couchbaseContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionString, err := couchbaseContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } cluster, err := gocb.Connect(connectionString, gocb.ClusterOptions{ @@ -55,12 +58,14 @@ func ExampleRunContainer() { Password: couchbaseContainer.Password(), }) if err != nil { - log.Fatalf("failed to connect to cluster: %s", err) + log.Printf("failed to connect to cluster: %s", err) + return } buckets, err := cluster.Buckets().GetAllBuckets(nil) if err != nil { - log.Fatalf("failed to get buckets: %s", err) + log.Printf("failed to get buckets: %s", err) + return } fmt.Println(len(buckets)) diff --git a/modules/couchbase/go.mod b/modules/couchbase/go.mod index ab8ec6e7ef..26739fb6e3 100644 --- a/modules/couchbase/go.mod +++ b/modules/couchbase/go.mod @@ -1,44 +1,44 @@ module github.com/testcontainers/testcontainers-go/modules/couchbase -go 1.21 - -toolchain go1.21.7 +go 1.22 require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/couchbase/gocb/v2 v2.7.2 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 github.com/tidwall/gjson v1.17.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/couchbase/gocbcore/v10 v10.3.2 // indirect github.com/couchbase/gocbcoreps v0.1.2 // indirect github.com/couchbase/goprotostellar v1.0.2 // indirect github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -47,6 +47,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -62,15 +63,14 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/grpc v1.61.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/couchbase/go.sum b/modules/couchbase/go.sum index f7e0ce6bcd..1abe707bb5 100644 --- a/modules/couchbase/go.sum +++ b/modules/couchbase/go.sum @@ -6,20 +6,20 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 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/couchbase/gocb/v2 v2.7.2 h1:muQZoLLnlaGInNIhqgEmDdyGhlVJ269aAIXa8rp14pA= github.com/couchbase/gocb/v2 v2.7.2/go.mod h1:majhVAf32ALdJLdsGYL9pEedvfC+w9S+00+KnxEivbI= github.com/couchbase/gocbcore/v10 v10.3.2 h1:Z1jIJSj2tCqQhd+Uwa4gKqHbCVJ8Mizw6zxX6XsAjPA= @@ -33,17 +33,17 @@ github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259 h1:2T github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY= github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 h1:2EAfFswAfgYn3a05DVcegiw6DgMgn1Mv5eGz6IHt1Cw= github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -71,8 +71,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -89,16 +87,22 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -122,6 +126,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -134,8 +140,9 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -189,8 +196,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -198,8 +205,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -208,16 +213,14 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -231,16 +234,16 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -250,8 +253,6 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn 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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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= @@ -261,29 +262,31 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/modules/databend/Makefile b/modules/databend/Makefile new file mode 100644 index 0000000000..a8ea6a7163 --- /dev/null +++ b/modules/databend/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-databend diff --git a/modules/databend/databend.go b/modules/databend/databend.go new file mode 100644 index 0000000000..85202bbe44 --- /dev/null +++ b/modules/databend/databend.go @@ -0,0 +1,135 @@ +package databend + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + databendUser = "databend" + defaultUser = "databend" + defaultPassword = "databend" + defaultDatabaseName = "default" +) + +// DatabendContainer represents the Databend container type used in the module +type DatabendContainer struct { + testcontainers.Container + username string + password string + database string +} + +var _ testcontainers.ContainerCustomizer = (*DatabendOption)(nil) + +// DatabendOption is an option for the Databend container. +type DatabendOption func(*DatabendContainer) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o DatabendOption) Customize(*testcontainers.GenericContainerRequest) error { + // NOOP to satisfy interface. + return nil +} + +// Run creates an instance of the Databend container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DatabendContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{"8000/tcp"}, + Env: map[string]string{ + "QUERY_DEFAULT_USER": defaultUser, + "QUERY_DEFAULT_PASSWORD": defaultPassword, + }, + WaitingFor: wait.ForListeningPort("8000/tcp"), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + username := req.Env["QUERY_DEFAULT_USER"] + password := req.Env["QUERY_DEFAULT_PASSWORD"] + if password == "" && username == "" { + return nil, errors.New("empty password and user") + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *DatabendContainer + if container != nil { + c = &DatabendContainer{ + Container: container, + password: password, + username: username, + database: defaultDatabaseName, + } + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// MustConnectionString panics if the address cannot be determined. +func (c *DatabendContainer) MustConnectionString(ctx context.Context, args ...string) string { + addr, err := c.ConnectionString(ctx, args...) + if err != nil { + panic(err) + } + return addr +} + +func (c *DatabendContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { + containerPort, err := c.MappedPort(ctx, "8000/tcp") + if err != nil { + return "", fmt.Errorf("mapped port: %w", err) + } + + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + extraArgs := "" + if len(args) > 0 { + extraArgs = "?" + strings.Join(args, "&") + } + if c.database == "" { + return "", errors.New("database name is empty") + } + + // databend://databend:databend@localhost:8000/default?sslmode=disable + connectionString := fmt.Sprintf("databend://%s:%s@%s:%s/%s%s", c.username, c.password, host, containerPort.Port(), c.database, extraArgs) + return connectionString, nil +} + +// WithUsername sets the username for the Databend container. +// WithUsername is [Run] option that configures the default query user by setting +// the `QUERY_DEFAULT_USER` container environment variable. +func WithUsername(username string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env["QUERY_DEFAULT_USER"] = username + return nil + } +} + +// WithPassword sets the password for the Databend container. +func WithPassword(password string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env["QUERY_DEFAULT_PASSWORD"] = password + return nil + } +} diff --git a/modules/databend/databend_test.go b/modules/databend/databend_test.go new file mode 100644 index 0000000000..58ac71e327 --- /dev/null +++ b/modules/databend/databend_test.go @@ -0,0 +1,74 @@ +package databend_test + +import ( + "context" + "database/sql" + "testing" + + _ "github.com/datafuselabs/databend-go" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/databend" +) + +func TestDatabend(t *testing.T) { + ctx := context.Background() + + ctr, err := databend.Run(ctx, "datafuselabs/databend:v1.2.615") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // perform assertions + // connectionString { + connectionString, err := ctr.ConnectionString(ctx, "sslmode=disable") + // } + require.NoError(t, err) + + mustConnectionString := ctr.MustConnectionString(ctx, "sslmode=disable") + require.Equal(t, connectionString, mustConnectionString) + + db, err := sql.Open("databend", connectionString) + require.NoError(t, err) + defer db.Close() + + err = db.Ping() + require.NoError(t, err) + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + + " `col_1` VARCHAR(128) NOT NULL, \n" + + " `col_2` VARCHAR(128) NOT NULL \n" + + ")") + require.NoError(t, err) +} + +func TestDatabendWithDefaultUserAndPassword(t *testing.T) { + ctx := context.Background() + + ctr, err := databend.Run(ctx, + "datafuselabs/databend:v1.2.615", + databend.WithUsername("databend")) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // perform assertions + connectionString, err := ctr.ConnectionString(ctx, "sslmode=disable") + require.NoError(t, err) + + db, err := sql.Open("databend", connectionString) + require.NoError(t, err) + defer db.Close() + err = db.Ping() + require.NoError(t, err) + + var i int + row := db.QueryRow("select 1") + err = row.Scan(&i) + require.NoError(t, err) + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + + " `col_1` VARCHAR(128) NOT NULL, \n" + + " `col_2` VARCHAR(128) NOT NULL \n" + + ")") + require.NoError(t, err) +} diff --git a/modules/databend/examples_test.go b/modules/databend/examples_test.go new file mode 100644 index 0000000000..ac284ef009 --- /dev/null +++ b/modules/databend/examples_test.go @@ -0,0 +1,88 @@ +package databend_test + +import ( + "context" + "database/sql" + "fmt" + "log" + + _ "github.com/datafuselabs/databend-go" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/databend" +) + +func ExampleRun() { + ctx := context.Background() + + databendContainer, err := databend.Run(ctx, + "datafuselabs/databend:v1.2.615", + databend.WithUsername("test1"), + databend.WithPassword("pass1"), + ) + defer func() { + if err := testcontainers.TerminateContainer(databendContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + state, err := databendContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} + +func ExampleRun_connect() { + ctx := context.Background() + + databendContainer, err := databend.Run(ctx, + "datafuselabs/databend:v1.2.615", + databend.WithUsername("root"), + databend.WithPassword("password"), + ) + defer func() { + if err := testcontainers.TerminateContainer(databendContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + connectionString, err := databendContainer.ConnectionString(ctx, "sslmode=disable") + if err != nil { + log.Printf("failed to get connection string: %s", err) + return + } + + db, err := sql.Open("databend", connectionString) + if err != nil { + log.Printf("failed to connect to Databend: %s", err) + return + } + defer db.Close() + + var i int + row := db.QueryRow("select 1") + err = row.Scan(&i) + if err != nil { + log.Printf("failed to scan result: %s", err) + return + } + + fmt.Println(i) + + // Output: + // 1 +} diff --git a/modules/databend/go.mod b/modules/databend/go.mod new file mode 100644 index 0000000000..c6c0254315 --- /dev/null +++ b/modules/databend/go.mod @@ -0,0 +1,63 @@ +module github.com/testcontainers/testcontainers-go/modules/databend + +go 1.22.0 + +require ( + github.com/datafuselabs/databend-go v0.7.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/databend/go.sum b/modules/databend/go.sum new file mode 100644 index 0000000000..a9ef44ba75 --- /dev/null +++ b/modules/databend/go.sum @@ -0,0 +1,199 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/datafuselabs/databend-go v0.7.0 h1:wPND9I8r/FfcY/nAPo8yeZbh5PMga3ICSDIaq8/eP3o= +github.com/datafuselabs/databend-go v0.7.0/go.mod h1:h/sGUBZs7EqJgqnZ3XB0KHfyUlpGvfNrw2lWcdDJVIw= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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 v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/dolt/dolt.go b/modules/dolt/dolt.go index 3a9a77bce1..9309ce4475 100644 --- a/modules/dolt/dolt.go +++ b/modules/dolt/dolt.go @@ -3,6 +3,7 @@ package dolt import ( "context" "database/sql" + "errors" "fmt" "path/filepath" "strings" @@ -18,8 +19,6 @@ const ( defaultDatabaseName = "test" ) -const defaultImage = "dolthub/dolt-sql-server:1.32.4" - // DoltContainer represents the Dolt container type used in the module type DoltContainer struct { testcontainers.Container @@ -40,10 +39,16 @@ func WithDefaultCredentials() testcontainers.CustomizeRequestOption { } } -// RunContainer creates an instance of the Dolt container type +// Deprecated: use Run instead +// RunContainer creates an instance of the Couchbase container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) { + return Run(ctx, "dolthub/dolt-sql-server:1.32.4", opts...) +} + +// Run creates an instance of the Dolt container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DoltContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "DOLT_USER": defaultUser, @@ -61,7 +66,9 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize opts = append(opts, WithDefaultCredentials()) for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } createUser := true @@ -78,19 +85,24 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } if len(password) == 0 && password == "" && !strings.EqualFold(rootUser, username) { - return nil, fmt.Errorf("empty password can be used only with the root user") + return nil, errors.New("empty password can be used only with the root user") } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var dc *DoltContainer + if container != nil { + dc = &DoltContainer{Container: container, username: username, password: password, database: database} + } if err != nil { - return nil, err + return dc, err } - dc := &DoltContainer{container, username, password, database} - // dolthub/dolt-sql-server does not create user or database, so we do so here - err = dc.initialize(ctx, createUser) - return dc, err + if err = dc.initialize(ctx, createUser); err != nil { + return dc, fmt.Errorf("initialize: %w", err) + } + + return dc, nil } func (c *DoltContainer) initialize(ctx context.Context, createUser bool) error { diff --git a/modules/dolt/dolt_test.go b/modules/dolt/dolt_test.go index 253eb356fb..a1e46cc976 100644 --- a/modules/dolt/dolt_test.go +++ b/modules/dolt/dolt_test.go @@ -9,88 +9,73 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/dolt" ) func TestDolt(t *testing.T) { ctx := context.Background() - container, err := dolt.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - connectionString, err := container.ConnectionString(ctx) + connectionString, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestDoltWithNonRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - _, err := dolt.RunContainer(ctx, + ctr, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), dolt.WithPassword("")) - if err.Error() != "empty password can be used only with the root user" { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.EqualError(t, err, "empty password can be used only with the root user") } func TestDoltWithPublicRemoteCloneUrl(t *testing.T) { ctx := context.Background() - _, err := dolt.RunContainer(ctx, + ctr, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), dolt.WithPassword("test"), dolt.WithScripts(filepath.Join("testdata", "check_clone_public.sh")), dolt.WithDoltCloneRemoteUrl("fake-remote-url")) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func createTestCredsFile(t *testing.T) string { - file, err := os.CreateTemp(os.TempDir(), "prefix") - if err != nil { - t.Fatal(err) - } + t.Helper() + file, err := os.CreateTemp(t.TempDir(), "prefix") + require.NoError(t, err) defer file.Close() _, err = file.WriteString("some-fake-creds") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return file.Name() } @@ -98,8 +83,8 @@ func TestDoltWithPrivateRemoteCloneUrl(t *testing.T) { ctx := context.Background() filename := createTestCredsFile(t) - defer os.RemoveAll(filename) - _, err := dolt.RunContainer(ctx, + ctr, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), dolt.WithPassword("test"), @@ -107,91 +92,65 @@ func TestDoltWithPrivateRemoteCloneUrl(t *testing.T) { dolt.WithDoltCloneRemoteUrl("fake-remote-url"), dolt.WithDoltCredsPublicKey("fake-public-key"), dolt.WithCredsFile(filename)) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestDoltWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := dolt.RunContainer(ctx, + ctr, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("root"), dolt.WithPassword("")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString := container.MustConnectionString(ctx) + connectionString := ctr.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestDoltWithScripts(t *testing.T) { ctx := context.Background() - container, err := dolt.RunContainer(ctx, + ctr, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString := container.MustConnectionString(ctx) + connectionString := ctr.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer stmt.Close() row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/dolt/examples_test.go b/modules/dolt/examples_test.go index ceb6a79a14..ddbf81b079 100644 --- a/modules/dolt/examples_test.go +++ b/modules/dolt/examples_test.go @@ -11,33 +11,33 @@ import ( "github.com/testcontainers/testcontainers-go/modules/dolt" ) -func ExampleRunContainer() { +func ExampleRun() { // runDoltContainer { ctx := context.Background() - doltContainer, err := dolt.RunContainer(ctx, - testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4"), + doltContainer, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithConfigFile(filepath.Join("testdata", "dolt_1_32_4.cnf")), dolt.WithDatabase("foo"), dolt.WithUsername("root"), dolt.WithPassword("password"), dolt.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic - } - - // Clean up the container defer func() { - if err := doltContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(doltContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run dolt container: %s", err) + return + } // } state, err := doltContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -46,48 +46,52 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { ctx := context.Background() - doltContainer, err := dolt.RunContainer(ctx, - testcontainers.WithImage("dolthub/dolt-sql-server:1.32.4"), + doltContainer, err := dolt.Run(ctx, + "dolthub/dolt-sql-server:1.32.4", dolt.WithConfigFile(filepath.Join("testdata", "dolt_1_32_4.cnf")), dolt.WithDatabase("foo"), dolt.WithUsername("bar"), dolt.WithPassword("password"), dolt.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic - } - defer func() { - if err := doltContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(doltContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run dolt container: %s", err) + return + } connectionString := doltContainer.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) if err != nil { - log.Fatalf("failed to open database connection: %s", err) // nolint:gocritic + log.Printf("failed to open database connection: %s", err) + return } defer db.Close() if err = db.Ping(); err != nil { - log.Fatalf("failed to ping database: %s", err) // nolint:gocritic + log.Printf("failed to ping database: %s", err) + return } stmt, err := db.Prepare("SELECT dolt_version();") if err != nil { - log.Fatalf("failed to prepate sql statement: %s", err) // nolint:gocritic + log.Printf("failed to prepate sql statement: %s", err) + return } defer stmt.Close() row := stmt.QueryRow() version := "" err = row.Scan(&version) if err != nil { - log.Fatalf("failed to scan row: %s", err) // nolint:gocritic + log.Printf("failed to scan row: %s", err) + return } fmt.Println(version) diff --git a/modules/dolt/go.mod b/modules/dolt/go.mod index d5c48c8fe9..e79f82e78c 100644 --- a/modules/dolt/go.mod +++ b/modules/dolt/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/dolt -go 1.21 +go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,11 +27,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -49,13 +53,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/dolt/go.sum b/modules/dolt/go.sum index 757cde4b83..96aef09c5a 100644 --- a/modules/dolt/go.sum +++ b/modules/dolt/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -42,8 +43,6 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +53,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -124,23 +133,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -151,38 +156,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/dynamodb/Makefile b/modules/dynamodb/Makefile new file mode 100644 index 0000000000..42d3e1226f --- /dev/null +++ b/modules/dynamodb/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-dynamodb diff --git a/modules/dynamodb/dynamodb.go b/modules/dynamodb/dynamodb.go new file mode 100644 index 0000000000..62a6938efe --- /dev/null +++ b/modules/dynamodb/dynamodb.go @@ -0,0 +1,90 @@ +package dynamodb + +import ( + "context" + "fmt" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + port = "8000/tcp" + containerName = "tc_dynamodb_local" +) + +// DynamoDBContainer represents the DynamoDB container type used in the module +type DynamoDBContainer struct { + testcontainers.Container +} + +// Run creates an instance of the DynamoDB container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*DynamoDBContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{string(port)}, + Entrypoint: []string{"java", "-Djava.library.path=./DynamoDBLocal_lib"}, + Cmd: []string{"-jar", "DynamoDBLocal.jar"}, + WaitingFor: wait.ForListeningPort(port), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, fmt.Errorf("customize: %w", err) + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *DynamoDBContainer + if container != nil { + c = &DynamoDBContainer{Container: container} + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// ConnectionString returns DynamoDB local endpoint host and port in : format +func (c *DynamoDBContainer) ConnectionString(ctx context.Context) (string, error) { + mappedPort, err := c.MappedPort(ctx, port) + if err != nil { + return "", err + } + + hostIP, err := c.Host(ctx) + if err != nil { + return "", err + } + + return hostIP + ":" + mappedPort.Port(), nil +} + +// WithSharedDB allows container reuse between successive runs. Data will be persisted +func WithSharedDB() testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Cmd = append(req.Cmd, "-sharedDb") + + req.Reuse = true + req.Name = containerName + + return nil + } +} + +// WithDisableTelemetry - DynamoDB local will not send any telemetry +func WithDisableTelemetry() testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + // if other flags (e.g. -sharedDb) exist, append to them + req.Cmd = append(req.Cmd, "-disableTelemetry") + + return nil + } +} diff --git a/modules/dynamodb/dynamodb_test.go b/modules/dynamodb/dynamodb_test.go new file mode 100644 index 0000000000..b62766d813 --- /dev/null +++ b/modules/dynamodb/dynamodb_test.go @@ -0,0 +1,260 @@ +package dynamodb_test + +import ( + "context" + "errors" + "fmt" + "net/url" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + smithyendpoints "github.com/aws/smithy-go/endpoints" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + tcdynamodb "github.com/testcontainers/testcontainers-go/modules/dynamodb" +) + +const ( + tableName string = "demo_table" + pkColumnName string = "demo_pk" + baseImage string = "amazon/dynamodb-local:" +) + +var image2_2_1 string = baseImage + "2.2.1" + +func TestRun(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, image2_2_1) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + cli := getDynamoDBClient(t, ctr) + require.NoError(t, err, "failed to get dynamodb client handle") + + requireTableExists(t, cli, tableName) + + value := "test_value" + addDataToTable(t, cli, value) + + queryResult := queryItem(t, cli, value) + require.Equal(t, value, queryResult) +} + +func TestRun_withCustomImageVersion(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, "amazon/dynamodb-local:2.2.0") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) +} + +func TestRun_withInvalidCustomImageVersion(t *testing.T) { + ctx := context.Background() + + _, err := tcdynamodb.Run(ctx, "amazon/dynamodb-local:0.0.7") + require.Error(t, err) +} + +func TestRun_withoutEndpointResolver(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, image2_2_1) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err, "container should start successfully") + + cli := dynamodb.New(dynamodb.Options{}) + + err = createTable(cli) + require.Error(t, err) +} + +func TestRun_withSharedDB(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, image2_2_1, tcdynamodb.WithSharedDB()) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + cli1 := getDynamoDBClient(t, ctr) + + requireTableExists(t, cli1, tableName) + + // create a second container: it should have the table created in the first container + ctr2, err := tcdynamodb.Run(ctx, image2_2_1, tcdynamodb.WithSharedDB()) + testcontainers.CleanupContainer(t, ctr2) + require.NoError(t, err) + + // fetch client handle again + cli2 := getDynamoDBClient(t, ctr2) + require.NoError(t, err, "failed to get dynamodb client handle") + + // list tables and verify + + result, err := cli2.ListTables(context.Background(), nil) + require.NoError(t, err, "dynamodb list tables operation failed") + + actualTableName := result.TableNames[0] + require.Equal(t, tableName, actualTableName) + + // add and query data from the second container + value := "test_value" + addDataToTable(t, cli2, value) + + // read data from the first container + queryResult := queryItem(t, cli1, value) + require.NoError(t, err) + require.Equal(t, value, queryResult) +} + +func TestRun_withoutSharedDB(t *testing.T) { + ctx := context.Background() + + ctr1, err := tcdynamodb.Run(ctx, image2_2_1) + testcontainers.CleanupContainer(t, ctr1) + require.NoError(t, err) + + cli := getDynamoDBClient(t, ctr1) + require.NoError(t, err, "failed to get dynamodb client handle") + + requireTableExists(t, cli, tableName) + + // create a second container: it should not have the table created in the first container + ctr2, err := tcdynamodb.Run(ctx, image2_2_1) + testcontainers.CleanupContainer(t, ctr2) + require.NoError(t, err) + + // fetch client handle again + cli = getDynamoDBClient(t, ctr2) + require.NoError(t, err, "failed to get dynamodb client handle") + + // list tables and verify + + result, err := cli.ListTables(context.Background(), nil) + require.NoError(t, err, "dynamodb list tables operation failed") + require.Empty(t, result.TableNames, "table should not exist after restarting container") +} + +func TestRun_shouldStartWithTelemetryDisabled(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, image2_2_1, tcdynamodb.WithDisableTelemetry()) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) +} + +func TestRun_shouldStartWithSharedDBEnabledAndTelemetryDisabled(t *testing.T) { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, image2_2_1, tcdynamodb.WithSharedDB(), tcdynamodb.WithDisableTelemetry()) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) +} + +func createTable(client *dynamodb.Client) error { + _, err := client.CreateTable(context.Background(), &dynamodb.CreateTableInput{ + TableName: aws.String(tableName), + KeySchema: []types.KeySchemaElement{ + { + AttributeName: aws.String(pkColumnName), + KeyType: types.KeyTypeHash, + }, + }, + AttributeDefinitions: []types.AttributeDefinition{ + { + AttributeName: aws.String(pkColumnName), + AttributeType: types.ScalarAttributeTypeS, + }, + }, + BillingMode: types.BillingModePayPerRequest, + }) + if err != nil { + return fmt.Errorf("create table: %w", err) + } + + return nil +} + +func addDataToTable(t *testing.T, client *dynamodb.Client, val string) { + t.Helper() + + _, err := client.PutItem(context.Background(), &dynamodb.PutItemInput{ + TableName: aws.String(tableName), + Item: map[string]types.AttributeValue{ + pkColumnName: &types.AttributeValueMemberS{Value: val}, + }, + }) + require.NoError(t, err) +} + +func queryItem(t *testing.T, client *dynamodb.Client, val string) string { + t.Helper() + + output, err := client.GetItem(context.Background(), &dynamodb.GetItemInput{ + TableName: aws.String(tableName), + Key: map[string]types.AttributeValue{ + pkColumnName: &types.AttributeValueMemberS{Value: val}, + }, + }) + require.NoError(t, err) + + result := output.Item[pkColumnName].(*types.AttributeValueMemberS) + + return result.Value +} + +type dynamoDBResolver struct { + HostPort string +} + +func (r *dynamoDBResolver) ResolveEndpoint(ctx context.Context, params dynamodb.EndpointParameters) (smithyendpoints.Endpoint, error) { + return smithyendpoints.Endpoint{ + URI: url.URL{Host: r.HostPort, Scheme: "http"}, + }, nil +} + +// getDynamoDBClient returns a new DynamoDB client with the endpoint resolver set to the DynamoDB container's host and port +func getDynamoDBClient(t *testing.T, c *tcdynamodb.DynamoDBContainer) *dynamodb.Client { + t.Helper() + + // createClient { + var errs []error + + hostPort, err := c.ConnectionString(context.Background()) + if err != nil { + errs = append(errs, fmt.Errorf("get connection string: %w", err)) + } + + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(credentials.StaticCredentialsProvider{ + Value: aws.Credentials{ + AccessKeyID: "DUMMYIDEXAMPLE", + SecretAccessKey: "DUMMYEXAMPLEKEY", + }, + })) + if err != nil { + errs = append(errs, fmt.Errorf("load default config: %w", err)) + } + + require.NoError(t, errors.Join(errs...)) + + return dynamodb.NewFromConfig(cfg, dynamodb.WithEndpointResolverV2(&dynamoDBResolver{HostPort: hostPort})) + // } +} + +func requireTableExists(t *testing.T, cli *dynamodb.Client, tableName string) { + t.Helper() + + err := createTable(cli) + require.NoError(t, err) + + result, err := cli.ListTables(context.Background(), nil) + require.NoError(t, err, "dynamodb list tables operation failed") + + actualTableName := result.TableNames[0] + require.Equal(t, tableName, actualTableName) +} diff --git a/modules/dynamodb/examples_test.go b/modules/dynamodb/examples_test.go new file mode 100644 index 0000000000..e4e478b943 --- /dev/null +++ b/modules/dynamodb/examples_test.go @@ -0,0 +1,38 @@ +package dynamodb_test + +import ( + "context" + "fmt" + "log" + + "github.com/testcontainers/testcontainers-go" + tcdynamodb "github.com/testcontainers/testcontainers-go/modules/dynamodb" +) + +func ExampleRun() { + // runDynamoDBContainer { + ctx := context.Background() + + ctr, err := tcdynamodb.Run(ctx, "amazon/dynamodb-local:2.2.1") + defer func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to run dynamodb container: %s", err) + return + } + // } + + state, err := ctr.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} diff --git a/modules/dynamodb/go.mod b/modules/dynamodb/go.mod new file mode 100644 index 0000000000..4a3b063bd4 --- /dev/null +++ b/modules/dynamodb/go.mod @@ -0,0 +1,76 @@ +module github.com/testcontainers/testcontainers-go/modules/dynamodb + +go 1.22 + +require ( + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/config v1.27.37 + github.com/aws/aws-sdk-go-v2/credentials v1.17.35 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1 + github.com/aws/smithy-go v1.21.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/dynamodb/go.sum b/modules/dynamodb/go.sum new file mode 100644 index 0000000000..80a9c6e219 --- /dev/null +++ b/modules/dynamodb/go.sum @@ -0,0 +1,228 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/config v1.27.37 h1:xaoIwzHVuRWRHFI0jhgEdEGc8xE1l91KaeRDsWEIncU= +github.com/aws/aws-sdk-go-v2/config v1.27.37/go.mod h1:S2e3ax9/8KnMSyRVNd3sWTKs+1clJ2f1U6nE0lpvQRg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.35 h1:7QknrZhYySEB1lEXJxGAmuD5sWwys5ZXNr4m5oEz0IE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.35/go.mod h1:8Vy4kk7at4aPSmibr7K+nLTzG6qUQAUO4tW49fzUV4E= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1 h1:DDN8yqYzFUDy2W5zk3tLQNKaO/1t0h3fNixPJacu264= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.1/go.mod h1:k5XW8MoMxsNZ20RJmsokakvENUwQyjv69R9GqrI4xdQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 h1:dOxqOlOEa2e2heC/74+ZzcJOa27+F1aXFZpYgY/4QfA= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19/go.mod h1:aV6U1beLFvk3qAgognjS3wnGGoDId8hlPEiBsLHXVZE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.1 h1:2jrVsMHqdLD1+PA4BA6Nh1eZp0Gsy3mFSB5MxDvcJtU= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.1/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1 h1:0L7yGCg3Hb3YQqnSgBTZM5wepougtL1aEccdcdYhHME= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 h1:8K0UNOkZiK9Uh3HIF6Bx0rcNCftqGCeKmOaR7Gp5BSo= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.1/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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 v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/elasticsearch/elasticsearch.go b/modules/elasticsearch/elasticsearch.go index 2ea0a8b8ba..96f97ef8c1 100644 --- a/modules/elasticsearch/elasticsearch.go +++ b/modules/elasticsearch/elasticsearch.go @@ -2,6 +2,9 @@ package elasticsearch import ( "context" + "crypto/tls" + "crypto/x509" + "errors" "fmt" "io" "os" @@ -15,11 +18,14 @@ const ( defaultTCPPort = "9300" defaultPassword = "changeme" defaultUsername = "elastic" + defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt" minimalImageVersion = "7.9.2" ) const ( - DefaultBaseImage = "docker.elastic.co/elasticsearch/elasticsearch" + // Deprecated: it will be removed in the next major version + DefaultBaseImage = "docker.elastic.co/elasticsearch/elasticsearch" + // Deprecated: it will be removed in the next major version DefaultBaseImageOSS = "docker.elastic.co/elasticsearch/elasticsearch-oss" ) @@ -29,11 +35,17 @@ type ElasticsearchContainer struct { Settings Options } +// Deprecated: use Run instead // RunContainer creates an instance of the Elasticsearch container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ElasticsearchContainer, error) { + return Run(ctx, "docker.elastic.co/elasticsearch/elasticsearch:7.9.2", opts...) +} + +// Run creates an instance of the Elasticsearch container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ElasticsearchContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: fmt.Sprintf("%s:%s", DefaultBaseImage, minimalImageVersion), + Image: img, Env: map[string]string{ "discovery.type": "single-node", "cluster.routing.allocation.disk.threshold_enabled": "false", @@ -42,78 +54,119 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize defaultHTTPPort + "/tcp", defaultTCPPort + "/tcp", }, - // regex that - // matches 8.3 JSON logging with started message and some follow up content within the message field - // matches 8.0 JSON logging with no whitespace between message field and content - // matches 7.x JSON logging with whitespace between message field and content - // matches 6.x text logging with node name in brackets and just a 'started' message till the end of the line - WaitingFor: wait.ForLog(`.*("message":\s?"started(\s|")?.*|]\sstarted\n)`).AsRegexp(), - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ - { - // the container needs a post create hook to set the default JVM options in a file - PostCreates: []testcontainers.ContainerHook{}, - PostReadies: []testcontainers.ContainerHook{}, - }, - }, }, Started: true, } // Gather all config options (defaults and then apply provided options) - settings := defaultOptions() + options := defaultOptions() for _, opt := range opts { if apply, ok := opt.(Option); ok { - apply(settings) + apply(options) } if err := opt.Customize(&req); err != nil { return nil, err } } - // Transfer the certificate settings to the container request - err := configureCertificate(settings, &req) - if err != nil { - return nil, err - } - // Transfer the password settings to the container request - err = configurePassword(settings, &req) - if err != nil { + if err := configurePassword(options, &req); err != nil { return nil, err } if isAtLeastVersion(req.Image, 7) { - req.LifecycleHooks[0].PostCreates = append(req.LifecycleHooks[0].PostCreates, configureJvmOpts) + req.LifecycleHooks = append(req.LifecycleHooks, + testcontainers.ContainerLifecycleHooks{ + PostCreates: []testcontainers.ContainerHook{configureJvmOpts}, + }, + ) } + // Set the default waiting strategy if not already set. + setWaitFor(options, &req.ContainerRequest) + container, err := testcontainers.GenericContainer(ctx, req) + var esContainer *ElasticsearchContainer + if container != nil { + esContainer = &ElasticsearchContainer{Container: container, Settings: *options} + } if err != nil { - return nil, err + return esContainer, fmt.Errorf("generic container: %w", err) } - esContainer := &ElasticsearchContainer{Container: container, Settings: *settings} + if err := esContainer.configureAddress(ctx); err != nil { + return esContainer, fmt.Errorf("configure address: %w", err) + } + + return esContainer, nil +} + +// certWriter is a helper that writes the details of a CA cert to options. +type certWriter struct { + options *Options + certPool *x509.CertPool +} - address, err := configureAddress(ctx, esContainer) +// Read reads the CA cert from the reader and appends it to the options. +func (w *certWriter) Read(r io.Reader) error { + buf, err := io.ReadAll(r) if err != nil { - return nil, err + return fmt.Errorf("read CA cert: %w", err) } - esContainer.Settings.Address = address + w.options.CACert = buf + w.certPool.AppendCertsFromPEM(w.options.CACert) - return esContainer, nil + return nil +} + +// setWaitFor sets the req.WaitingFor strategy based on settings. +func setWaitFor(options *Options, req *testcontainers.ContainerRequest) { + var strategies []wait.Strategy + if req.WaitingFor != nil { + // Custom waiting strategy, ensure we honour it. + strategies = append(strategies, req.WaitingFor) + } + + waitHTTP := wait.ForHTTP("/").WithPort(defaultHTTPPort) + if sslRequired(req) { + waitHTTP = waitHTTP.WithTLS(true).WithAllowInsecure(true) + cw := &certWriter{ + options: options, + certPool: x509.NewCertPool(), + } + + waitHTTP = waitHTTP. + WithTLS(true, &tls.Config{RootCAs: cw.certPool}) + + strategies = append(strategies, wait.ForFile(defaultCaCertPath).WithMatcher(cw.Read)) + } + + if options.Password != "" || options.Username != "" { + waitHTTP = waitHTTP.WithBasicAuth(options.Username, options.Password) + } + + strategies = append(strategies, waitHTTP) + + if len(strategies) > 1 { + req.WaitingFor = wait.ForAll(strategies...) + return + } + + req.WaitingFor = strategies[0] } // configureAddress sets the address of the Elasticsearch container. // If the certificate is set, it will use https as protocol, otherwise http. -func configureAddress(ctx context.Context, c *ElasticsearchContainer) (string, error) { +func (c *ElasticsearchContainer) configureAddress(ctx context.Context) error { containerPort, err := c.MappedPort(ctx, defaultHTTPPort+"/tcp") if err != nil { - return "", err + return fmt.Errorf("mapped port: %w", err) } host, err := c.Host(ctx) if err != nil { - return "", err + return fmt.Errorf("host: %w", err) } proto := "http" @@ -121,53 +174,33 @@ func configureAddress(ctx context.Context, c *ElasticsearchContainer) (string, e proto = "https" } - return fmt.Sprintf("%s://%s:%s", proto, host, containerPort.Port()), nil + c.Settings.Address = fmt.Sprintf("%s://%s:%s", proto, host, containerPort.Port()) + + return nil } -// configureCertificate transfers the certificate settings to the container request. -// For that, it defines a post start hook that copies the certificate from the container to the host. -// The certificate is only available since version 8, and will be located in a well-known location. -func configureCertificate(settings *Options, req *testcontainers.GenericContainerRequest) error { - if isAtLeastVersion(req.Image, 8) { - // These configuration keys explicitly disable CA generation. - // If any are set we skip the file retrieval. - configKeys := []string{ - "xpack.security.enabled", - "xpack.security.http.ssl.enabled", - "xpack.security.transport.ssl.enabled", - } - for _, configKey := range configKeys { - if value, ok := req.Env[configKey]; ok { - if value == "false" { - return nil - } +// sslRequired returns true if the SSL is required, otherwise false. +func sslRequired(req *testcontainers.ContainerRequest) bool { + if !isAtLeastVersion(req.Image, 8) { + return false + } + + // These configuration keys explicitly disable CA generation. + // If any are set we skip the file retrieval. + configKeys := []string{ + "xpack.security.enabled", + "xpack.security.http.ssl.enabled", + "xpack.security.transport.ssl.enabled", + } + for _, configKey := range configKeys { + if value, ok := req.Env[configKey]; ok { + if value == "false" { + return false } } - - // The container needs a post ready hook to copy the certificate from the container to the host. - // This certificate is only available since version 8 - req.LifecycleHooks[0].PostReadies = append(req.LifecycleHooks[0].PostReadies, - func(ctx context.Context, container testcontainers.Container) error { - const defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt" - - readCloser, err := container.CopyFileFromContainer(ctx, defaultCaCertPath) - if err != nil { - return err - } - - // receive the bytes from the default location - certBytes, err := io.ReadAll(readCloser) - if err != nil { - return err - } - - settings.CACert = certBytes - - return nil - }) } - return nil + return true } // configurePassword transfers the password settings to the container request. @@ -180,7 +213,7 @@ func configurePassword(settings *Options, req *testcontainers.GenericContainerRe if settings.Password != "" { if isOSS(req.Image) { - return fmt.Errorf("it's not possible to activate security on Elastic OSS Image. Please switch to the default distribution.") + return errors.New("it's not possible to activate security on Elastic OSS Image. Please switch to the default distribution.") } if _, ok := req.Env["ELASTIC_PASSWORD"]; !ok { diff --git a/modules/elasticsearch/elasticsearch_test.go b/modules/elasticsearch/elasticsearch_test.go index 0bfdca7793..14c5640e72 100644 --- a/modules/elasticsearch/elasticsearch_test.go +++ b/modules/elasticsearch/elasticsearch_test.go @@ -8,6 +8,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/elasticsearch" ) @@ -73,35 +75,24 @@ func TestElasticsearch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - opts := []testcontainers.ContainerCustomizer{testcontainers.WithImage(tt.image)} + opts := []testcontainers.ContainerCustomizer{} if tt.passwordCustomiser != nil { opts = append(opts, tt.passwordCustomiser) } - esContainer, err := elasticsearch.RunContainer(ctx, opts...) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := esContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + esContainer, err := elasticsearch.Run(ctx, tt.image, opts...) + testcontainers.CleanupContainer(t, esContainer) + require.NoError(t, err) httpClient := configureHTTPClient(esContainer) - req, err := http.NewRequest("GET", esContainer.Settings.Address, nil) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, esContainer.Settings.Address, nil) + require.NoError(t, err) // set the password for the request using the Authentication header if tt.passwordCustomiser != nil { - if esContainer.Settings.Username != "elastic" { - t.Fatal("expected username to be elastic but got", esContainer.Settings.Username) - } + require.Equalf(t, "elastic", esContainer.Settings.Username, "expected username to be elastic but got: %s", esContainer.Settings.Username) // basicAuthHeader { req.SetBasicAuth(esContainer.Settings.Username, esContainer.Settings.Password) @@ -109,56 +100,33 @@ func TestElasticsearch(t *testing.T) { } resp, err := httpClient.Do(req) - if resp != nil { - defer resp.Body.Close() - } + require.NoError(t, err) + require.NotNil(t, resp) + defer resp.Body.Close() - if tt.image != baseImage8 && err != nil { - if tt.passwordCustomiser != nil { - t.Fatal(err, "should access with authorised HTTP client.") - } else if tt.passwordCustomiser == nil { - t.Fatal(err, "should access with unauthorised HTTP client.") - } + if tt.image == baseImage8 && tt.passwordCustomiser == nil { + // Elasticsearch 8 should return 401 Unauthorized, not an error in the request + require.Equalf(t, http.StatusUnauthorized, resp.StatusCode, "expected 401 status code for unauthorised HTTP client using TLS, but got: %s", resp.StatusCode) + + // finish validating the response when the request is unauthorised + return } - if tt.image == baseImage8 { - if tt.passwordCustomiser != nil && err != nil { - t.Fatal(err, "should access with authorised HTTP client using TLS.") - } - if tt.passwordCustomiser == nil && err == nil { - // Elasticsearch 8 should return 401 Unauthorized, not an error in the request - if resp.StatusCode != http.StatusUnauthorized { - t.Fatal("expected 401 status code for unauthorised HTTP client using TLS, but got", resp.StatusCode) - } + // validate Elasticsearch response + require.Equalf(t, http.StatusOK, resp.StatusCode, "expected 200 status code but got: %s", resp.StatusCode) - // finish validating the response when the request is unauthorised - return - } + var esResp ElasticsearchResponse + err = json.NewDecoder(resp.Body).Decode(&esResp) + require.NoError(t, err) + switch tt.image { + case baseImage7: + require.Equalf(t, "7.9.2", esResp.Version.Number, "expected version to be 7.9.2 but got: %s", esResp.Version.Number) + case baseImage8: + require.Equalf(t, "8.9.0", esResp.Version.Number, "expected version to be 8.9.0 but got: %s", esResp.Version.Number) } - // validate response - if resp != nil { - // validate Elasticsearch response - if resp.StatusCode != http.StatusOK { - t.Fatal("expected 200 status code but got", resp.StatusCode) - } - - var esResp ElasticsearchResponse - if err := json.NewDecoder(resp.Body).Decode(&esResp); err != nil { - t.Fatal(err) - } - - if tt.image == baseImage7 && esResp.Version.Number != "7.9.2" { - t.Fatal("expected version to be 7.9.2 but got", esResp.Version.Number) - } else if tt.image == baseImage8 && esResp.Version.Number != "8.9.0" { - t.Fatal("expected version to be 8.9.0 but got", esResp.Version.Number) - } - - if esResp.Tagline != "You Know, for Search" { - t.Fatal("expected tagline to be 'You Know, for Search' but got", esResp.Tagline) - } - } + require.Equalf(t, "You Know, for Search", esResp.Tagline, "expected tagline to be 'You Know, for Search' but got: %s", esResp.Tagline) }) } } @@ -184,69 +152,45 @@ func TestElasticsearch8WithoutSSL(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { ctx := context.Background() - container, err := elasticsearch.RunContainer( + ctr, err := elasticsearch.Run( ctx, - testcontainers.WithImage(baseImage8), + baseImage8, testcontainers.WithEnv(map[string]string{ test.configKey: "false", })) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - if len(container.Settings.CACert) > 0 { - t.Fatal("expected CA cert to be empty") - } + require.Emptyf(t, ctr.Settings.CACert, "expected CA cert to be empty") }) } - } func TestElasticsearch8WithoutCredentials(t *testing.T) { ctx := context.Background() - container, err := elasticsearch.RunContainer(ctx, testcontainers.WithImage(baseImage8)) - if err != nil { - t.Fatal(err) - } + ctr, err := elasticsearch.Run(ctx, baseImage8) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + httpClient := configureHTTPClient(ctr) - httpClient := configureHTTPClient(container) - - req, err := http.NewRequest("GET", container.Settings.Address, nil) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, ctr.Settings.Address, nil) + require.NoError(t, err) // elastic:changeme are the default credentials for Elasticsearch 8 - req.SetBasicAuth(container.Settings.Username, container.Settings.Password) + req.SetBasicAuth(ctr.Settings.Username, ctr.Settings.Password) resp, err := httpClient.Do(req) - if err != nil { - t.Fatal(err, "Should be able to access / URI with client using default password over HTTPS.") - } + require.NoErrorf(t, err, "Should be able to access / URI with client using default password over HTTPS.") defer resp.Body.Close() var esResp ElasticsearchResponse - if err := json.NewDecoder(resp.Body).Decode(&esResp); err != nil { - t.Fatal(err) - } + err = json.NewDecoder(resp.Body).Decode(&esResp) + require.NoError(t, err) - if esResp.Tagline != "You Know, for Search" { - t.Fatal("expected tagline to be 'You Know, for Search' but got", esResp.Tagline) - } + require.Equalf(t, "You Know, for Search", esResp.Tagline, "expected tagline to be 'You Know, for Search' but got: %s", esResp.Tagline) } func TestElasticsearchOSSCannotuseWithPassword(t *testing.T) { @@ -254,10 +198,9 @@ func TestElasticsearchOSSCannotuseWithPassword(t *testing.T) { ossImage := elasticsearch.DefaultBaseImageOSS + ":7.9.2" - _, err := elasticsearch.RunContainer(ctx, testcontainers.WithImage(ossImage), elasticsearch.WithPassword("foo")) - if err == nil { - t.Fatal(err, "Should not be able to use WithPassword with OSS image.") - } + ctr, err := elasticsearch.Run(ctx, ossImage, elasticsearch.WithPassword("foo")) + testcontainers.CleanupContainer(t, ctr) + require.Errorf(t, err, "Should not be able to use WithPassword with OSS image.") } // configureHTTPClient configures an HTTP client for the Elasticsearch container. diff --git a/modules/elasticsearch/examples_test.go b/modules/elasticsearch/examples_test.go index 09ba893e43..f4ada5df60 100644 --- a/modules/elasticsearch/examples_test.go +++ b/modules/elasticsearch/examples_test.go @@ -13,23 +13,25 @@ import ( "github.com/testcontainers/testcontainers-go/modules/elasticsearch" ) -func ExampleRunContainer() { +func ExampleRun() { // runElasticsearchContainer { ctx := context.Background() - elasticsearchContainer, err := elasticsearch.RunContainer(ctx, testcontainers.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.9.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } + elasticsearchContainer, err := elasticsearch.Run(ctx, "docker.elastic.co/elasticsearch/elasticsearch:8.9.0") defer func() { - if err := elasticsearchContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := elasticsearchContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -38,23 +40,23 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_withUsingPassword() { +func ExampleRun_withUsingPassword() { // usingPassword { ctx := context.Background() - elasticsearchContainer, err := elasticsearch.RunContainer( + elasticsearchContainer, err := elasticsearch.Run( ctx, - testcontainers.WithImage("docker.elastic.co/elasticsearch/elasticsearch:7.9.2"), + "docker.elastic.co/elasticsearch/elasticsearch:7.9.2", elasticsearch.WithPassword("foo"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - err := elasticsearchContainer.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } fmt.Println(strings.HasPrefix(elasticsearchContainer.Settings.Address, "http://")) @@ -65,23 +67,23 @@ func ExampleRunContainer_withUsingPassword() { // foo } -func ExampleRunContainer_connectUsingElasticsearchClient() { +func ExampleRun_connectUsingElasticsearchClient() { // elasticsearchClient { ctx := context.Background() - elasticsearchContainer, err := elasticsearch.RunContainer( + elasticsearchContainer, err := elasticsearch.Run( ctx, - testcontainers.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.9.0"), + "docker.elastic.co/elasticsearch/elasticsearch:8.9.0", elasticsearch.WithPassword("foo"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - err := elasticsearchContainer.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } cfg := es.Config{ Addresses: []string{ @@ -94,19 +96,22 @@ func ExampleRunContainer_connectUsingElasticsearchClient() { esClient, err := es.NewClient(cfg) if err != nil { - log.Fatalf("error creating the client: %s", err) // nolint:gocritic + log.Printf("error creating the client: %s", err) + return } resp, err := esClient.Info() if err != nil { - log.Fatalf("error getting response: %s", err) + log.Printf("error getting response: %s", err) + return } defer resp.Body.Close() // } var esResp ElasticsearchResponse if err := json.NewDecoder(resp.Body).Decode(&esResp); err != nil { - log.Fatalf("error decoding response: %s", err) + log.Printf("error decoding response: %s", err) + return } fmt.Println(esResp.Tagline) diff --git a/modules/elasticsearch/go.mod b/modules/elasticsearch/go.mod index c2e0ed79b1..daaa6e8fac 100644 --- a/modules/elasticsearch/go.mod +++ b/modules/elasticsearch/go.mod @@ -1,26 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/elasticsearch -go 1.21 +go 1.22 require ( github.com/elastic/go-elasticsearch/v8 v8.12.1 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 golang.org/x/mod v0.16.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elastic/elastic-transport-go/v8 v8.4.0 // indirect @@ -29,12 +29,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -55,12 +55,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/elasticsearch/go.sum b/modules/elasticsearch/go.sum index 0ef00ba63f..42bbadf11f 100644 --- a/modules/elasticsearch/go.sum +++ b/modules/elasticsearch/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -45,8 +45,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -57,8 +55,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= @@ -67,6 +65,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -134,8 +136,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -144,13 +146,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -161,33 +161,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -196,5 +193,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/elasticsearch/options.go b/modules/elasticsearch/options.go index ed801c3b09..ba4dca75c3 100644 --- a/modules/elasticsearch/options.go +++ b/modules/elasticsearch/options.go @@ -16,7 +16,6 @@ type Options struct { func defaultOptions() *Options { return &Options{ - CACert: nil, Username: defaultUsername, } } diff --git a/modules/elasticsearch/version.go b/modules/elasticsearch/version.go index 9ddc2836ad..3124e312ab 100644 --- a/modules/elasticsearch/version.go +++ b/modules/elasticsearch/version.go @@ -22,7 +22,7 @@ func isAtLeastVersion(image string, major int) bool { } if !strings.HasPrefix(version, "v") { - version = fmt.Sprintf("v%s", version) + version = "v" + version } if semver.IsValid(version) { diff --git a/modules/etcd/Makefile b/modules/etcd/Makefile new file mode 100644 index 0000000000..7531baef98 --- /dev/null +++ b/modules/etcd/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-etcd diff --git a/modules/etcd/cmd_test.go b/modules/etcd/cmd_test.go new file mode 100644 index 0000000000..918c68dc84 --- /dev/null +++ b/modules/etcd/cmd_test.go @@ -0,0 +1,108 @@ +package etcd + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_configureCMD(t *testing.T) { + t.Run("default", func(t *testing.T) { + got := configureCMD(options{}) + want := []string{"etcd", "--name=default"} + require.Equal(t, want, got) + }) + + t.Run("with-node", func(t *testing.T) { + got := configureCMD(options{ + nodeNames: []string{"node1"}, + }) + want := []string{ + "etcd", + "--name=node1", + "--initial-advertise-peer-urls=http://node1:2380", + "--advertise-client-urls=http://node1:2379", + "--listen-peer-urls=http://0.0.0.0:2380", + "--listen-client-urls=http://0.0.0.0:2379", + "--initial-cluster-state=new", + "--initial-cluster=node1=http://node1:2380", + } + require.Equal(t, want, got) + }) + + t.Run("with-node-datadir", func(t *testing.T) { + got := configureCMD(options{ + nodeNames: []string{"node1"}, + mountDataDir: true, + }) + want := []string{ + "etcd", + "--name=node1", + "--initial-advertise-peer-urls=http://node1:2380", + "--advertise-client-urls=http://node1:2379", + "--listen-peer-urls=http://0.0.0.0:2380", + "--listen-client-urls=http://0.0.0.0:2379", + "--initial-cluster-state=new", + "--initial-cluster=node1=http://node1:2380", + "--data-dir=/data.etcd", + } + require.Equal(t, want, got) + }) + + t.Run("with-node-datadir-additional-args", func(t *testing.T) { + got := configureCMD(options{ + nodeNames: []string{"node1"}, + mountDataDir: true, + additionalArgs: []string{"--auto-compaction-retention=1"}, + }) + want := []string{ + "etcd", + "--name=node1", + "--initial-advertise-peer-urls=http://node1:2380", + "--advertise-client-urls=http://node1:2379", + "--listen-peer-urls=http://0.0.0.0:2380", + "--listen-client-urls=http://0.0.0.0:2379", + "--initial-cluster-state=new", + "--initial-cluster=node1=http://node1:2380", + "--data-dir=/data.etcd", + "--auto-compaction-retention=1", + } + require.Equal(t, want, got) + }) + + t.Run("with-cluster", func(t *testing.T) { + got := configureCMD(options{ + nodeNames: []string{"node1", "node2"}, + }) + want := []string{ + "etcd", + "--name=node1", + "--initial-advertise-peer-urls=http://node1:2380", + "--advertise-client-urls=http://node1:2379", + "--listen-peer-urls=http://0.0.0.0:2380", + "--listen-client-urls=http://0.0.0.0:2379", + "--initial-cluster-state=new", + "--initial-cluster=node1=http://node1:2380,node2=http://node2:2380", + } + require.Equal(t, want, got) + }) + + t.Run("with-cluster-token", func(t *testing.T) { + got := configureCMD(options{ + nodeNames: []string{"node1", "node2"}, + clusterToken: "token", + }) + want := []string{ + "etcd", + "--name=node1", + "--initial-advertise-peer-urls=http://node1:2380", + "--advertise-client-urls=http://node1:2379", + "--listen-peer-urls=http://0.0.0.0:2380", + "--listen-client-urls=http://0.0.0.0:2379", + "--initial-cluster-state=new", + "--initial-cluster=node1=http://node1:2380,node2=http://node2:2380", + "--initial-cluster-token=token", + } + require.Equal(t, want, got) + }) +} diff --git a/modules/etcd/etcd.go b/modules/etcd/etcd.go new file mode 100644 index 0000000000..7ea78b4385 --- /dev/null +++ b/modules/etcd/etcd.go @@ -0,0 +1,272 @@ +package etcd + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/testcontainers/testcontainers-go" + tcnetwork "github.com/testcontainers/testcontainers-go/network" +) + +const ( + clientPort = "2379" + peerPort = "2380" + dataDir = "/data.etcd" + defaultClusterToken = "mys3cr3ttok3n" + scheme = "http" +) + +// EtcdContainer represents the etcd container type used in the module. It can be used to create a single-node instance or a cluster. +// For the cluster, the first node creates the cluster and the other nodes join it as child nodes. +type EtcdContainer struct { + testcontainers.Container + // childNodes contains the child nodes of the current node, forming a cluster + childNodes []*EtcdContainer + opts options +} + +// Terminate terminates the etcd container, its child nodes, and the network in which the cluster is running +// to communicate between the nodes. +func (c *EtcdContainer) Terminate(ctx context.Context) error { + var errs []error + + // child nodes has no other children + for i, child := range c.childNodes { + if err := child.Terminate(ctx); err != nil { + errs = append(errs, fmt.Errorf("terminate child node(%d): %w", i, err)) + } + } + + if c.Container != nil { + if err := c.Container.Terminate(ctx); err != nil { + errs = append(errs, fmt.Errorf("terminate cluster node: %w", err)) + } + } + + // remove the cluster network if it was created, but only for the first node + // we could check if the current node is the first one (index 0), + // and/or check that there are no child nodes + if c.opts.clusterNetwork != nil && c.opts.currentNode == 0 { + if err := c.opts.clusterNetwork.Remove(ctx); err != nil { + errs = append(errs, fmt.Errorf("remove cluster network: %w", err)) + } + } + + return errors.Join(errs...) +} + +// Run creates an instance of the etcd container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*EtcdContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + Cmd: []string{}, + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + settings := defaultOptions(&req) + for _, opt := range opts { + if apply, ok := opt.(Option); ok { + apply(&settings) + } + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + clusterOpts, err := configureCluster(ctx, &settings, opts) + if err != nil { + return nil, fmt.Errorf("configure cluster: %w", err) + } + + // configure CMD with the nodes + genericContainerReq.Cmd = configureCMD(settings) + + // Initialise the etcd container with the current settings. + // The cluster network, if needed, is already part of the settings, + // so the following error handling returns a partially initialised container, + // allowing the caller to clean up the resources with the Terminate method. + c := &EtcdContainer{opts: settings} + + if settings.clusterNetwork != nil { + // apply the network to the current node + err := tcnetwork.WithNetwork([]string{settings.nodeNames[settings.currentNode]}, settings.clusterNetwork)(&genericContainerReq) + if err != nil { + return c, fmt.Errorf("with network: %w", err) + } + } + + if c.Container, err = testcontainers.GenericContainer(ctx, genericContainerReq); err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + // only the first node creates the cluster + if settings.currentNode == 0 { + for i := 1; i < len(settings.nodeNames); i++ { + // move to the next node + childNode, err := Run(ctx, req.Image, append(clusterOpts, withCurrentNode(i))...) + if err != nil { + // return the parent cluster node and the error, so the caller can clean up. + return c, fmt.Errorf("run cluster node: %w", err) + } + + c.childNodes = append(c.childNodes, childNode) + } + } + + return c, nil +} + +// configureCluster configures the cluster settings, ensuring that the cluster is properly configured with the necessary network and options, +// avoiding duplicate application of options to be passed to the successive nodes. +func configureCluster(ctx context.Context, settings *options, opts []testcontainers.ContainerCustomizer) ([]testcontainers.ContainerCustomizer, error) { + var clusterOpts []testcontainers.ContainerCustomizer + if len(settings.nodeNames) == 0 { + return clusterOpts, nil + } + + // pass cluster options to each node + etcdOpts := []Option{} + for _, opt := range opts { + // if the option is of type Option, it won't be applied to the settings + // this prevents the same option from being applied multiple times (e.g. updating the current node) + if apply, ok := opt.(Option); ok { + etcdOpts = append(etcdOpts, apply) + } else { + clusterOpts = append(clusterOpts, opt) + } + } + + if settings.clusterNetwork == nil { // the first time the network is created + newNetwork, err := tcnetwork.New(ctx) + if err != nil { + return clusterOpts, fmt.Errorf("new network: %w", err) + } + + // set the network for the first node + settings.clusterNetwork = newNetwork + + clusterOpts = append(clusterOpts, withClusterNetwork(newNetwork)) // save the network for the next nodes + } + + // we finally need to re-apply all the etcd-specific options + clusterOpts = append(clusterOpts, withClusterOptions(etcdOpts)) + + return clusterOpts, nil +} + +// configureCMD configures the etcd command line arguments, based on the settings provided, +// in order to create a cluster or a single-node instance. +func configureCMD(settings options) []string { + cmds := []string{"etcd"} + + if len(settings.nodeNames) == 0 { + cmds = append(cmds, "--name=default") + } else { + clusterCmds := []string{ + "--name=" + settings.nodeNames[settings.currentNode], + "--initial-advertise-peer-urls=" + scheme + "://" + settings.nodeNames[settings.currentNode] + ":" + peerPort, + "--advertise-client-urls=" + scheme + "://" + settings.nodeNames[settings.currentNode] + ":" + clientPort, + "--listen-peer-urls=" + scheme + "://0.0.0.0:" + peerPort, + "--listen-client-urls=" + scheme + "://0.0.0.0:" + clientPort, + "--initial-cluster-state=new", + } + + clusterStateValues := make([]string, len(settings.nodeNames)) + for i, node := range settings.nodeNames { + clusterStateValues[i] = node + "=" + scheme + "://" + node + ":" + peerPort + } + clusterCmds = append(clusterCmds, "--initial-cluster="+strings.Join(clusterStateValues, ",")) + + if settings.clusterToken != "" { + clusterCmds = append(clusterCmds, "--initial-cluster-token="+settings.clusterToken) + } + + cmds = append(cmds, clusterCmds...) + } + + if settings.mountDataDir { + cmds = append(cmds, "--data-dir="+dataDir) + } + + cmds = append(cmds, settings.additionalArgs...) + + return cmds +} + +// ClientEndpoint returns the client endpoint for the etcd container, and an error if any. +// For a cluster, it returns the client endpoint of the first node. +func (c *EtcdContainer) ClientEndpoint(ctx context.Context) (string, error) { + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + port, err := c.MappedPort(ctx, clientPort) + if err != nil { + return "", err + } + + return fmt.Sprintf("http://%s:%s", host, port.Port()), nil +} + +// ClientEndpoints returns the client endpoints for the etcd cluster. +func (c *EtcdContainer) ClientEndpoints(ctx context.Context) ([]string, error) { + endpoint, err := c.ClientEndpoint(ctx) + if err != nil { + return nil, err + } + + endpoints := []string{endpoint} + + for _, node := range c.childNodes { + endpoint, err := node.ClientEndpoint(ctx) + if err != nil { + return nil, err + } + endpoints = append(endpoints, endpoint) + } + + return endpoints, nil +} + +// PeerEndpoint returns the peer endpoint for the etcd container, and an error if any. +// For a cluster, it returns the peer endpoint of the first node. +func (c *EtcdContainer) PeerEndpoint(ctx context.Context) (string, error) { + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + port, err := c.MappedPort(ctx, peerPort) + if err != nil { + return "", err + } + + return fmt.Sprintf("http://%s:%s", host, port.Port()), nil +} + +// PeerEndpoints returns the peer endpoints for the etcd cluster. +func (c *EtcdContainer) PeerEndpoints(ctx context.Context) ([]string, error) { + endpoint, err := c.PeerEndpoint(ctx) + if err != nil { + return nil, err + } + + endpoints := []string{endpoint} + + for _, node := range c.childNodes { + endpoint, err := node.PeerEndpoint(ctx) + if err != nil { + return nil, err + } + endpoints = append(endpoints, endpoint) + } + + return endpoints, nil +} diff --git a/modules/etcd/etcd_test.go b/modules/etcd/etcd_test.go new file mode 100644 index 0000000000..046e277cac --- /dev/null +++ b/modules/etcd/etcd_test.go @@ -0,0 +1,60 @@ +package etcd_test + +import ( + "context" + "io" + "testing" + "time" + + "github.com/stretchr/testify/require" + clientv3 "go.etcd.io/etcd/client/v3" + + "github.com/testcontainers/testcontainers-go" + tcexec "github.com/testcontainers/testcontainers-go/exec" + "github.com/testcontainers/testcontainers-go/modules/etcd" +) + +func TestRun(t *testing.T) { + ctx := context.Background() + + ctr, err := etcd.Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + c, r, err := ctr.Exec(ctx, []string{"etcdctl", "member", "list"}, tcexec.Multiplexed()) + require.NoError(t, err) + require.Zero(t, c) + + output, err := io.ReadAll(r) + require.NoError(t, err) + require.Contains(t, string(output), "default") +} + +func TestRun_PutGet(t *testing.T) { + ctx := context.Background() + + ctr, err := etcd.Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14", etcd.WithNodes("etcd-1", "etcd-2", "etcd-3")) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + clientEndpoints, err := ctr.ClientEndpoints(ctx) + require.NoError(t, err) + + cli, err := clientv3.New(clientv3.Config{ + Endpoints: clientEndpoints, + DialTimeout: 5 * time.Second, + }) + require.NoError(t, err) + defer cli.Close() + + ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + defer cancel() + _, err = cli.Put(ctx, "sample_key", "sample_value") + require.NoError(t, err) + + resp, err := cli.Get(ctx, "sample_key") + require.NoError(t, err) + + require.Len(t, resp.Kvs, 1) + require.Equal(t, "sample_value", string(resp.Kvs[0].Value)) +} diff --git a/modules/etcd/etcd_unit_test.go b/modules/etcd/etcd_unit_test.go new file mode 100644 index 0000000000..d32b9519f7 --- /dev/null +++ b/modules/etcd/etcd_unit_test.go @@ -0,0 +1,113 @@ +package etcd + +import ( + "context" + "fmt" + "io" + "testing" + + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/errdefs" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + tcexec "github.com/testcontainers/testcontainers-go/exec" + tcnetwork "github.com/testcontainers/testcontainers-go/network" +) + +func TestRunCluster1Node(t *testing.T) { + ctx := context.Background() + + ctr, err := Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // the topology has only one node with no children + require.Empty(t, ctr.childNodes) + require.Equal(t, defaultClusterToken, ctr.opts.clusterToken) +} + +func TestRunClusterMultipleNodes(t *testing.T) { + t.Run("2-nodes", testCluster(t, "etcd-1", "etcd-2")) + t.Run("3-nodes", testCluster(t, "etcd-1", "etcd-2", "etcd-3")) +} + +func TestTerminate(t *testing.T) { + ctx := context.Background() + + ctr, err := Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14", WithNodes("etcd-1", "etcd-2", "etcd-3")) + require.NoError(t, err) + require.NoError(t, ctr.Terminate(ctx)) + + // verify that the network and the containers does no longer exist + + cli, err := testcontainers.NewDockerClientWithOpts(context.Background()) + require.NoError(t, err) + defer cli.Close() + + _, err = cli.ContainerInspect(context.Background(), ctr.GetContainerID()) + require.True(t, errdefs.IsNotFound(err)) + + for _, child := range ctr.childNodes { + _, err := cli.ContainerInspect(context.Background(), child.GetContainerID()) + require.True(t, errdefs.IsNotFound(err)) + } + + _, err = cli.NetworkInspect(context.Background(), ctr.opts.clusterNetwork.ID, network.InspectOptions{}) + require.True(t, errdefs.IsNotFound(err)) +} + +func TestTerminate_partiallyInitialised(t *testing.T) { + newNetwork, err := tcnetwork.New(context.Background()) + require.NoError(t, err) + + ctr := &EtcdContainer{ + opts: options{ + clusterNetwork: newNetwork, + }, + } + + require.NoError(t, ctr.Terminate(context.Background())) + + cli, err := testcontainers.NewDockerClientWithOpts(context.Background()) + require.NoError(t, err) + defer cli.Close() + + _, err = cli.NetworkInspect(context.Background(), ctr.opts.clusterNetwork.ID, network.InspectOptions{}) + require.True(t, errdefs.IsNotFound(err)) +} + +// testCluster is a helper function to test the creation of an etcd cluster with the specified nodes. +func testCluster(t *testing.T, node1 string, node2 string, nodes ...string) func(t *testing.T) { + t.Helper() + + return func(tt *testing.T) { + const clusterToken string = "My-cluster-t0k3n" + + ctx := context.Background() + + ctr, err := Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14", WithNodes(node1, node2, nodes...), WithClusterToken(clusterToken)) + testcontainers.CleanupContainer(t, ctr) + require.NoError(tt, err) + + require.Equal(tt, clusterToken, ctr.opts.clusterToken) + + // the topology has one parent node, one child node and optionally more child nodes + // depending on the number of nodes specified + require.Len(tt, ctr.childNodes, 1+len(nodes)) + + for i, node := range ctr.childNodes { + require.Empty(t, node.childNodes) // child nodes has no children + + c, r, err := node.Exec(ctx, []string{"etcdctl", "member", "list"}, tcexec.Multiplexed()) + require.NoError(tt, err) + + output, err := io.ReadAll(r) + require.NoError(t, err) + require.Contains(t, string(output), fmt.Sprintf("etcd-%d", i+1)) + + require.Zero(tt, c) + require.Equal(tt, clusterToken, node.opts.clusterToken) + } + } +} diff --git a/modules/etcd/examples_test.go b/modules/etcd/examples_test.go new file mode 100644 index 0000000000..950d5ecd05 --- /dev/null +++ b/modules/etcd/examples_test.go @@ -0,0 +1,97 @@ +package etcd_test + +import ( + "context" + "fmt" + "log" + "time" + + clientv3 "go.etcd.io/etcd/client/v3" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/etcd" +) + +func ExampleRun() { + // runetcdContainer { + ctx := context.Background() + + etcdContainer, err := etcd.Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14") + defer func() { + if err := testcontainers.TerminateContainer(etcdContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := etcdContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} + +func ExampleRun_cluster() { + ctx := context.Background() + + ctr, err := etcd.Run(ctx, "gcr.io/etcd-development/etcd:v3.5.14", etcd.WithNodes("etcd-1", "etcd-2", "etcd-3")) + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + defer func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + + clientEndpoints, err := ctr.ClientEndpoints(ctx) + if err != nil { + log.Printf("failed to get client endpoints: %s", err) + return + } + + // we have 3 nodes, 1 cluster node and 2 child nodes + fmt.Println(len(clientEndpoints)) + + cli, err := clientv3.New(clientv3.Config{ + Endpoints: clientEndpoints, + DialTimeout: 5 * time.Second, + }) + if err != nil { + log.Printf("failed to create etcd client: %s", err) + return + } + defer cli.Close() + + ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + defer cancel() + _, err = cli.Put(ctx, "sample_key", "sample_value") + if err != nil { + log.Printf("failed to put key: %s", err) + return + } + + resp, err := cli.Get(ctx, "sample_key") + if err != nil { + log.Printf("failed to get key: %s", err) + return + } + + fmt.Println(len(resp.Kvs)) + fmt.Println(string(resp.Kvs[0].Value)) + + // Output: + // 3 + // 1 + // sample_value +} diff --git a/modules/etcd/go.mod b/modules/etcd/go.mod new file mode 100644 index 0000000000..b0946686cc --- /dev/null +++ b/modules/etcd/go.mod @@ -0,0 +1,75 @@ +module github.com/testcontainers/testcontainers-go/modules/etcd + +go 1.22 + +require ( + github.com/docker/docker v27.1.1+incompatible + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 + go.etcd.io/etcd/client/v3 v3.5.16 +) + +require ( + dario.cat/mergo v1.0.0 // 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/containerd/containerd v1.7.18 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.etcd.io/etcd/api/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/etcd/go.sum b/modules/etcd/go.sum new file mode 100644 index 0000000000..505f9ae432 --- /dev/null +++ b/modules/etcd/go.sum @@ -0,0 +1,216 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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 v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= +go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= +go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= +go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= +go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= +go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/etcd/options.go b/modules/etcd/options.go new file mode 100644 index 0000000000..1359e4a3b4 --- /dev/null +++ b/modules/etcd/options.go @@ -0,0 +1,107 @@ +package etcd + +import ( + "context" + "fmt" + + "github.com/testcontainers/testcontainers-go" + tcexec "github.com/testcontainers/testcontainers-go/exec" +) + +type options struct { + currentNode int + clusterNetwork *testcontainers.DockerNetwork + nodeNames []string + clusterToken string + additionalArgs []string + mountDataDir bool // flag needed to avoid extra calculations with the lifecycle hooks + containerRequest *testcontainers.ContainerRequest +} + +func defaultOptions(req *testcontainers.ContainerRequest) options { + return options{ + clusterToken: defaultClusterToken, + containerRequest: req, + } +} + +// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (Option)(nil) + +// Option is an option for the Etcd container. +type Option func(*options) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { + // NOOP to satisfy interface. + return nil +} + +// WithAdditionalArgs is an option to pass additional arguments to the etcd container. +// They will be appended last to the command line. +func WithAdditionalArgs(args ...string) Option { + return func(o *options) { + o.additionalArgs = args + } +} + +// WithDataDir is an option to mount the data directory, which is located at /data.etcd. +// The option will add a lifecycle hook to the container to change the permissions of the data directory. +func WithDataDir() Option { + return func(o *options) { + // Avoid extra calculations with the lifecycle hooks + o.mountDataDir = true + + o.containerRequest.LifecycleHooks = append(o.containerRequest.LifecycleHooks, testcontainers.ContainerLifecycleHooks{ + PostStarts: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { + _, _, err := c.Exec(ctx, []string{"chmod", "o+rwx", "-R", dataDir}, tcexec.Multiplexed()) + if err != nil { + return fmt.Errorf("chmod etcd data dir: %w", err) + } + + return nil + }, + }, + }) + } +} + +// WithNodes is an option to set the nodes of the etcd cluster. +// It should be used to create a cluster with more than one node. +func WithNodes(node1 string, node2 string, nodes ...string) Option { + return func(o *options) { + o.nodeNames = append([]string{node1, node2}, nodes...) + } +} + +// withCurrentNode is an option to set the current node index. +// It's an internal option and should not be used by the user. +func withCurrentNode(i int) Option { + return func(o *options) { + o.currentNode = i + } +} + +// withClusterNetwork is an option to set the cluster network. +// It's an internal option and should not be used by the user. +func withClusterNetwork(n *testcontainers.DockerNetwork) Option { + return func(o *options) { + o.clusterNetwork = n + } +} + +// WithClusterToken is an option to set the cluster token. +func WithClusterToken(token string) Option { + return func(o *options) { + o.clusterToken = token + } +} + +func withClusterOptions(opts []Option) Option { + return func(o *options) { + for _, opt := range opts { + opt(o) + } + } +} diff --git a/modules/gcloud/bigquery.go b/modules/gcloud/bigquery.go index 7a28313cba..60b206d2ef 100644 --- a/modules/gcloud/bigquery.go +++ b/modules/gcloud/bigquery.go @@ -8,12 +8,18 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +// Deprecated: use RunBigQuery instead // RunBigQueryContainer creates an instance of the GCloud container type for BigQuery. -// The URI will always use http:// as the protocol. func RunBigQueryContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunBigQuery(ctx, "ghcr.io/goccy/bigquery-emulator:0.6.1", opts...) +} + +// RunBigQuery creates an instance of the GCloud container type for BigQuery. +// The URI will always use http:// as the protocol. +func RunBigQuery(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "ghcr.io/goccy/bigquery-emulator:0.6.1", + Image: img, ExposedPorts: []string{"9050/tcp", "9060/tcp"}, WaitingFor: wait.ForHTTP("/discovery/v1/apis/bigquery/v2/rest").WithPort("9050/tcp").WithStartupTimeout(time.Second * 5), }, @@ -27,18 +33,5 @@ func RunBigQueryContainer(ctx context.Context, opts ...testcontainers.ContainerC req.Cmd = append(req.Cmd, "--project", settings.ProjectID) - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - bigqueryContainer, err := newGCloudContainer(ctx, 9050, container, settings) - if err != nil { - return nil, err - } - - // always prepend http:// to the URI - bigqueryContainer.URI = "http://" + bigqueryContainer.URI - - return bigqueryContainer, nil + return newGCloudContainer(ctx, req, 9050, settings, "http://") } diff --git a/modules/gcloud/bigquery_test.go b/modules/gcloud/bigquery_test.go index ad71bd0a2b..f03a00f830 100644 --- a/modules/gcloud/bigquery_test.go +++ b/modules/gcloud/bigquery_test.go @@ -23,21 +23,20 @@ func ExampleRunBigQueryContainer() { // runBigQueryContainer { ctx := context.Background() - bigQueryContainer, err := gcloud.RunBigQueryContainer( + bigQueryContainer, err := gcloud.RunBigQuery( ctx, - testcontainers.WithImage("ghcr.io/goccy/bigquery-emulator:0.6.1"), + "ghcr.io/goccy/bigquery-emulator:0.6.1", gcloud.WithProjectID("bigquery-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := bigQueryContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(bigQueryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // bigQueryClient { @@ -52,7 +51,8 @@ func ExampleRunBigQueryContainer() { client, err := bigquery.NewClient(ctx, projectID, opts...) if err != nil { - log.Fatalf("failed to create bigquery client: %v", err) // nolint:gocritic + log.Printf("failed to create bigquery client: %v", err) + return } defer client.Close() // } @@ -60,13 +60,15 @@ func ExampleRunBigQueryContainer() { createFnQuery := client.Query("CREATE FUNCTION testr(arr ARRAY>) AS ((SELECT SUM(IF(elem.name = \"foo\",elem.val,null)) FROM UNNEST(arr) AS elem))") _, err = createFnQuery.Read(ctx) if err != nil { - log.Fatalf("failed to create function: %v", err) + log.Printf("failed to create function: %v", err) + return } selectQuery := client.Query("SELECT testr([STRUCT(\"foo\", 10), STRUCT(\"bar\", 40), STRUCT(\"foo\", 20)])") it, err := selectQuery.Read(ctx) if err != nil { - log.Fatalf("failed to read query: %v", err) + log.Printf("failed to read query: %v", err) + return } var val []bigquery.Value @@ -76,7 +78,8 @@ func ExampleRunBigQueryContainer() { break } if err != nil { - log.Fatalf("failed to iterate: %v", err) + log.Printf("failed to iterate: %v", err) + return } } diff --git a/modules/gcloud/bigtable.go b/modules/gcloud/bigtable.go index 8294ad2678..134f14d1d6 100644 --- a/modules/gcloud/bigtable.go +++ b/modules/gcloud/bigtable.go @@ -2,17 +2,22 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// RunBigTableContainer creates an instance of the GCloud container type for BigTable +// Deprecated: use RunBigTable instead +// RunBigTableContainer creates an instance of the GCloud container type for BigTable. func RunBigTableContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunBigQuery(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...) +} + +// RunBigTable creates an instance of the GCloud container type for BigTable. +func RunBigTable(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", + Image: img, ExposedPorts: []string{"9000/tcp"}, WaitingFor: wait.ForLog("running"), }, @@ -27,13 +32,8 @@ func RunBigTableContainer(ctx context.Context, opts ...testcontainers.ContainerC req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators bigtable start --host-port 0.0.0.0:9000 " + fmt.Sprintf("--project=%s", settings.ProjectID), - } - - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err + "gcloud beta emulators bigtable start --host-port 0.0.0.0:9000 --project=" + settings.ProjectID, } - return newGCloudContainer(ctx, 9000, container, settings) + return newGCloudContainer(ctx, req, 9000, settings, "") } diff --git a/modules/gcloud/bigtable_test.go b/modules/gcloud/bigtable_test.go index 0504d68fc0..553581bcc4 100644 --- a/modules/gcloud/bigtable_test.go +++ b/modules/gcloud/bigtable_test.go @@ -18,21 +18,20 @@ func ExampleRunBigTableContainer() { // runBigTableContainer { ctx := context.Background() - bigTableContainer, err := gcloud.RunBigTableContainer( + bigTableContainer, err := gcloud.RunBigTable( ctx, - testcontainers.WithImage("gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators"), + "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("bigtable-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := bigTableContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(bigTableContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // bigTableAdminClient { @@ -50,24 +49,28 @@ func ExampleRunBigTableContainer() { } adminClient, err := bigtable.NewAdminClient(ctx, projectId, instanceId, options...) if err != nil { - log.Fatalf("failed to create admin client: %v", err) // nolint:gocritic + log.Printf("failed to create admin client: %v", err) + return } defer adminClient.Close() // } err = adminClient.CreateTable(ctx, tableName) if err != nil { - log.Fatalf("failed to create table: %v", err) + log.Printf("failed to create table: %v", err) + return } err = adminClient.CreateColumnFamily(ctx, tableName, "name") if err != nil { - log.Fatalf("failed to create column family: %v", err) + log.Printf("failed to create column family: %v", err) + return } // bigTableClient { client, err := bigtable.NewClient(ctx, projectId, instanceId, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } @@ -78,12 +81,14 @@ func ExampleRunBigTableContainer() { mut.Set("name", "firstName", bigtable.Now(), []byte("Gopher")) err = tbl.Apply(ctx, "1", mut) if err != nil { - log.Fatalf("failed to apply mutation: %v", err) + log.Printf("failed to apply mutation: %v", err) + return } row, err := tbl.ReadRow(ctx, "1", bigtable.RowFilter(bigtable.FamilyFilter("name"))) if err != nil { - log.Fatalf("failed to read row: %v", err) + log.Printf("failed to read row: %v", err) + return } fmt.Println(string(row["name"][0].Value)) diff --git a/modules/gcloud/datastore.go b/modules/gcloud/datastore.go index 72b487f12b..caf53e9879 100644 --- a/modules/gcloud/datastore.go +++ b/modules/gcloud/datastore.go @@ -2,17 +2,22 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// RunDatastoreContainer creates an instance of the GCloud container type for Datastore +// Deprecated: use RunDatastore instead +// RunDatastoreContainer creates an instance of the GCloud container type for Datastore. func RunDatastoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunDatastore(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...) +} + +// RunDatastore creates an instance of the GCloud container type for Datastore. +func RunDatastore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", + Image: img, ExposedPorts: []string{"8081/tcp"}, WaitingFor: wait.ForHTTP("/").WithPort("8081/tcp"), }, @@ -27,13 +32,8 @@ func RunDatastoreContainer(ctx context.Context, opts ...testcontainers.Container req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators datastore start --host-port 0.0.0.0:8081 " + fmt.Sprintf("--project=%s", settings.ProjectID), - } - - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err + "gcloud beta emulators datastore start --host-port 0.0.0.0:8081 --project=" + settings.ProjectID, } - return newGCloudContainer(ctx, 8081, container, settings) + return newGCloudContainer(ctx, req, 8081, settings, "") } diff --git a/modules/gcloud/datastore_test.go b/modules/gcloud/datastore_test.go index e9db5116d3..fa056bbf63 100644 --- a/modules/gcloud/datastore_test.go +++ b/modules/gcloud/datastore_test.go @@ -18,21 +18,20 @@ func ExampleRunDatastoreContainer() { // runDatastoreContainer { ctx := context.Background() - datastoreContainer, err := gcloud.RunDatastoreContainer( + datastoreContainer, err := gcloud.RunDatastore( ctx, - testcontainers.WithImage("gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators"), + "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("datastore-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := datastoreContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(datastoreContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // datastoreClient { @@ -46,7 +45,8 @@ func ExampleRunDatastoreContainer() { dsClient, err := datastore.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) // nolint:gocritic + log.Printf("failed to create client: %v", err) + return } defer dsClient.Close() // } @@ -61,13 +61,15 @@ func ExampleRunDatastoreContainer() { } _, err = dsClient.Put(ctx, k, &data) if err != nil { - log.Fatalf("failed to put data: %v", err) + log.Printf("failed to put data: %v", err) + return } saved := Task{} err = dsClient.Get(ctx, k, &saved) if err != nil { - log.Fatalf("failed to get data: %v", err) + log.Printf("failed to get data: %v", err) + return } fmt.Println(saved.Description) diff --git a/modules/gcloud/firestore.go b/modules/gcloud/firestore.go index ee998a55b3..297b47f80c 100644 --- a/modules/gcloud/firestore.go +++ b/modules/gcloud/firestore.go @@ -2,17 +2,22 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// RunFirestoreContainer creates an instance of the GCloud container type for Firestore +// Deprecated: use RunFirestore instead +// RunFirestoreContainer creates an instance of the GCloud container type for Firestore. func RunFirestoreContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunFirestore(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...) +} + +// RunFirestore creates an instance of the GCloud container type for Firestore. +func RunFirestore(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", + Image: img, ExposedPorts: []string{"8080/tcp"}, WaitingFor: wait.ForLog("running"), }, @@ -27,13 +32,8 @@ func RunFirestoreContainer(ctx context.Context, opts ...testcontainers.Container req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators firestore start --host-port 0.0.0.0:8080 " + fmt.Sprintf("--project=%s", settings.ProjectID), - } - - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err + "gcloud beta emulators firestore start --host-port 0.0.0.0:8080 --project=" + settings.ProjectID, } - return newGCloudContainer(ctx, 8080, container, settings) + return newGCloudContainer(ctx, req, 8080, settings, "") } diff --git a/modules/gcloud/firestore_test.go b/modules/gcloud/firestore_test.go index 3e1c6f7977..83ccd0464c 100644 --- a/modules/gcloud/firestore_test.go +++ b/modules/gcloud/firestore_test.go @@ -28,35 +28,36 @@ func ExampleRunFirestoreContainer() { // runFirestoreContainer { ctx := context.Background() - firestoreContainer, err := gcloud.RunFirestoreContainer( + firestoreContainer, err := gcloud.RunFirestore( ctx, - testcontainers.WithImage("gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators"), + "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("firestore-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := firestoreContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(firestoreContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // firestoreClient { projectID := firestoreContainer.Settings.ProjectID - conn, err := grpc.Dial(firestoreContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{})) + conn, err := grpc.NewClient(firestoreContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{})) if err != nil { - log.Fatalf("failed to dial: %v", err) // nolint:gocritic + log.Printf("failed to dial: %v", err) + return } options := []option.ClientOption{option.WithGRPCConn(conn)} client, err := firestore.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } @@ -75,17 +76,20 @@ func ExampleRunFirestoreContainer() { } _, err = docRef.Create(ctx, data) if err != nil { - log.Fatalf("failed to create document: %v", err) + log.Printf("failed to create document: %v", err) + return } docsnap, err := docRef.Get(ctx) if err != nil { - log.Fatalf("failed to get document: %v", err) + log.Printf("failed to get document: %v", err) + return } var saved Person if err := docsnap.DataTo(&saved); err != nil { - log.Fatalf("failed to convert data: %v", err) + log.Printf("failed to convert data: %v", err) + return } fmt.Println(saved.Firstname, saved.Lastname) diff --git a/modules/gcloud/gcloud.go b/modules/gcloud/gcloud.go index 3814c9ab72..d88194ffc2 100644 --- a/modules/gcloud/gcloud.go +++ b/modules/gcloud/gcloud.go @@ -18,26 +18,29 @@ type GCloudContainer struct { } // newGCloudContainer creates a new GCloud container, obtaining the URL to access the container from the specified port. -func newGCloudContainer(ctx context.Context, port int, c testcontainers.Container, settings options) (*GCloudContainer, error) { +func newGCloudContainer(ctx context.Context, req testcontainers.GenericContainerRequest, port int, settings options, urlPrefix string) (*GCloudContainer, error) { + container, err := testcontainers.GenericContainer(ctx, req) + var c *GCloudContainer + if container != nil { + c = &GCloudContainer{Container: container, Settings: settings} + } + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + mappedPort, err := c.MappedPort(ctx, nat.Port(fmt.Sprintf("%d/tcp", port))) if err != nil { - return nil, err + return c, fmt.Errorf("mapped port: %w", err) } hostIP, err := c.Host(ctx) if err != nil { - return nil, err + return c, fmt.Errorf("host: %w", err) } - uri := fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) - - gCloudContainer := &GCloudContainer{ - Container: c, - Settings: settings, - URI: uri, - } + c.URI = urlPrefix + hostIP + ":" + mappedPort.Port() - return gCloudContainer, nil + return c, nil } type options struct { diff --git a/modules/gcloud/go.mod b/modules/gcloud/go.mod index 1bb0e472d9..5c05fce458 100644 --- a/modules/gcloud/go.mod +++ b/modules/gcloud/go.mod @@ -1,6 +1,6 @@ module github.com/testcontainers/testcontainers-go/modules/gcloud -go 1.21 +go 1.22 require ( cloud.google.com/go/bigquery v1.59.1 @@ -10,32 +10,32 @@ require ( cloud.google.com/go/pubsub v1.36.2 cloud.google.com/go/spanner v1.57.0 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.30.0 - google.golang.org/api v0.168.0 - google.golang.org/grpc v1.62.0 + github.com/testcontainers/testcontainers-go v0.34.0 + google.golang.org/api v0.169.0 + google.golang.org/grpc v1.64.1 ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/longrunning v0.5.5 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/apache/arrow/go/v14 v14.0.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/envoyproxy/go-control-plane v0.12.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect @@ -53,10 +53,11 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -68,11 +69,12 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -83,21 +85,22 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/gcloud/go.sum b/modules/gcloud/go.sum index 90c138a945..f8036b37f9 100644 --- a/modules/gcloud/go.sum +++ b/modules/gcloud/go.sum @@ -1,12 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4= cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc= cloud.google.com/go/bigtable v1.21.0 h1:BFN4jhkA9ULYYV2Ug7AeOtetVLnN2jKuIq5TcRc5C38= cloud.google.com/go/bigtable v1.21.0/go.mod h1:V0sYNRtk0dgAKjyRr/MyBpHpSXqh+9P39euf820EZ74= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw= @@ -25,8 +25,8 @@ cloud.google.com/go/pubsub v1.36.2 h1:nAUD4aiWHZFYyINhRag1qOnHUk0/7QiWEa04XWnqAC cloud.google.com/go/pubsub v1.36.2/go.mod h1:mHCFLNG8abCrPzhuOnpBcr9DUy+l3/LWWn0qoJdbh1w= cloud.google.com/go/spanner v1.57.0 h1:fJq+ZfQUDHE+cy1li0bJA8+sy2oiSGhuGqN5nqVaZdU= cloud.google.com/go/spanner v1.57.0/go.mod h1:aXQ5QDdhPRIqVhYmnkAdwPYvj/DRN0FguclhEWw+jOo= -cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4= -cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= @@ -34,10 +34,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -49,26 +47,25 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -145,14 +142,20 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +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/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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -181,6 +184,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -231,8 +236,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMey go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= @@ -241,8 +246,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -252,8 +257,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -265,19 +270,19 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -294,18 +299,18 @@ golang.org/x/sys v0.5.0/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -317,8 +322,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn 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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= @@ -327,8 +332,8 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY= -google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -338,17 +343,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 h1:Xs9lu+tLXxLIfuci70nG4cpwaRC+mRQPUL7LoIeDJC4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -363,11 +368,13 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= diff --git a/modules/gcloud/pubsub.go b/modules/gcloud/pubsub.go index bf83f3a2f5..d57ea35c16 100644 --- a/modules/gcloud/pubsub.go +++ b/modules/gcloud/pubsub.go @@ -2,17 +2,22 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// RunPubsubContainer creates an instance of the GCloud container type for Pubsub +// Deprecated: use RunPubsub instead +// RunPubsubContainer creates an instance of the GCloud container type for Pubsub. func RunPubsubContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunPubsub(ctx, "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", opts...) +} + +// RunPubsub creates an instance of the GCloud container type for Pubsub. +func RunPubsub(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", + Image: img, ExposedPorts: []string{"8085/tcp"}, WaitingFor: wait.ForLog("started"), }, @@ -27,13 +32,8 @@ func RunPubsubContainer(ctx context.Context, opts ...testcontainers.ContainerCus req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085 " + fmt.Sprintf("--project=%s", settings.ProjectID), - } - - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err + "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085 --project=" + settings.ProjectID, } - return newGCloudContainer(ctx, 8085, container, settings) + return newGCloudContainer(ctx, req, 8085, settings, "") } diff --git a/modules/gcloud/pubsub_test.go b/modules/gcloud/pubsub_test.go index 5d46424f42..151df3a546 100644 --- a/modules/gcloud/pubsub_test.go +++ b/modules/gcloud/pubsub_test.go @@ -18,52 +18,56 @@ func ExampleRunPubsubContainer() { // runPubsubContainer { ctx := context.Background() - pubsubContainer, err := gcloud.RunPubsubContainer( + pubsubContainer, err := gcloud.RunPubsub( ctx, - testcontainers.WithImage("gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators"), + "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("pubsub-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := pubsubContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(pubsubContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // pubsubClient { projectID := pubsubContainer.Settings.ProjectID - conn, err := grpc.Dial(pubsubContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient(pubsubContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Fatalf("failed to dial: %v", err) // nolint:gocritic + log.Printf("failed to dial: %v", err) + return } options := []option.ClientOption{option.WithGRPCConn(conn)} client, err := pubsub.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } topic, err := client.CreateTopic(ctx, "greetings") if err != nil { - log.Fatalf("failed to create topic: %v", err) + log.Printf("failed to create topic: %v", err) + return } subscription, err := client.CreateSubscription(ctx, "subscription", pubsub.SubscriptionConfig{Topic: topic}) if err != nil { - log.Fatalf("failed to create subscription: %v", err) + log.Printf("failed to create subscription: %v", err) + return } result := topic.Publish(ctx, &pubsub.Message{Data: []byte("Hello World")}) _, err = result.Get(ctx) if err != nil { - log.Fatalf("failed to publish message: %v", err) + log.Printf("failed to publish message: %v", err) + return } var data []byte @@ -74,7 +78,8 @@ func ExampleRunPubsubContainer() { defer cancel() }) if err != nil { - log.Fatalf("failed to receive message: %v", err) + log.Printf("failed to receive message: %v", err) + return } fmt.Println(string(data)) diff --git a/modules/gcloud/spanner.go b/modules/gcloud/spanner.go index eb7d10ea8c..8b306db4ce 100644 --- a/modules/gcloud/spanner.go +++ b/modules/gcloud/spanner.go @@ -7,11 +7,17 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -// RunSpannerContainer creates an instance of the GCloud container type for Spanner +// Deprecated: use RunSpanner instead +// RunSpannerContainer creates an instance of the GCloud container type for Spanner. func RunSpannerContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { + return RunSpanner(ctx, "gcr.io/cloud-spanner-emulator/emulator:1.4.0", opts...) +} + +// RunSpanner creates an instance of the GCloud container type for Spanner. +func RunSpanner(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GCloudContainer, error) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "gcr.io/cloud-spanner-emulator/emulator:1.4.0", + Image: img, ExposedPorts: []string{"9010/tcp"}, WaitingFor: wait.ForLog("Cloud Spanner emulator running"), }, @@ -23,10 +29,5 @@ func RunSpannerContainer(ctx context.Context, opts ...testcontainers.ContainerCu return nil, err } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 9010, container, settings) + return newGCloudContainer(ctx, req, 9010, settings, "") } diff --git a/modules/gcloud/spanner_test.go b/modules/gcloud/spanner_test.go index e50777142a..02a1be1c78 100644 --- a/modules/gcloud/spanner_test.go +++ b/modules/gcloud/spanner_test.go @@ -23,21 +23,20 @@ func ExampleRunSpannerContainer() { // runSpannerContainer { ctx := context.Background() - spannerContainer, err := gcloud.RunSpannerContainer( + spannerContainer, err := gcloud.RunSpanner( ctx, - testcontainers.WithImage("gcr.io/cloud-spanner-emulator/emulator:1.4.0"), + "gcr.io/cloud-spanner-emulator/emulator:1.4.0", gcloud.WithProjectID("spanner-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := spannerContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(spannerContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // spannerAdminClient { @@ -57,31 +56,35 @@ func ExampleRunSpannerContainer() { instanceAdmin, err := instance.NewInstanceAdminClient(ctx, options...) if err != nil { - log.Fatalf("failed to create instance admin client: %v", err) // nolint:gocritic + log.Printf("failed to create instance admin client: %v", err) + return } defer instanceAdmin.Close() // } instanceOp, err := instanceAdmin.CreateInstance(ctx, &instancepb.CreateInstanceRequest{ - Parent: fmt.Sprintf("projects/%s", projectId), + Parent: "projects/" + projectId, InstanceId: instanceId, Instance: &instancepb.Instance{ DisplayName: instanceId, }, }) if err != nil { - log.Fatalf("failed to create instance: %v", err) + log.Printf("failed to create instance: %v", err) + return } _, err = instanceOp.Wait(ctx) if err != nil { - log.Fatalf("failed to wait for instance creation: %v", err) + log.Printf("failed to wait for instance creation: %v", err) + return } // spannerDBAdminClient { c, err := database.NewDatabaseAdminClient(ctx, options...) if err != nil { - log.Fatalf("failed to create admin client: %v", err) + log.Printf("failed to create admin client: %v", err) + return } defer c.Close() // } @@ -94,17 +97,20 @@ func ExampleRunSpannerContainer() { }, }) if err != nil { - log.Fatalf("failed to create database: %v", err) + log.Printf("failed to create database: %v", err) + return } _, err = databaseOp.Wait(ctx) if err != nil { - log.Fatalf("failed to wait for database creation: %v", err) + log.Printf("failed to wait for database creation: %v", err) + return } db := fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseName) client, err := spanner.NewClient(ctx, db, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() @@ -114,18 +120,21 @@ func ExampleRunSpannerContainer() { []interface{}{"Go", "Gopher"}), }) if err != nil { - log.Fatalf("failed to apply mutation: %v", err) + log.Printf("failed to apply mutation: %v", err) + return } row, err := client.Single().ReadRow(ctx, "Languages", spanner.Key{"Go"}, []string{"mascot"}) if err != nil { - log.Fatalf("failed to read row: %v", err) + log.Printf("failed to read row: %v", err) + return } var mascot string err = row.ColumnByName("Mascot", &mascot) if err != nil { - log.Fatalf("failed to read column: %v", err) + log.Printf("failed to read column: %v", err) + return } fmt.Println(mascot) diff --git a/modules/grafana-lgtm/Makefile b/modules/grafana-lgtm/Makefile new file mode 100644 index 0000000000..a49c12c337 --- /dev/null +++ b/modules/grafana-lgtm/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-grafanalgtm diff --git a/modules/grafana-lgtm/examples_test.go b/modules/grafana-lgtm/examples_test.go new file mode 100644 index 0000000000..a3602b7685 --- /dev/null +++ b/modules/grafana-lgtm/examples_test.go @@ -0,0 +1,240 @@ +package grafanalgtm_test + +import ( + "context" + "errors" + "fmt" + "log" + "log/slog" + "math/rand" + "time" + + "go.opentelemetry.io/contrib/bridges/otelslog" + "go.opentelemetry.io/contrib/instrumentation/runtime" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/log/global" + metricsapi "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + otellog "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/trace" + "golang.org/x/sync/errgroup" + + "github.com/testcontainers/testcontainers-go" + grafanalgtm "github.com/testcontainers/testcontainers-go/modules/grafana-lgtm" +) + +func ExampleRun() { + // runGrafanaLGTMContainer { + ctx := context.Background() + + grafanaLgtmContainer, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0") + defer func() { + if err := testcontainers.TerminateContainer(grafanaLgtmContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := grafanaLgtmContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} + +func ExampleRun_otelCollector() { + ctx := context.Background() + + ctr, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0", grafanalgtm.WithAdminCredentials("admin", "123456789")) + defer func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start Grafana LGTM container: %s", err) + return + } + + // Set up OpenTelemetry. + otelShutdown, err := setupOTelSDK(ctx, ctr) + if err != nil { + log.Printf("failed to set up OpenTelemetry: %s", err) + return + } + // Handle shutdown properly so nothing leaks. + defer func() { + if err := otelShutdown(context.Background()); err != nil { + log.Printf("failed to shutdown OpenTelemetry: %s", err) + } + }() + + // roll dice 10000 times, concurrently + max := 10_000 + var wg errgroup.Group + for i := 0; i < max; i++ { + wg.Go(func() error { + return rolldice(ctx) + }) + } + + if err = wg.Wait(); err != nil { + log.Printf("failed to roll dice: %s", err) + return + } + + // Output: +} + +// setupOTelSDK bootstraps the OpenTelemetry pipeline. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (shutdown func(context.Context) error, err error) { + var shutdownFuncs []func(context.Context) error + + // shutdown calls cleanup functions registered via shutdownFuncs. + // The errors from the calls are joined. + // Each registered cleanup will be invoked once. + shutdown = func(ctx context.Context) error { + var errs []error + for _, fn := range shutdownFuncs { + if err := fn(ctx); err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) + } + + // Ensure that the OpenTelemetry SDK is properly shutdown. + defer func() { + if err != nil { + err = errors.Join(shutdown(ctx)) + } + }() + + prop := propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) + otel.SetTextMapPropagator(prop) + + otlpHttpEndpoint := ctr.MustOtlpHttpEndpoint(ctx) + + traceExporter, err := otlptrace.New(ctx, + otlptracehttp.NewClient( + // adding schema to avoid this error: + // 2024/07/19 13:16:30 internal_logging.go:50: "msg"="otlptrace: parse endpoint url" "error"="parse \"127.0.0.1:33007\": first path segment in URL cannot contain colon" "url"="127.0.0.1:33007" + // it does not happen with the logs and metrics exporters + otlptracehttp.WithEndpointURL("http://"+otlpHttpEndpoint), + otlptracehttp.WithInsecure(), + ), + ) + if err != nil { + return nil, fmt.Errorf("new trace exporter: %w", err) + } + + tracerProvider := trace.NewTracerProvider(trace.WithBatcher(traceExporter)) + if err != nil { + return nil, fmt.Errorf("new trace provider: %w", err) + } + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + + metricExporter, err := otlpmetrichttp.New(ctx, + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.WithEndpoint(otlpHttpEndpoint), + ) + if err != nil { + return nil, fmt.Errorf("new metric exporter: %w", err) + } + + // The exporter embeds a default OpenTelemetry Reader and + // implements prometheus.Collector, allowing it to be used as + // both a Reader and Collector. + prometheusExporter, err := prometheus.New() + if err != nil { + return nil, fmt.Errorf("new prometheus exporter: %w", err) + } + + meterProvider := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(metricExporter)), + metric.WithReader(prometheusExporter), + ) + if err != nil { + return nil, fmt.Errorf("new meter provider: %w", err) + } + + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) + otel.SetMeterProvider(meterProvider) + + logExporter, err := otlploghttp.New(ctx, + otlploghttp.WithInsecure(), + otlploghttp.WithEndpoint(otlpHttpEndpoint), + ) + if err != nil { + return nil, fmt.Errorf("new log exporter: %w", err) + } + + loggerProvider := otellog.NewLoggerProvider(otellog.WithProcessor(otellog.NewBatchProcessor(logExporter))) + if err != nil { + return nil, fmt.Errorf("new logger provider: %w", err) + } + + shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) + global.SetLoggerProvider(loggerProvider) + + if err = runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)); err != nil { + return nil, fmt.Errorf("start runtime instrumentation: %w", err) + } + + return shutdown, nil +} + +// rollDiceApp { +const schemaName = "https://github.com/grafana/docker-otel-lgtm" + +var ( + tracer = otel.Tracer(schemaName) + logger = otelslog.NewLogger(schemaName) + meter = otel.Meter(schemaName) +) + +func rolldice(ctx context.Context) error { + ctx, span := tracer.Start(ctx, "roll") + defer span.End() + + // 20-sided dice + roll := 1 + rand.Intn(20) + logger.InfoContext(ctx, fmt.Sprintf("Rolled a dice: %d\n", roll), slog.Int("result", roll)) + + opt := metricsapi.WithAttributes( + attribute.Key("sides").Int(roll), + ) + + // This is the equivalent of prometheus.NewCounterVec + counter, err := meter.Int64Counter("rolldice-counter", metricsapi.WithDescription("a 20-sided dice")) + if err != nil { + return fmt.Errorf("roll dice: %w", err) + } + counter.Add(ctx, int64(roll), opt) + + return nil +} + +// } diff --git a/modules/grafana-lgtm/go.mod b/modules/grafana-lgtm/go.mod new file mode 100644 index 0000000000..cab19dc60a --- /dev/null +++ b/modules/grafana-lgtm/go.mod @@ -0,0 +1,86 @@ +module github.com/testcontainers/testcontainers-go/modules/grafana-lgtm + +go 1.22 + +require ( + github.com/docker/go-connections v0.5.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 + go.opentelemetry.io/contrib/bridges/otelslog v0.3.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 + go.opentelemetry.io/otel/exporters/prometheus v0.50.0 + go.opentelemetry.io/otel/log v0.4.0 + go.opentelemetry.io/otel/metric v1.28.0 + go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/sdk/log v0.4.0 + go.opentelemetry.io/otel/sdk/metric v1.28.0 + golang.org/x/sync v0.8.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // 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.20.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + 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 + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // 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/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.64.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/grafana-lgtm/go.sum b/modules/grafana-lgtm/go.sum new file mode 100644 index 0000000000..d637aca1fa --- /dev/null +++ b/modules/grafana-lgtm/go.sum @@ -0,0 +1,221 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/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/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +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/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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/bridges/otelslog v0.3.0 h1:Kf8NK4WW/pn3f9Gwx6XJAB2zlaW2M3VLQ4sQ3TKJhA8= +go.opentelemetry.io/contrib/bridges/otelslog v0.3.0/go.mod h1:JV00+So1cv6GIYNUeO0xFfl/qE+DUtS3hpBlLIyOFUE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 h1:nOlJEAJyrcy8hexK65M+dsCHIx7CVVbybcFDNkcTcAc= +go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0/go.mod h1:u79lGGIlkg3Ryw425RbMjEkGYNxSnXRyR286O840+u4= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 h1:zBPZAISA9NOc5cE8zydqDiS0itvg/P/0Hn9m72a5gvM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0/go.mod h1:gcj2fFjEsqpV3fXuzAA+0Ze1p2/4MJ4T7d77AmkvueQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/grafana-lgtm/grafana.go b/modules/grafana-lgtm/grafana.go new file mode 100644 index 0000000000..3cee949938 --- /dev/null +++ b/modules/grafana-lgtm/grafana.go @@ -0,0 +1,204 @@ +package grafanalgtm + +import ( + "context" + "fmt" + + "github.com/docker/go-connections/nat" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + GrafanaPort = "3000/tcp" + LokiPort = "3100/tcp" + TempoPort = "3200/tcp" + OtlpGrpcPort = "4317/tcp" + OtlpHttpPort = "4318/tcp" + PrometheusPort = "9090/tcp" +) + +// GrafanaLGTMContainer represents the Grafana LGTM container type used in the module +type GrafanaLGTMContainer struct { + testcontainers.Container +} + +// Run creates an instance of the Grafana LGTM container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*GrafanaLGTMContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{GrafanaPort, LokiPort, TempoPort, OtlpGrpcPort, OtlpHttpPort, PrometheusPort}, + WaitingFor: wait.ForLog(".*The OpenTelemetry collector and the Grafana LGTM stack are up and running.*\\s").AsRegexp().WithOccurrence(1), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, fmt.Errorf("customize: %w", err) + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + if err != nil { + return nil, fmt.Errorf("generic container: %w", err) + } + + c := &GrafanaLGTMContainer{Container: container} + + url, err := c.OtlpHttpEndpoint(ctx) + if err != nil { + // return the container instance to allow the caller to clean up + return c, fmt.Errorf("otlp http endpoint: %w", err) + } + + testcontainers.Logger.Printf("Access to the Grafana dashboard: %s", url) + + return c, nil +} + +// WithAdminCredentials sets the admin credentials for the Grafana LGTM container +func WithAdminCredentials(user, password string) testcontainers.ContainerCustomizer { + return testcontainers.WithEnv(map[string]string{ + "GF_SECURITY_ADMIN_USER": user, + "GF_SECURITY_ADMIN_PASSWORD": password, + }) +} + +// LokiEndpoint returns the Loki endpoint +func (c *GrafanaLGTMContainer) LokiEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, LokiPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustLokiEndpoint returns the Loki endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustLokiEndpoint(ctx context.Context) string { + url, err := c.LokiEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +// TempoEndpoint returns the Tempo endpoint +func (c *GrafanaLGTMContainer) TempoEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, TempoPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustTempoEndpoint returns the Tempo endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustTempoEndpoint(ctx context.Context) string { + url, err := c.TempoEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +// HttpEndpoint returns the HTTP URL +func (c *GrafanaLGTMContainer) HttpEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, GrafanaPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustHttpEndpoint returns the HTTP endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustHttpEndpoint(ctx context.Context) string { + url, err := c.HttpEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +// OtlpHttpEndpoint returns the OTLP HTTP endpoint +func (c *GrafanaLGTMContainer) OtlpHttpEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, OtlpHttpPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustOtlpHttpEndpoint returns the OTLP HTTP endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustOtlpHttpEndpoint(ctx context.Context) string { + url, err := c.OtlpHttpEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +// OtlpGrpcEndpoint returns the OTLP gRPC endpoint +func (c *GrafanaLGTMContainer) OtlpGrpcEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, OtlpGrpcPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustOtlpGrpcEndpoint returns the OTLP gRPC endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustOtlpGrpcEndpoint(ctx context.Context) string { + url, err := c.OtlpGrpcEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +// PrometheusHttpEndpoint returns the Prometheus HTTP endpoint +func (c *GrafanaLGTMContainer) PrometheusHttpEndpoint(ctx context.Context) (string, error) { + url, err := baseEndpoint(ctx, c, PrometheusPort) + if err != nil { + return "", err + } + + return url, nil +} + +// MustPrometheusHttpEndpoint returns the Prometheus HTTP endpoint or panics if an error occurs +func (c *GrafanaLGTMContainer) MustPrometheusHttpEndpoint(ctx context.Context) string { + url, err := c.PrometheusHttpEndpoint(ctx) + if err != nil { + panic(err) + } + + return url +} + +func baseEndpoint(ctx context.Context, c *GrafanaLGTMContainer, port nat.Port) (string, error) { + host, err := c.Host(ctx) + if err != nil { + return "", err + } + + mappedPort, err := c.MappedPort(ctx, port) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s:%s", host, mappedPort.Port()), nil +} diff --git a/modules/grafana-lgtm/grafana_test.go b/modules/grafana-lgtm/grafana_test.go new file mode 100644 index 0000000000..826451e579 --- /dev/null +++ b/modules/grafana-lgtm/grafana_test.go @@ -0,0 +1,49 @@ +package grafanalgtm_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + grafanalgtm "github.com/testcontainers/testcontainers-go/modules/grafana-lgtm" +) + +func TestGrafanaLGTM(t *testing.T) { + ctx := context.Background() + + grafanaLgtmContainer, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0") + testcontainers.CleanupContainer(t, grafanaLgtmContainer) + require.NoError(t, err) + + // perform assertions + + t.Run("container is running with right version", func(t *testing.T) { + healthURL, err := url.Parse(fmt.Sprintf("http://%s/api/health", grafanaLgtmContainer.MustHttpEndpoint(ctx))) + require.NoError(t, err) + + httpReq := http.Request{ + Method: http.MethodGet, + URL: healthURL, + } + + httpClient := http.Client{} + + httpResp, err := httpClient.Do(&httpReq) + require.NoError(t, err) + + defer httpResp.Body.Close() + + require.Equal(t, http.StatusOK, httpResp.StatusCode) + + body := make(map[string]interface{}) + err = json.NewDecoder(httpResp.Body).Decode(&body) + require.NoError(t, err) + require.Equal(t, "11.0.0", body["version"]) + }) +} diff --git a/modules/inbucket/examples_test.go b/modules/inbucket/examples_test.go index b5c72d12b4..c10f8b5b58 100644 --- a/modules/inbucket/examples_test.go +++ b/modules/inbucket/examples_test.go @@ -9,26 +9,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/inbucket" ) -func ExampleRunContainer() { +func ExampleRun() { // runInbucketContainer { ctx := context.Background() - inbucketContainer, err := inbucket.RunContainer(ctx, testcontainers.WithImage("inbucket/inbucket:sha-2d409bb")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + inbucketContainer, err := inbucket.Run(ctx, "inbucket/inbucket:sha-2d409bb") defer func() { - if err := inbucketContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(inbucketContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := inbucketContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/inbucket/go.mod b/modules/inbucket/go.mod index ceffac58d5..65a656dad5 100644 --- a/modules/inbucket/go.mod +++ b/modules/inbucket/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/inbucket -go 1.21 +go 1.22 require ( github.com/inbucket/inbucket v2.0.0+incompatible - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,12 +27,13 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jhillyerd/inbucket v2.0.0+incompatible // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -50,13 +54,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/inbucket/go.sum b/modules/inbucket/go.sum index c867849ee4..84749386ff 100644 --- a/modules/inbucket/go.sum +++ b/modules/inbucket/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -56,12 +55,18 @@ github.com/jhillyerd/inbucket v2.0.0+incompatible h1:gTmxV077ktqV4ZbFjB/0rjiTrds github.com/jhillyerd/inbucket v2.0.0+incompatible/go.mod h1:yVEPtM/7T2OWUGzu6wKm1xIrcVPzhI9IW2MzvqIWaYY= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -82,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -93,6 +100,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -126,23 +135,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -153,38 +158,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/inbucket/inbucket.go b/modules/inbucket/inbucket.go index c446404df3..56695cb581 100644 --- a/modules/inbucket/inbucket.go +++ b/modules/inbucket/inbucket.go @@ -44,15 +44,25 @@ func (c *InbucketContainer) WebInterface(ctx context.Context) (string, error) { return "", err } - return fmt.Sprintf("http://%s", net.JoinHostPort(host, containerPort.Port())), nil + return "http://" + net.JoinHostPort(host, containerPort.Port()), nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Inbucket container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*InbucketContainer, error) { + return Run(ctx, "inbucket/inbucket:sha-2d409bb", opts...) +} + +// Run creates an instance of the Inbucket container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*InbucketContainer, error) { req := testcontainers.ContainerRequest{ - Image: "inbucket/inbucket:sha-2d409bb", - ExposedPorts: []string{"2500/tcp", "9000/tcp"}, - WaitingFor: wait.ForLog("SMTP listening on tcp4"), + Image: img, + ExposedPorts: []string{"2500/tcp", "9000/tcp", "1100/tcp"}, + WaitingFor: wait.ForAll( + wait.ForListeningPort("2500/tcp"), + wait.ForListeningPort("9000/tcp"), + wait.ForListeningPort("1100/tcp"), + ), } genericContainerReq := testcontainers.GenericContainerRequest{ @@ -67,9 +77,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *InbucketContainer + if container != nil { + c = &InbucketContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &InbucketContainer{Container: container}, nil + return c, nil } diff --git a/modules/inbucket/inbucket_test.go b/modules/inbucket/inbucket_test.go index 16b5bd90e9..1cb53cedd7 100644 --- a/modules/inbucket/inbucket_test.go +++ b/modules/inbucket/inbucket_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/inbucket/inbucket/pkg/rest/client" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" ) @@ -13,60 +14,34 @@ import ( func TestInbucket(t *testing.T) { ctx := context.Background() - container, err := RunContainer(ctx, testcontainers.WithImage("inbucket/inbucket:sha-2d409bb")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := Run(ctx, "inbucket/inbucket:sha-2d409bb") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // smtpConnection { - smtpUrl, err := container.SmtpConnection(ctx) + smtpUrl, err := ctr.SmtpConnection(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // webInterface { - webInterfaceUrl, err := container.WebInterface(ctx) + webInterfaceUrl, err := ctr.WebInterface(ctx) // } - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) restClient, err := client.New(webInterfaceUrl) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) headers, err := restClient.ListMailbox("to@example.org") - if err != nil { - t.Fatal(err) - } - if len(headers) != 0 { - t.Fatalf("expected 0 messages, got %d", len(headers)) - } + require.NoError(t, err) + require.Empty(t, headers) msg := []byte("To: to@example.org\r\n" + "Subject: Testcontainers test!\r\n" + "\r\n" + "This is a Testcontainers test.\r\n") - if err = smtp.SendMail(smtpUrl, nil, "from@example.org", []string{"to@example.org"}, msg); err != nil { - t.Fatal(err) - } + err = smtp.SendMail(smtpUrl, nil, "from@example.org", []string{"to@example.org"}, msg) + require.NoError(t, err) headers, err = restClient.ListMailbox("to@example.org") - if err != nil { - t.Fatal(err) - } - - if len(headers) != 1 { - t.Fatalf("expected 1 message, got %d", len(headers)) - } - // perform assertions + require.NoError(t, err) + require.Len(t, headers, 1) } diff --git a/modules/influxdb/examples_test.go b/modules/influxdb/examples_test.go index 23923882f8..30c892537f 100644 --- a/modules/influxdb/examples_test.go +++ b/modules/influxdb/examples_test.go @@ -9,31 +9,31 @@ import ( "github.com/testcontainers/testcontainers-go/modules/influxdb" ) -func ExampleRunContainer() { +func ExampleRun() { // runInfluxContainer { ctx := context.Background() - influxdbContainer, err := influxdb.RunContainer( - ctx, testcontainers.WithImage("influxdb:1.8.10"), + influxdbContainer, err := influxdb.Run(ctx, + "influxdb:1.8.10", influxdb.WithDatabase("influx"), influxdb.WithUsername("root"), influxdb.WithPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := influxdbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(influxdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := influxdbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/influxdb/go.mod b/modules/influxdb/go.mod index d6b4e8020f..43041bf961 100644 --- a/modules/influxdb/go.mod +++ b/modules/influxdb/go.mod @@ -1,25 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/influxdb -go 1.21 +go 1.22 require ( github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -27,12 +27,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -53,13 +53,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/influxdb/go.sum b/modules/influxdb/go.sum index e57f104ae3..4007c33ccd 100644 --- a/modules/influxdb/go.sum +++ b/modules/influxdb/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -41,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -55,8 +53,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSH github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= @@ -65,6 +63,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -99,6 +99,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -132,23 +134,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -159,33 +157,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -194,5 +189,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/influxdb/influxdb.go b/modules/influxdb/influxdb.go index 887da08e52..4c6024d79c 100644 --- a/modules/influxdb/influxdb.go +++ b/modules/influxdb/influxdb.go @@ -2,28 +2,30 @@ package influxdb import ( "context" + "encoding/json" "fmt" + "io" "path" - "strings" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// defaultImage { -const defaultImage = "influxdb:1.8" - -// } - // InfluxDbContainer represents the MySQL container type used in the module type InfluxDbContainer struct { testcontainers.Container } +// Deprecated: use Run instead // RunContainer creates an instance of the InfluxDB container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*InfluxDbContainer, error) { + return Run(ctx, "influxdb:1.8", opts...) +} + +// Run creates an instance of the InfluxDB container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*InfluxDbContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"8086/tcp", "8088/tcp"}, Env: map[string]string{ "INFLUXDB_BIND_ADDRESS": ":8088", @@ -33,7 +35,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize "INFLUXDB_HTTP_HTTPS_ENABLED": "false", "INFLUXDB_HTTP_AUTH_ENABLED": "false", }, - WaitingFor: wait.ForListeningPort("8086/tcp"), + WaitingFor: waitForHttpHealth(), } genericContainerReq := testcontainers.GenericContainerRequest{ ContainerRequest: req, @@ -41,47 +43,22 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) - } - - hasInitDb := false - - for _, f := range genericContainerReq.Files { - if f.ContainerFilePath == "/" && strings.HasSuffix(f.HostFilePath, "docker-entrypoint-initdb.d") { - // Init service in container will start influxdb, run scripts in docker-entrypoint-initdb.d and then - // terminate the influxdb server, followed by restart of influxdb. This is tricky to wait for, and - // in this case, we are assuming that data was added by init script, so we then look for an - // "Open shard" which is the last thing that happens before the server is ready to accept connections. - // This is probably different for InfluxDB 2.x, but that is left as an exercise for the reader. - strategies := []wait.Strategy{ - genericContainerReq.WaitingFor, - wait.ForLog("influxdb init process in progress..."), - wait.ForLog("Server shutdown completed"), - wait.ForLog("Opened shard"), - } - genericContainerReq.WaitingFor = wait.ForAll(strategies...) - hasInitDb = true - break + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err } } - if !hasInitDb { - if lastIndex := strings.LastIndex(genericContainerReq.Image, ":"); lastIndex != -1 { - tag := genericContainerReq.Image[lastIndex+1:] - if tag == "latest" || tag[0] == '2' { - genericContainerReq.WaitingFor = wait.ForLog(`Listening log_id=[0-9a-zA-Z_]+ service=tcp-listener transport=http`).AsRegexp() - } - } else { - genericContainerReq.WaitingFor = wait.ForLog("Listening for signals") - } + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *InfluxDbContainer + if container != nil { + c = &InfluxDbContainer{Container: container} } - container, err := testcontainers.GenericContainer(ctx, genericContainerReq) if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &InfluxDbContainer{container}, nil + return c, nil } func (c *InfluxDbContainer) MustConnectionUrl(ctx context.Context) string { @@ -139,9 +116,8 @@ func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { } } -// WithInitDb will copy a 'docker-entrypoint-initdb.d' directory to the container. -// The secPath is the path to the directory on the host machine. -// The directory will be copied to the root of the container. +// WithInitDb returns a request customizer that initialises the database using the file `docker-entrypoint-initdb.d` +// located in `srcPath` directory. func WithInitDb(srcPath string) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { cf := testcontainers.ContainerFile{ @@ -150,6 +126,25 @@ func WithInitDb(srcPath string) testcontainers.CustomizeRequestOption { FileMode: 0o755, } req.Files = append(req.Files, cf) + + req.WaitingFor = wait.ForAll( + wait.ForLog("Server shutdown completed"), + waitForHttpHealth(), + ) return nil } } + +func waitForHttpHealth() *wait.HTTPStrategy { + return wait.ForHTTP("/health"). + WithResponseMatcher(func(body io.Reader) bool { + decoder := json.NewDecoder(body) + r := struct { + Status string `json:"status"` + }{} + if err := decoder.Decode(&r); err != nil { + return false + } + return r.Status == "pass" + }) +} diff --git a/modules/influxdb/influxdb_test.go b/modules/influxdb/influxdb_test.go index f80afc1cd6..3c4379c66c 100644 --- a/modules/influxdb/influxdb_test.go +++ b/modules/influxdb/influxdb_test.go @@ -15,60 +15,43 @@ import ( "github.com/testcontainers/testcontainers-go/modules/influxdb" ) -func containerCleanup(t *testing.T, container testcontainers.Container) { - err := container.Terminate(context.Background()) - require.NoError(t, err, "failed to terminate container") -} - func TestV1Container(t *testing.T) { ctx := context.Background() - influxDbContainer, err := influxdb.RunContainer(ctx, - testcontainers.WithImage("influxdb:1.8.10"), - ) + influxDbContainer, err := influxdb.Run(ctx, "influxdb:1.8.10") + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) state, err := influxDbContainer.State(ctx) require.NoError(t, err) - if !state.Running { - t.Fatal("InfluxDB container is not running") - } + require.Truef(t, state.Running, "InfluxDB container is not running") } func TestV2Container(t *testing.T) { ctx := context.Background() - influxDbContainer, err := influxdb.RunContainer(ctx, - testcontainers.WithImage("influxdb:latest"), + influxDbContainer, err := influxdb.Run(ctx, + "influxdb:2.7.5-alpine", influxdb.WithDatabase("foo"), influxdb.WithUsername("root"), influxdb.WithPassword("password"), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) state, err := influxDbContainer.State(ctx) require.NoError(t, err) - if !state.Running { - t.Fatal("InfluxDB container is not running") - } + require.Truef(t, state.Running, "InfluxDB container is not running") } func TestWithInitDb(t *testing.T) { ctx := context.Background() - influxDbContainer, err := influxdb.RunContainer(ctx, - testcontainers.WithImage("influxdb:1.8.10"), - influxdb.WithInitDb(filepath.Join("testdata")), + influxDbContainer, err := influxdb.Run(ctx, + "influxdb:1.8.10", + influxdb.WithInitDb("testdata"), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) if state, err := influxDbContainer.State(ctx); err != nil || !state.Running { require.NoError(t, err) @@ -85,9 +68,7 @@ func TestWithInitDb(t *testing.T) { response, err := cli.Query(q) require.NoError(t, err) - if response.Error() != nil { - t.Fatal(response.Error()) - } + require.NoError(t, response.Error()) testJson, err := json.Marshal(response.Results) require.NoError(t, err) @@ -97,14 +78,12 @@ func TestWithInitDb(t *testing.T) { func TestWithConfigFile(t *testing.T) { influxVersion := "1.8.10" - influxDbContainer, err := influxdb.RunContainer(context.Background(), - testcontainers.WithImage("influxdb:"+influxVersion), + influxDbContainer, err := influxdb.Run(context.Background(), + "influxdb:"+influxVersion, influxdb.WithConfigFile(filepath.Join("testdata", "influxdb.conf")), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) if state, err := influxDbContainer.State(context.Background()); err != nil || !state.Running { require.NoError(t, err) @@ -122,5 +101,5 @@ func TestWithConfigFile(t *testing.T) { require.NoError(t, err) assert.Equal(t, "1.8.10", version) - assert.True(t, ping > 0) + assert.Greater(t, ping, time.Duration(0)) } diff --git a/modules/k3s/go.mod b/modules/k3s/go.mod index 236ec89874..5a382b7739 100644 --- a/modules/k3s/go.mod +++ b/modules/k3s/go.mod @@ -1,11 +1,12 @@ module github.com/testcontainers/testcontainers-go/modules/k3s -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.2 k8s.io/apimachinery v0.29.2 @@ -15,14 +16,14 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -40,10 +41,11 @@ require ( github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -55,6 +57,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -67,18 +70,16 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.16.1 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/modules/k3s/go.sum b/modules/k3s/go.sum index 49d6b5dd86..4584dcf7ac 100644 --- a/modules/k3s/go.sum +++ b/modules/k3s/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -52,11 +52,13 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -78,8 +80,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -93,6 +95,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -139,6 +143,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -153,6 +159,7 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F 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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -174,66 +181,75 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/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/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -248,8 +264,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= diff --git a/modules/k3s/k3s.go b/modules/k3s/k3s.go index 4190cfba71..f223c08c2b 100644 --- a/modules/k3s/k3s.go +++ b/modules/k3s/k3s.go @@ -47,15 +47,21 @@ func WithManifest(manifestPath string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the K3s container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*K3sContainer, error) { + return Run(ctx, "rancher/k3s:v1.27.1-k3s1", opts...) +} + +// Run creates an instance of the K3s container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*K3sContainer, error) { host, err := getContainerHost(ctx, opts...) if err != nil { return nil, err } req := testcontainers.ContainerRequest{ - Image: "docker.io/rancher/k3s:v1.27.1-k3s1", + Image: img, ExposedPorts: []string{ defaultKubeSecurePort, defaultRancherWebhookPort, @@ -92,11 +98,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *K3sContainer + if container != nil { + c = &K3sContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &K3sContainer{Container: container}, nil + return c, nil } func getContainerHost(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (string, error) { @@ -209,7 +220,7 @@ func (c *K3sContainer) LoadImages(ctx context.Context, images ...string) error { return fmt.Errorf("saving images %w", err) } - containerPath := fmt.Sprintf("/tmp/%s", filepath.Base(imagesTar.Name())) + containerPath := "/tmp/" + filepath.Base(imagesTar.Name()) err = c.Container.CopyFileToContainer(ctx, imagesTar.Name(), containerPath, 0x644) if err != nil { return fmt.Errorf("copying image to container %w", err) diff --git a/modules/k3s/k3s_example_test.go b/modules/k3s/k3s_example_test.go index 2d065bc70a..cd1c7afc7e 100644 --- a/modules/k3s/k3s_example_test.go +++ b/modules/k3s/k3s_example_test.go @@ -13,50 +13,52 @@ import ( "github.com/testcontainers/testcontainers-go/modules/k3s" ) -func ExampleRunContainer() { +func ExampleRun() { // runK3sContainer { ctx := context.Background() - k3sContainer, err := k3s.RunContainer(ctx, - testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + k3sContainer, err := k3s.Run(ctx, "rancher/k3s:v1.27.1-k3s1") defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(k3sContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := k3sContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) if err != nil { - log.Fatalf("failed to get kubeconfig: %s", err) + log.Printf("failed to get kubeconfig: %s", err) + return } restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) if err != nil { - log.Fatalf("failed to create rest config: %s", err) + log.Printf("failed to create rest config: %s", err) + return } k8s, err := kubernetes.NewForConfig(restcfg) if err != nil { - log.Fatalf("failed to create k8s client: %s", err) + log.Printf("failed to create k8s client: %s", err) + return } nodes, err := k8s.CoreV1().Nodes().List(ctx, v1.ListOptions{}) if err != nil { - log.Fatalf("failed to list nodes: %s", err) + log.Printf("failed to list nodes: %s", err) + return } fmt.Println(len(nodes.Items)) diff --git a/modules/k3s/k3s_test.go b/modules/k3s/k3s_test.go index 32b7cfa66d..d89121dde3 100644 --- a/modules/k3s/k3s_test.go +++ b/modules/k3s/k3s_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kwait "k8s.io/apimachinery/pkg/util/wait" @@ -22,58 +23,34 @@ func Test_LoadImages(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Minute)) defer cancel() - k3sContainer, err := k3s.RunContainer(ctx, - testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1"), - ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + k3sContainer, err := k3s.Run(ctx, "rancher/k3s:v1.27.1-k3s1") + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) k8s, err := kubernetes.NewForConfig(restcfg) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) provider, err := testcontainers.ProviderDocker.GetProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // ensure nginx image is available locally err = provider.PullImage(ctx, "nginx") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Run("Test load image not available", func(t *testing.T) { err := k3sContainer.LoadImages(ctx, "fake.registry/fake:non-existing") - if err == nil { - t.Fatal("should had failed") - } + require.Error(t, err) }) t.Run("Test load image in cluster", func(t *testing.T) { err := k3sContainer.LoadImages(ctx, "nginx") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ @@ -95,9 +72,7 @@ func Test_LoadImages(t *testing.T) { } _, err = k8s.CoreV1().Pods("default").Create(ctx, pod, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = kwait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) { state, err := getTestPodState(ctx, k8s) @@ -109,25 +84,20 @@ func Test_LoadImages(t *testing.T) { } return state.Running != nil, nil }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err := getTestPodState(ctx, k8s) - if err != nil { - t.Fatal(err) - } - if state.Running == nil { - t.Fatalf("Unexpected status %v", state) - } + require.NoError(t, err) + require.NotNil(t, state.Running) }) } -func getTestPodState(ctx context.Context, k8s *kubernetes.Clientset) (state corev1.ContainerState, err error) { +func getTestPodState(ctx context.Context, k8s *kubernetes.Clientset) (corev1.ContainerState, error) { var pod *corev1.Pod + var err error pod, err = k8s.CoreV1().Pods("default").Get(ctx, "test-pod", metav1.GetOptions{}) if err != nil || len(pod.Status.ContainerStatuses) == 0 { - return + return corev1.ContainerState{}, err } return pod.Status.ContainerStatuses[0].State, nil } @@ -135,34 +105,18 @@ func getTestPodState(ctx context.Context, k8s *kubernetes.Clientset) (state core func Test_APIServerReady(t *testing.T) { ctx := context.Background() - k3sContainer, err := k3s.RunContainer(ctx, - testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1"), - ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + k3sContainer, err := k3s.Run(ctx, "rancher/k3s:v1.27.1-k3s1") + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) k8s, err := kubernetes.NewForConfig(restcfg) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ @@ -183,27 +137,17 @@ func Test_APIServerReady(t *testing.T) { } _, err = k8s.CoreV1().Pods("default").Create(context.Background(), pod, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("failed to create pod %v", err) - } + require.NoError(t, err) } func Test_WithManifestOption(t *testing.T) { ctx := context.Background() - k3sContainer, err := k3s.RunContainer(ctx, - testcontainers.WithImage("docker.io/rancher/k3s:v1.27.1-k3s1"), + k3sContainer, err := k3s.Run(ctx, + "rancher/k3s:v1.27.1-k3s1", k3s.WithManifest("nginx-manifest.yaml"), testcontainers.WithWaitStrategy(wait.ForExec([]string{"kubectl", "wait", "pod", "nginx", "--for=condition=Ready"})), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) } diff --git a/modules/k6/examples_test.go b/modules/k6/examples_test.go index 99cc77dcde..c842814d4c 100644 --- a/modules/k6/examples_test.go +++ b/modules/k6/examples_test.go @@ -11,7 +11,7 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -func ExampleRunContainer() { +func ExampleRun() { // runHTTPBin { ctx := context.Background() @@ -29,52 +29,66 @@ func ExampleRunContainer() { Started: true, } httpbin, err := testcontainers.GenericContainer(ctx, gcr) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := httpbin.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(httpbin); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } // getHTTPBinIP { httpbinIP, err := httpbin.ContainerIP(ctx) if err != nil { - log.Fatalf("failed to get container IP: %s", err) // nolint:gocritic + log.Printf("failed to get container IP: %s", err) + return } // } absPath, err := filepath.Abs(filepath.Join("scripts", "httpbin.js")) if err != nil { - log.Fatalf("failed to get absolute path to test script: %s", err) + log.Printf("failed to get absolute path to test script: %s", err) + return } // runK6Container { // run the httpbin.js test scripts passing the IP address the httpbin container - k6, err := k6.RunContainer( + k6, err := k6.Run( ctx, + "szkiba/k6x:v0.3.1", k6.WithCache(), k6.WithTestScript(absPath), k6.SetEnvVar("HTTPBIN", httpbinIP), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := k6.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + cacheMount, err := k6.CacheMount(ctx) + if err != nil { + log.Printf("failed to determine cache mount: %s", err) + } + + var options []testcontainers.TerminateOption + if cacheMount != "" { + options = append(options, testcontainers.RemoveVolumes(cacheMount)) + } + + if err = testcontainers.TerminateContainer(k6, options...); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } //} // assert the result of the test state, err := k6.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.ExitCode) diff --git a/modules/k6/go.mod b/modules/k6/go.mod index 428b3e7d45..cd1600f0f7 100644 --- a/modules/k6/go.mod +++ b/modules/k6/go.mod @@ -1,22 +1,24 @@ module github.com/testcontainers/testcontainers-go/modules/k6 -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/docker/docker v27.1.1+incompatible + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.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/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -24,11 +26,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -37,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -48,13 +52,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/k6/go.sum b/modules/k6/go.sum index d8ba86a7c1..447eec4038 100644 --- a/modules/k6/go.sum +++ b/modules/k6/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -89,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -122,23 +131,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -149,38 +154,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/k6/k6.go b/modules/k6/k6.go index c0e9f01fe0..e44c354d2d 100644 --- a/modules/k6/k6.go +++ b/modules/k6/k6.go @@ -17,6 +17,9 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +// cacheTarget is the path to the cache volume in the container. +const cacheTarget = "/cache" + // K6Container represents the K6 container type used in the module type K6Container struct { testcontainers.Container @@ -32,11 +35,9 @@ type DownloadableFile struct { func (d *DownloadableFile) getDownloadPath() string { baseName := path.Base(d.Uri.Path) return path.Join(d.DownloadDir, baseName) - } func downloadFileFromDescription(d DownloadableFile) error { - client := http.Client{Timeout: time.Second * 60} req, err := http.NewRequest(http.MethodGet, d.Uri.String(), nil) if err != nil { @@ -62,7 +63,6 @@ func downloadFileFromDescription(d DownloadableFile) error { _, err = io.Copy(downloadedFile, resp.Body) return err - } // WithTestScript mounts the given script into the ./test directory in the container @@ -78,7 +78,6 @@ func WithTestScript(scriptPath string) testcontainers.CustomizeRequestOption { } return WithTestScriptReader(f, scriptBaseName) - } // WithTestScriptReader copies files into the Container using the Reader API @@ -144,7 +143,7 @@ func WithCache() testcontainers.CustomizeRequestOption { cacheVol := os.Getenv("TC_K6_BUILD_CACHE") // if no volume is provided, create one and ensure add labels for garbage collection if cacheVol == "" { - cacheVol = fmt.Sprintf("k6-cache-%s", testcontainers.SessionID()) + cacheVol = "k6-cache-" + testcontainers.SessionID() volOptions = &mount.VolumeOptions{ Labels: testcontainers.GenericLabels(), } @@ -156,7 +155,7 @@ func WithCache() testcontainers.CustomizeRequestOption { Name: cacheVol, VolumeOptions: volOptions, }, - Target: "/cache", + Target: cacheTarget, } req.Mounts = append(req.Mounts, mount) @@ -164,10 +163,16 @@ func WithCache() testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the K6 container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*K6Container, error) { + return Run(ctx, "szkiba/k6x:v0.3.1", opts...) +} + +// Run creates an instance of the K6 container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*K6Container, error) { req := testcontainers.ContainerRequest{ - Image: "szkiba/k6x:v0.3.1", + Image: img, Cmd: []string{"run"}, WaitingFor: wait.ForExit(), } @@ -184,9 +189,31 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *K6Container + if container != nil { + c = &K6Container{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// CacheMount returns the name of volume used as a cache or an empty string +// if no cache was found. +func (k *K6Container) CacheMount(ctx context.Context) (string, error) { + inspect, err := k.Inspect(ctx) + if err != nil { + return "", fmt.Errorf("inspect: %w", err) + } + + for _, m := range inspect.Mounts { + if m.Type == mount.TypeVolume && m.Destination == cacheTarget { + return m.Name, nil + } } - return &K6Container{Container: container}, nil + return "", nil } diff --git a/modules/k6/k6_test.go b/modules/k6/k6_test.go index 29a7c2bcce..bc40bd13a6 100644 --- a/modules/k6/k6_test.go +++ b/modules/k6/k6_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/k6" ) @@ -39,48 +41,52 @@ func TestK6(t *testing.T) { }, } + var cacheMount string + t.Cleanup(func() { + if cacheMount == "" { + return + } + + // Ensure the cache volume is removed as mounts that specify a volume + // source as defined by the name are not removed automatically. + provider, err := testcontainers.NewDockerProvider() + require.NoError(t, err) + defer provider.Close() + + require.NoError(t, provider.Client().VolumeRemove(context.Background(), cacheMount, true)) + }) + for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { ctx := context.Background() var options testcontainers.CustomizeRequestOption if !strings.HasPrefix(tc.script, "http") { absPath, err := filepath.Abs(filepath.Join("scripts", tc.script)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) options = k6.WithTestScript(absPath) } else { uri, err := url.Parse(tc.script) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) desc := k6.DownloadableFile{Uri: *uri, DownloadDir: t.TempDir()} options = k6.WithRemoteTestScript(desc) } - container, err := k6.RunContainer(ctx, k6.WithCache(), options) - if err != nil { - t.Fatal(err) + ctr, err := k6.Run(ctx, "szkiba/k6x:v0.3.1", k6.WithCache(), options) + if ctr != nil && cacheMount == "" { + // First container, determine the cache mount. + cacheMount, err = ctr.CacheMount(ctx) + require.NoError(t, err) } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // assert the result of the test - state, err := container.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.ExitCode != tc.expect { - t.Fatalf("expected %d got %d", tc.expect, state.ExitCode) - } + state, err := ctr.State(ctx) + require.NoError(t, err) + require.Equal(t, tc.expect, state.ExitCode) }) } } diff --git a/modules/kafka/consumer_test.go b/modules/kafka/consumer_test.go index d7305540f8..9df926e72d 100644 --- a/modules/kafka/consumer_test.go +++ b/modules/kafka/consumer_test.go @@ -16,6 +16,7 @@ type TestKafkaConsumer struct { } func NewTestKafkaConsumer(t *testing.T) (*TestKafkaConsumer, <-chan bool, <-chan bool, func()) { + t.Helper() kc := &TestKafkaConsumer{ t: t, ready: make(chan bool, 1), diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go index ee68cca9ff..c275924ecc 100644 --- a/modules/kafka/examples_test.go +++ b/modules/kafka/examples_test.go @@ -9,29 +9,29 @@ import ( "github.com/testcontainers/testcontainers-go/modules/kafka" ) -func ExampleRunContainer() { +func ExampleRun() { // runKafkaContainer { ctx := context.Background() - kafkaContainer, err := kafka.RunContainer(ctx, + kafkaContainer, err := kafka.Run(ctx, + "confluentinc/confluent-local:7.5.0", kafka.WithClusterID("test-cluster"), - testcontainers.WithImage("confluentinc/confluent-local:7.5.0"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container after defer func() { - if err := kafkaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(kafkaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := kafkaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(kafkaContainer.ClusterID) diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index e45a2c3363..da1366a692 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -1,26 +1,27 @@ module github.com/testcontainers/testcontainers-go/modules/kafka -go 1.21 +go 1.22 require ( github.com/IBM/sarama v1.42.1 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 golang.org/x/mod v0.16.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/eapache/go-resiliency v1.4.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect @@ -30,7 +31,6 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -41,9 +41,11 @@ require ( github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -53,6 +55,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // 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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -65,13 +68,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 60787c41c6..dfa6758b3f 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -6,27 +6,28 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -50,8 +51,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -86,12 +85,18 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -116,6 +121,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -127,6 +134,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -165,8 +174,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -181,14 +190,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -204,45 +213,44 @@ golang.org/x/sys v0.5.0/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index f5d49e9db9..73e392e1d2 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -2,8 +2,10 @@ package kafka import ( "context" + "errors" "fmt" "math" + "strconv" "strings" "github.com/docker/go-connections/nat" @@ -36,10 +38,16 @@ type KafkaContainer struct { ClusterID string } +// Deprecated: use Run instead // RunContainer creates an instance of the Kafka container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) { + return Run(ctx, "confluentinc/confluent-local:7.5.0", opts...) +} + +// Run creates an instance of the Kafka container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) { req := testcontainers.ContainerRequest{ - Image: "confluentinc/confluent-local:7.5.0", + Image: img, ExposedPorts: []string{string(publicPort)}, Env: map[string]string{ // envVars { @@ -52,7 +60,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1", "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1", "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1", - "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": fmt.Sprintf("%d", math.MaxInt), + "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": strconv.Itoa(math.MaxInt), "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0", "KAFKA_NODE_ID": "1", "KAFKA_PROCESS_ROLES": "broker,controller", @@ -65,24 +73,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ { PostStarts: []testcontainers.ContainerHook{ - // 1. copy the starter script into the container + // Use a single hook to copy the starter script and wait for + // the Kafka server to be ready. This prevents the wait running + // if the starter script fails to copy. func(ctx context.Context, c testcontainers.Container) error { - host, err := c.Host(ctx) - if err != nil { - return err - } - - port, err := c.MappedPort(ctx, publicPort) - if err != nil { - return err + // 1. copy the starter script into the container + if err := copyStarterScript(ctx, c); err != nil { + return fmt.Errorf("copy starter script: %w", err) } - scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), host) - - return c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755) - }, - // 2. wait for the Kafka server to be ready - func(ctx context.Context, c testcontainers.Container) error { + // 2. wait for the Kafka server to be ready return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c) }, }, @@ -106,16 +106,53 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize return nil, err } - clusterID := genericContainerReq.Env["CLUSTER_ID"] - configureControllerQuorumVoters(&genericContainerReq) container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *KafkaContainer + if container != nil { + c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// copyStarterScript copies the starter script into the container. +func copyStarterScript(ctx context.Context, c testcontainers.Container) error { + if err := wait.ForListeningPort(publicPort). + SkipInternalCheck(). + WaitUntilReady(ctx, c); err != nil { + return fmt.Errorf("wait for exposed port: %w", err) } - return &KafkaContainer{Container: container, ClusterID: clusterID}, nil + host, err := c.Host(ctx) + if err != nil { + return fmt.Errorf("host: %w", err) + } + + inspect, err := c.Inspect(ctx) + if err != nil { + return fmt.Errorf("inspect: %w", err) + } + + hostname := inspect.Config.Hostname + + port, err := c.MappedPort(ctx, publicPort) + if err != nil { + return fmt.Errorf("mapped port: %w", err) + } + + scriptContent := fmt.Sprintf(starterScriptContent, host, port.Int(), hostname) + + if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil { + return fmt.Errorf("copy to container: %w", err) + } + + return nil } func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption { @@ -168,7 +205,7 @@ func configureControllerQuorumVoters(req *testcontainers.GenericContainerRequest // which is available since version 7.0.0. func validateKRaftVersion(fqName string) error { if fqName == "" { - return fmt.Errorf("image cannot be empty") + return errors.New("image cannot be empty") } image := fqName[:strings.LastIndex(fqName, ":")] @@ -183,7 +220,7 @@ func validateKRaftVersion(fqName string) error { // semver requires the version to start with a "v" if !strings.HasPrefix(version, "v") { - version = fmt.Sprintf("v%s", version) + version = "v" + version } if semver.Compare(version, "v7.4.0") < 0 { // version < v7.4.0 diff --git a/modules/kafka/kafka_helpers_test.go b/modules/kafka/kafka_helpers_test.go index 4a49a00f50..6ef7deb60f 100644 --- a/modules/kafka/kafka_helpers_test.go +++ b/modules/kafka/kafka_helpers_test.go @@ -3,6 +3,8 @@ package kafka import ( "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" ) @@ -55,9 +57,7 @@ func TestConfigureQuorumVoters(t *testing.T) { t.Run(test.name, func(t *testing.T) { configureControllerQuorumVoters(test.req) - if test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] != test.expectedVoters { - t.Fatalf("expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"]) - } + require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"]) }) } } @@ -99,12 +99,10 @@ func TestValidateKRaftVersion(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := validateKRaftVersion(test.image) - if test.wantErr && err == nil { - t.Fatalf("expected error, got nil") - } - - if !test.wantErr && err != nil { - t.Fatalf("expected no error, got %s", err) + if test.wantErr { + require.Errorf(t, err, "expected error, got nil") + } else { + require.NoErrorf(t, err, "expected no error, got %s", err) } }) } diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 662bb5d0a8..af858f849f 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/IBM/sarama" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" @@ -16,36 +17,25 @@ func TestKafka(t *testing.T) { ctx := context.Background() - kafkaContainer, err := kafka.RunContainer(ctx, kafka.WithClusterID("kraftCluster"), testcontainers.WithImage("confluentinc/confluent-local:7.5.0")) - if err != nil { - t.Fatal(err) - } + kafkaContainer, err := kafka.Run(ctx, "confluentinc/confluent-local:7.5.0", kafka.WithClusterID("kraftCluster")) + testcontainers.CleanupContainer(t, kafkaContainer) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := kafkaContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + assertAdvertisedListeners(t, kafkaContainer) - if !strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster") { - t.Fatalf("expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID) - } + require.Truef(t, strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster"), "expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID) // getBrokers { brokers, err := kafkaContainer.Brokers(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) config := sarama.NewConfig() client, err := sarama.NewConsumerGroup(brokers, "groupName", config) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) consumer, ready, done, cancel := NewTestKafkaConsumer(t) + defer cancel() go func() { if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil { cancel() @@ -61,35 +51,41 @@ func TestKafka(t *testing.T) { config.Producer.Return.Successes = true producer, err := sarama.NewSyncProducer(brokers, config) - if err != nil { - cancel() - t.Fatal(err) - } + require.NoError(t, err) - if _, _, err := producer.SendMessage(&sarama.ProducerMessage{ + _, _, err = producer.SendMessage(&sarama.ProducerMessage{ Topic: topic, Key: sarama.StringEncoder("key"), Value: sarama.StringEncoder("value"), - }); err != nil { - cancel() - t.Fatal(err) - } + }) + require.NoError(t, err) <-done - if !strings.EqualFold(string(consumer.message.Key), "key") { - t.Fatalf("expected key to be %s, got %s", "key", string(consumer.message.Key)) - } - if !strings.EqualFold(string(consumer.message.Value), "value") { - t.Fatalf("expected value to be %s, got %s", "value", string(consumer.message.Value)) - } + require.Truef(t, strings.EqualFold(string(consumer.message.Key), "key"), "expected key to be %s, got %s", "key", string(consumer.message.Key)) + require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value)) } func TestKafka_invalidVersion(t *testing.T) { ctx := context.Background() - _, err := kafka.RunContainer(ctx, kafka.WithClusterID("kraftCluster"), testcontainers.WithImage("confluentinc/confluent-local:6.3.3")) - if err == nil { - t.Fatal(err) - } + ctr, err := kafka.Run(ctx, "confluentinc/confluent-local:6.3.3", kafka.WithClusterID("kraftCluster")) + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) +} + +// assertAdvertisedListeners checks that the advertised listeners are set correctly: +// - The BROKER:// protocol is using the hostname of the Kafka container +func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { + t.Helper() + inspect, err := container.Inspect(context.Background()) + require.NoError(t, err) + + brokerURL := "BROKER://" + inspect.Config.Hostname + ":9092" + + ctx := context.Background() + + bs := testcontainers.RequireContainerExec(ctx, t, container, []string{"cat", "/usr/sbin/testcontainers_start.sh"}) + + require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs) } diff --git a/modules/localstack/examples_test.go b/modules/localstack/examples_test.go index 2e5e0636ab..d503ecee6f 100644 --- a/modules/localstack/examples_test.go +++ b/modules/localstack/examples_test.go @@ -19,28 +19,26 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -func ExampleRunContainer() { +func ExampleRun() { // runLocalstackContainer { ctx := context.Background() - localstackContainer, err := localstack.RunContainer(ctx, - testcontainers.WithImage("localstack/localstack:1.4.0"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + localstackContainer, err := localstack.Run(ctx, "localstack/localstack:1.4.0") defer func() { - if err := localstackContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(localstackContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := localstackContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -49,38 +47,45 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_withNetwork() { +func ExampleRun_withNetwork() { // localstackWithNetwork { ctx := context.Background() - newNetwork, err := network.New(ctx, network.WithCheckDuplicate()) + newNetwork, err := network.New(ctx) if err != nil { - log.Fatalf("failed to create network: %s", err) + log.Printf("failed to create network: %s", err) + return } + defer func() { + if err := newNetwork.Remove(context.Background()); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + nwName := newNetwork.Name - localstackContainer, err := localstack.RunContainer( + localstackContainer, err := localstack.Run( ctx, - testcontainers.WithImage("localstack/localstack:0.13.0"), + "localstack/localstack:0.13.0", testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), network.WithNetwork([]string{nwName}, newNetwork), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - - // Clean up the container defer func() { - if err := localstackContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(localstackContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } networks, err := localstackContainer.Networks(ctx) if err != nil { - log.Fatalf("failed to get container networks: %s", err) // nolint:gocritic + log.Printf("failed to get container networks: %s", err) + return } fmt.Println(len(networks)) @@ -89,17 +94,23 @@ func ExampleRunContainer_withNetwork() { // 1 } -func ExampleRunContainer_legacyMode() { +func ExampleRun_legacyMode() { ctx := context.Background() - _, err := localstack.RunContainer( + ctr, err := localstack.Run( ctx, - testcontainers.WithImage("localstack/localstack:0.10.0"), + "localstack/localstack:0.10.0", testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), testcontainers.WithWaitStrategy(wait.ForLog("Ready.").WithStartupTimeout(5*time.Minute).WithOccurrence(1)), ) + defer func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err == nil { - log.Fatalf("expected an error, got nil") + log.Printf("expected an error, got nil") + return } fmt.Println(err) @@ -108,7 +119,7 @@ func ExampleRunContainer_legacyMode() { // version=localstack/localstack:0.10.0. Testcontainers for Go does not support running LocalStack in legacy mode. Please use a version >= 0.11.0 } -func ExampleRunContainer_usingLambdas() { +func ExampleRun_usingLambdas() { ctx := context.Background() flagsFn := func() string { @@ -125,8 +136,8 @@ func ExampleRunContainer_usingLambdas() { lambdaName := "localstack-lambda-url-example" // withCustomContainerRequest { - container, err := localstack.RunContainer(ctx, - testcontainers.WithImage("localstack/localstack:2.3.0"), + ctr, err := localstack.Run(ctx, + "localstack/localstack:2.3.0", testcontainers.WithEnv(map[string]string{ "SERVICES": "lambda", "LAMBDA_DOCKER_FLAGS": flagsFn(), @@ -141,17 +152,17 @@ func ExampleRunContainer_usingLambdas() { }, }, }), - // } ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } + // } defer func() { - err := container.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // the three commands below are doing the following: // 1. create a lambda function @@ -171,9 +182,10 @@ func ExampleRunContainer_usingLambdas() { {"awslocal", "lambda", "wait", "function-active-v2", "--function-name", lambdaName}, } for _, cmd := range lambdaCommands { - _, _, err := container.Exec(ctx, cmd) + _, _, err := ctr.Exec(ctx, cmd) if err != nil { - log.Fatalf("failed to execute command %v: %s", cmd, err) // nolint:gocritic + log.Printf("failed to execute command %v: %s", cmd, err) + return } } @@ -181,15 +193,17 @@ func ExampleRunContainer_usingLambdas() { cmd := []string{ "awslocal", "lambda", "list-function-url-configs", "--function-name", lambdaName, } - _, reader, err := container.Exec(ctx, cmd, exec.Multiplexed()) + _, reader, err := ctr.Exec(ctx, cmd, exec.Multiplexed()) if err != nil { - log.Fatalf("failed to execute command %v: %s", cmd, err) + log.Printf("failed to execute command %v: %s", cmd, err) + return } buf := new(bytes.Buffer) _, err = buf.ReadFrom(reader) if err != nil { - log.Fatalf("failed to read from reader: %s", err) + log.Printf("failed to read from reader: %s", err) + return } content := buf.Bytes() @@ -207,7 +221,8 @@ func ExampleRunContainer_usingLambdas() { v := &FunctionURLConfig{} err = json.Unmarshal(content, v) if err != nil { - log.Fatalf("failed to unmarshal content: %s", err) + log.Printf("failed to unmarshal content: %s", err) + return } httpClient := http.Client{ @@ -217,21 +232,24 @@ func ExampleRunContainer_usingLambdas() { functionURL := v.FunctionURLConfigs[0].FunctionURL // replace the port with the one exposed by the container - mappedPort, err := container.MappedPort(ctx, "4566/tcp") + mappedPort, err := ctr.MappedPort(ctx, "4566/tcp") if err != nil { - log.Fatalf("failed to get mapped port: %s", err) + log.Printf("failed to get mapped port: %s", err) + return } functionURL = strings.ReplaceAll(functionURL, "4566", mappedPort.Port()) resp, err := httpClient.Post(functionURL, "application/json", bytes.NewBufferString(`{"num1": "10", "num2": "10"}`)) if err != nil { - log.Fatalf("failed to send request to lambda function: %s", err) + log.Printf("failed to send request to lambda function: %s", err) + return } jsonResponse, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("failed to read response body: %s", err) + log.Printf("failed to read response body: %s", err) + return } fmt.Println(string(jsonResponse)) diff --git a/modules/localstack/go.mod b/modules/localstack/go.mod index 5c41132f16..a96a35a1ba 100644 --- a/modules/localstack/go.mod +++ b/modules/localstack/go.mod @@ -1,6 +1,6 @@ module github.com/testcontainers/testcontainers-go/modules/localstack -go 1.21 +go 1.22 require ( github.com/aws/aws-sdk-go v1.50.31 @@ -8,18 +8,18 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.5 github.com/aws/aws-sdk-go-v2/credentials v1.17.5 github.com/aws/aws-sdk-go-v2/service/s3 v1.51.2 - github.com/docker/docker v25.0.5+incompatible + github.com/aws/smithy-go v1.21.0 + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 golang.org/x/mod v0.16.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.12.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect @@ -33,26 +33,26 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.2 // indirect - github.com/aws/smithy-go v1.20.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.17.7 // indirect github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -73,12 +73,11 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.19.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect - google.golang.org/grpc v1.62.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/localstack/go.sum b/modules/localstack/go.sum index 2d5b3a25d3..cd93facea9 100644 --- a/modules/localstack/go.sum +++ b/modules/localstack/go.sum @@ -4,10 +4,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.12.0 h1:rbICA+XZFwrBef2Odk++0LjFvClNCJGRK+fsrP254Ts= -github.com/Microsoft/hcsshim v0.12.0/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/aws/aws-sdk-go v1.50.31 h1:gx2NRLLEDUmQFC4YUsfMUKkGCwpXVO8ijUecq/nOQGA= github.com/aws/aws-sdk-go v1.50.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w= @@ -44,26 +42,28 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 h1:9/GylMS45hGGFCcMrUZDVayQ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1/go.mod h1:YjAPFn4kGFqKC54VsHs5fn5B6d+PCY2tziEa3U/GB5Y= github.com/aws/aws-sdk-go-v2/service/sts v1.28.2 h1:0YjXuWdYHvsm0HnT4vO8XpwG1D+i2roxSCBoN6deJ7M= github.com/aws/aws-sdk-go-v2/service/sts v1.28.2/go.mod h1:jI+FWmYkSMn+4APWmZiZTgt0oM0TrvymD51FMqCnWgA= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -80,8 +80,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -107,6 +105,8 @@ github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNe github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -141,6 +141,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -176,8 +178,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -186,13 +188,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -204,33 +204,30 @@ golang.org/x/sys v0.1.0/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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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 v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -242,5 +239,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/localstack/localstack.go b/modules/localstack/localstack.go index 4dbd08d53d..9754adba71 100644 --- a/modules/localstack/localstack.go +++ b/modules/localstack/localstack.go @@ -16,7 +16,6 @@ import ( const ( defaultPort = 4566 - defaultVersion = "1.4.0" hostnameExternalEnvVar = "HOSTNAME_EXTERNAL" localstackHostEnvVar = "LOCALSTACK_HOST" ) @@ -30,7 +29,7 @@ func isLegacyMode(image string) bool { } if !strings.HasPrefix(version, "v") { - version = fmt.Sprintf("v%s", version) + version = "v" + version } if semver.IsValid(version) { @@ -49,7 +48,7 @@ func isVersion2(image string) bool { } if !strings.HasPrefix(version, "v") { - version = fmt.Sprintf("v%s", version) + version = "v" + version } if semver.IsValid(version) { @@ -63,21 +62,27 @@ func isVersion2(image string) bool { // on that network to the given alias. // Deprecated: use network.WithNetwork or network.WithNewNetwork instead func WithNetwork(networkName string, alias string) testcontainers.CustomizeRequestOption { - return network.WithNewNetwork(context.Background(), []string{alias}, network.WithCheckDuplicate()) + return network.WithNewNetwork(context.Background(), []string{alias}) } -// RunContainer creates an instance of the LocalStack container type, being possible to pass a custom request and options: -// - overrideReq: a function that can be used to override the default container request, usually used to set the image version, environment variables for localstack, etc. +// Deprecated: use Run instead +// RunContainer creates an instance of the LocalStack container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*LocalStackContainer, error) { - dockerHost := testcontainers.ExtractDockerSocket() + return Run(ctx, "localstack/localstack:1.4.0", opts...) +} + +// Run creates an instance of the LocalStack container type +// - overrideReq: a function that can be used to override the default container request, usually used to set the image version, environment variables for localstack, etc. +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*LocalStackContainer, error) { + dockerHost := testcontainers.MustExtractDockerSocket(ctx) req := testcontainers.ContainerRequest{ - Image: fmt.Sprintf("localstack/localstack:%s", defaultVersion), + Image: img, WaitingFor: wait.ForHTTP("/_localstack/health").WithPort("4566/tcp").WithStartupTimeout(120 * time.Second), ExposedPorts: []string{fmt.Sprintf("%d/tcp", defaultPort)}, Env: map[string]string{}, HostConfigModifier: func(hostConfig *container.HostConfig) { - hostConfig.Binds = []string{fmt.Sprintf("%s:/var/run/docker.sock", dockerHost)} + hostConfig.Binds = []string{dockerHost + ":/var/run/docker.sock"} }, } @@ -111,13 +116,15 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize localStackReq.GenericContainerRequest.Logger.Printf("Setting %s to %s (%s)\n", envVar, req.Env[envVar], hostnameExternalReason) container, err := testcontainers.GenericContainer(ctx, localStackReq.GenericContainerRequest) - if err != nil { - return nil, err + var c *LocalStackContainer + if container != nil { + c = &LocalStackContainer{Container: container} } - c := &LocalStackContainer{ - Container: container, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } + return c, nil } diff --git a/modules/localstack/localstack_test.go b/modules/localstack/localstack_test.go index b2e3a39ffd..e9ad8c8330 100644 --- a/modules/localstack/localstack_test.go +++ b/modules/localstack/localstack_test.go @@ -2,13 +2,11 @@ package localstack import ( "context" - "fmt" "io" "strings" "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -43,7 +41,7 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "explicitly as environment variable", reason) + require.Equal(t, "explicitly as environment variable", reason) }) t.Run("HOSTNAME_EXTERNAL matches the last network alias on a container with non-default network", func(t *testing.T) { @@ -58,8 +56,8 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "to match last network alias on container with non-default network", reason) - assert.Equal(t, "foo3", req.Env[tt.envVar]) + require.Equal(t, "to match last network alias on container with non-default network", reason) + require.Equal(t, "foo3", req.Env[tt.envVar]) }) t.Run("HOSTNAME_EXTERNAL matches the daemon host because there are no aliases", func(t *testing.T) { @@ -78,8 +76,8 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "to match host-routable address for container", reason) - assert.Equal(t, expectedDaemonHost, req.Env[tt.envVar]) + require.Equal(t, "to match host-routable address for container", reason) + require.Equal(t, expectedDaemonHost, req.Env[tt.envVar]) }) } } @@ -101,8 +99,8 @@ func TestIsLegacyMode(t *testing.T) { for _, tt := range tests { t.Run(tt.version, func(t *testing.T) { - got := isLegacyMode(fmt.Sprintf("localstack/localstack:%s", tt.version)) - assert.Equal(t, tt.want, got, "runInLegacyMode() = %v, want %v", got, tt.want) + got := isLegacyMode("localstack/localstack:" + tt.version) + require.Equal(t, tt.want, got, "runInLegacyMode() = %v, want %v", got, tt.want) }) } } @@ -111,25 +109,28 @@ func TestRunContainer(t *testing.T) { tests := []struct { version string }{ - {defaultVersion}, + {"1.4.0"}, {"2.0.0"}, } for _, tt := range tests { ctx := context.Background() - container, err := RunContainer( + ctr, err := Run( ctx, - testcontainers.WithImage(fmt.Sprintf("localstack/localstack:%s", tt.version)), + "localstack/localstack:"+tt.version, ) + testcontainers.CleanupContainer(t, ctr) t.Run("Localstack:"+tt.version+" - multiple services exposed on same port", func(t *testing.T) { require.NoError(t, err) - assert.NotNil(t, container) + require.NotNil(t, ctr) - rawPorts, err := container.Ports(ctx) + inspect, err := ctr.Inspect(ctx) require.NoError(t, err) + rawPorts := inspect.NetworkSettings.Ports + ports := 0 // only one port is exposed among all the ports in the container for _, v := range rawPorts { @@ -138,7 +139,7 @@ func TestRunContainer(t *testing.T) { } } - assert.Equal(t, 1, ports) // a single port is exposed + require.Equal(t, 1, ports) // a single port is exposed }) } } @@ -146,9 +147,10 @@ func TestRunContainer(t *testing.T) { func TestStartWithoutOverride(t *testing.T) { ctx := context.Background() - container, err := RunContainer(ctx) + ctr, err := Run(ctx, "localstack/localstack:2.0.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - assert.NotNil(t, container) + require.NotNil(t, ctr) } func TestStartV2WithNetwork(t *testing.T) { @@ -156,15 +158,17 @@ func TestStartV2WithNetwork(t *testing.T) { nw, err := network.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, nw) - localstack, err := RunContainer( + localstack, err := Run( ctx, + "localstack/localstack:2.0.0", network.WithNetwork([]string{"localstack"}, nw), - testcontainers.WithImage("localstack/localstack:2.0.0"), testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), ) + testcontainers.CleanupContainer(t, localstack) require.NoError(t, err) - assert.NotNil(t, localstack) + require.NotNil(t, localstack) networkName := nw.Name @@ -195,6 +199,7 @@ func TestStartV2WithNetwork(t *testing.T) { }, Started: true, }) + testcontainers.CleanupContainer(t, cli) require.NoError(t, err) - assert.NotNil(t, cli) + require.NotNil(t, cli) } diff --git a/modules/localstack/v1/s3_test.go b/modules/localstack/v1/s3_test.go index 2ab12c704b..a35bbe98b2 100644 --- a/modules/localstack/v1/s3_test.go +++ b/modules/localstack/v1/s3_test.go @@ -62,10 +62,11 @@ func awsSession(ctx context.Context, l *localstack.LocalStackContainer) (*sessio func TestS3(t *testing.T) { ctx := context.Background() - container, err := localstack.RunContainer(ctx) + ctr, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - session, err := awsSession(ctx, container) + session, err := awsSession(ctx, ctr) require.NoError(t, err) s3Uploader := s3manager.NewUploader(session) @@ -81,7 +82,7 @@ func TestS3(t *testing.T) { Bucket: aws.String(bucketName), }) require.NoError(t, err) - assert.NotNil(t, outputBucket) + require.NotNil(t, outputBucket) // put object s3Key1 := "key1" @@ -95,15 +96,15 @@ func TestS3(t *testing.T) { ContentDisposition: aws.String("attachment"), }) require.NoError(t, err) - assert.NotNil(t, outputObject) + require.NotNil(t, outputObject) t.Run("List Buckets", func(t *testing.T) { output, err := s3API.ListBuckets(nil) require.NoError(t, err) - assert.NotNil(t, output) + require.NotNil(t, output) buckets := output.Buckets - assert.Len(t, buckets, 1) + require.Len(t, buckets, 1) assert.Equal(t, bucketName, *buckets[0].Name) }) @@ -112,11 +113,11 @@ func TestS3(t *testing.T) { Bucket: aws.String(bucketName), }) require.NoError(t, err) - assert.NotNil(t, output) + require.NotNil(t, output) objects := output.Contents - assert.Len(t, objects, 1) + require.Len(t, objects, 1) assert.Equal(t, s3Key1, *objects[0].Key) assert.Equal(t, int64(len(body1)), *objects[0].Size) }) diff --git a/modules/localstack/v2/s3_test.go b/modules/localstack/v2/s3_test.go index 6acb7240c4..2df71dcb39 100644 --- a/modules/localstack/v2/s3_test.go +++ b/modules/localstack/v2/s3_test.go @@ -3,13 +3,13 @@ package v2_test import ( "bytes" "context" - "fmt" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" + smithyendpoints "github.com/aws/smithy-go/endpoints" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,6 +25,20 @@ const ( region = "us-east-1" ) +// awsResolverV2 { +type resolverV2 struct { + // you could inject additional application context here as well +} + +func (*resolverV2) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) ( + smithyendpoints.Endpoint, error, +) { + // delegate back to the default v2 resolver otherwise + return s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params) +} + +// } + // awsSDKClientV2 { func s3Client(ctx context.Context, l *localstack.LocalStackContainer) (*s3.Client, error) { mappedPort, err := l.MappedPort(ctx, nat.Port("4566/tcp")) @@ -43,25 +57,18 @@ func s3Client(ctx context.Context, l *localstack.LocalStackContainer) (*s3.Clien return nil, err } - customResolver := aws.EndpointResolverWithOptionsFunc( - func(service, region string, opts ...interface{}) (aws.Endpoint, error) { - return aws.Endpoint{ - PartitionID: "aws", - URL: fmt.Sprintf("http://%s:%d", host, mappedPort.Int()), - SigningRegion: region, - }, nil - }) - awsCfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region), - config.WithEndpointResolverWithOptions(customResolver), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, token)), ) if err != nil { return nil, err } + // reference: https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/endpoints/#with-both client := s3.NewFromConfig(awsCfg, func(o *s3.Options) { + o.BaseEndpoint = aws.String("http://" + host + ":" + mappedPort.Port()) + o.EndpointResolverV2 = &resolverV2{} o.UsePathStyle = true }) @@ -73,10 +80,11 @@ func s3Client(ctx context.Context, l *localstack.LocalStackContainer) (*s3.Clien func TestS3(t *testing.T) { ctx := context.Background() - container, err := localstack.RunContainer(ctx) + ctr, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - s3Client, err := s3Client(ctx, container) + s3Client, err := s3Client(ctx, ctr) require.NoError(t, err) t.Run("S3 operations", func(t *testing.T) { @@ -87,7 +95,7 @@ func TestS3(t *testing.T) { Bucket: aws.String(bucketName), }) require.NoError(t, err) - assert.NotNil(t, outputBucket) + require.NotNil(t, outputBucket) // put object s3Key1 := "key1" @@ -101,15 +109,15 @@ func TestS3(t *testing.T) { ContentDisposition: aws.String("attachment"), }) require.NoError(t, err) - assert.NotNil(t, outputObject) + require.NotNil(t, outputObject) t.Run("List Buckets", func(t *testing.T) { output, err := s3Client.ListBuckets(ctx, &s3.ListBucketsInput{}) require.NoError(t, err) - assert.NotNil(t, output) + require.NotNil(t, output) buckets := output.Buckets - assert.Len(t, buckets, 1) + require.Len(t, buckets, 1) assert.Equal(t, bucketName, *buckets[0].Name) }) @@ -118,11 +126,11 @@ func TestS3(t *testing.T) { Bucket: aws.String(bucketName), }) require.NoError(t, err) - assert.NotNil(t, output) + require.NotNil(t, output) objects := output.Contents - assert.Len(t, objects, 1) + require.Len(t, objects, 1) assert.Equal(t, s3Key1, *objects[0].Key) assert.Equal(t, aws.Int64(int64(len(body1))), objects[0].Size) }) diff --git a/modules/mariadb/examples_test.go b/modules/mariadb/examples_test.go index 36209a2deb..59e168d3e4 100644 --- a/modules/mariadb/examples_test.go +++ b/modules/mariadb/examples_test.go @@ -10,33 +10,33 @@ import ( "github.com/testcontainers/testcontainers-go/modules/mariadb" ) -func ExampleRunContainer() { +func ExampleRun() { // runMariaDBContainer { ctx := context.Background() - mariadbContainer, err := mariadb.RunContainer(ctx, - testcontainers.WithImage("mariadb:11.0.3"), + mariadbContainer, err := mariadb.Run(ctx, + "mariadb:11.0.3", mariadb.WithConfigFile(filepath.Join("testdata", "my.cnf")), mariadb.WithScripts(filepath.Join("testdata", "schema.sql")), mariadb.WithDatabase("foo"), mariadb.WithUsername("root"), mariadb.WithPassword(""), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mariadbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mariadbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mariadbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/mariadb/go.mod b/modules/mariadb/go.mod index c74cf8f257..319e67d627 100644 --- a/modules/mariadb/go.mod +++ b/modules/mariadb/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/mariadb -go 1.21 +go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,11 +27,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -49,13 +53,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mariadb/go.sum b/modules/mariadb/go.sum index 757cde4b83..96aef09c5a 100644 --- a/modules/mariadb/go.sum +++ b/modules/mariadb/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -42,8 +43,6 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +53,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -124,23 +133,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -151,38 +156,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/mariadb/mariadb.go b/modules/mariadb/mariadb.go index a84410f262..4036cacc76 100644 --- a/modules/mariadb/mariadb.go +++ b/modules/mariadb/mariadb.go @@ -2,6 +2,7 @@ package mariadb import ( "context" + "errors" "fmt" "path/filepath" "strings" @@ -17,11 +18,6 @@ const ( defaultDatabaseName = "test" ) -// defaultImage { -const defaultImage = "mariadb:11.0.3" - -// } - // MariaDBContainer represents the MariaDB container type used in the module type MariaDBContainer struct { testcontainers.Container @@ -124,10 +120,16 @@ func WithScripts(scripts ...string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the MariaDB container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MariaDBContainer, error) { + return Run(ctx, "mariadb:11.0.3", opts...) +} + +// Run creates an instance of the MariaDB container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MariaDBContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "MARIADB_USER": defaultUser, @@ -164,29 +166,36 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize password := req.Env["MARIADB_PASSWORD"] if len(password) == 0 && password == "" && !strings.EqualFold(rootUser, username) { - return nil, fmt.Errorf("empty password can be used only with the root user") + return nil, errors.New("empty password can be used only with the root user") } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MariaDBContainer + if container != nil { + c = &MariaDBContainer{ + Container: container, + username: username, + password: password, + database: req.Env["MARIADB_DATABASE"], + } } - database := req.Env["MARIADB_DATABASE"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MariaDBContainer{container, username, password, database}, nil + return c, nil } // MustConnectionString panics if the address cannot be determined. func (c *MariaDBContainer) MustConnectionString(ctx context.Context, args ...string) string { - addr, err := c.ConnectionString(ctx,args...) + addr, err := c.ConnectionString(ctx, args...) if err != nil { panic(err) } return addr } - func (c *MariaDBContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { containerPort, err := c.MappedPort(ctx, "3306/tcp") if err != nil { diff --git a/modules/mariadb/mariadb_test.go b/modules/mariadb/mariadb_test.go index a520f25758..706ee2eb76 100644 --- a/modules/mariadb/mariadb_test.go +++ b/modules/mariadb/mariadb_test.go @@ -8,6 +8,7 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mariadb" @@ -16,220 +17,148 @@ import ( func TestMariaDB(t *testing.T) { ctx := context.Background() - container, err := mariadb.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { // By default, MariaDB transmits data between the server and clients without encrypting it. - connectionString, err := container.ConnectionString(ctx, "tls=false") + connectionString, err := ctr.ConnectionString(ctx, "tls=false") // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - mustConnectionString := container.MustConnectionString(ctx,"tls=false") - if mustConnectionString!=connectionString{ - t.Errorf("ConnectionString was not equal to MustConnectionString") - } + mustConnectionString := ctr.MustConnectionString(ctx, "tls=false") + require.Equal(t, connectionString, mustConnectionString) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMariaDBWithNonRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - _, err := mariadb.RunContainer(ctx, + _, err := mariadb.Run(ctx, + "mariadb:11.0.3", mariadb.WithDatabase("foo"), mariadb.WithUsername("test"), mariadb.WithPassword("")) - if err.Error() != "empty password can be used only with the root user" { - t.Fatal(err) - } + require.EqualError(t, err, "empty password can be used only with the root user") } func TestMariaDBWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := mariadb.RunContainer(ctx, + ctr, err := mariadb.Run(ctx, + "mariadb:11.0.3", mariadb.WithDatabase("foo"), mariadb.WithUsername("root"), mariadb.WithPassword("")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMariaDBWithMySQLEnvVars(t *testing.T) { ctx := context.Background() - container, err := mariadb.RunContainer(ctx, testcontainers.WithImage("mariadb:10.3.29"), + ctr, err := mariadb.Run(ctx, "mariadb:10.3.29", mariadb.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - assertDataCanBeFetched(t, ctx, container) + assertDataCanBeFetched(t, ctx, ctr) } func TestMariaDBWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := mariadb.RunContainer(ctx, testcontainers.WithImage("mariadb:11.0.3"), + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3", mariadb.WithConfigFile(filepath.Join("testdata", "my.cnf"))) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) // In MariaDB 10.2.2 and later, the default file format is Barracuda and Antelope is deprecated. // Barracuda is a newer InnoDB file format. It supports the COMPACT, REDUNDANT, DYNAMIC and // COMPRESSED row formats. Tables with large BLOB or TEXT columns in particular could benefit // from the dynamic row format. stmt, err := db.Prepare("SELECT @@GLOBAL.innodb_default_row_format") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer stmt.Close() row := stmt.QueryRow() innodbFileFormat := "" err = row.Scan(&innodbFileFormat) - if err != nil { - t.Errorf("error fetching innodb_default_row_format value") - } - if innodbFileFormat != "dynamic" { - t.Fatal("The InnoDB file format has been set by the ini file content") - } + require.NoError(t, err) + require.Equal(t, "dynamic", innodbFileFormat) } func TestMariaDBWithScripts(t *testing.T) { ctx := context.Background() - container, err := mariadb.RunContainer(ctx, + ctr, err := mariadb.Run(ctx, + "mariadb:11.0.3", mariadb.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - assertDataCanBeFetched(t, ctx, container) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + assertDataCanBeFetched(t, ctx, ctr) } func assertDataCanBeFetched(t *testing.T, ctx context.Context, container *mariadb.MariaDBContainer) { + t.Helper() connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer stmt.Close() + row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/meilisearch/Makefile b/modules/meilisearch/Makefile new file mode 100644 index 0000000000..d1554572d4 --- /dev/null +++ b/modules/meilisearch/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-meilisearch diff --git a/modules/meilisearch/examples_test.go b/modules/meilisearch/examples_test.go new file mode 100644 index 0000000000..5d41f23f6a --- /dev/null +++ b/modules/meilisearch/examples_test.go @@ -0,0 +1,45 @@ +package meilisearch_test + +import ( + "context" + "fmt" + "log" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/meilisearch" +) + +func ExampleRun() { + // runMeilisearchContainer { + ctx := context.Background() + + meiliContainer, err := meilisearch.Run( + ctx, + "getmeili/meilisearch:v1.10.3", + meilisearch.WithMasterKey("my-master-key"), + meilisearch.WithDumpImport("testdata/movies.dump"), + ) + defer func() { + if err := testcontainers.TerminateContainer(meiliContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := meiliContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + fmt.Printf("%s\n", meiliContainer.MasterKey()) + + // Output: + // true + // my-master-key +} diff --git a/modules/meilisearch/go.mod b/modules/meilisearch/go.mod new file mode 100644 index 0000000000..2ce7f8b066 --- /dev/null +++ b/modules/meilisearch/go.mod @@ -0,0 +1,63 @@ +module github.com/testcontainers/testcontainers-go/modules/meilisearch + +go 1.22 + +require ( + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // 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.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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.3.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // 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/klauspost/compress v1.17.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/time v0.7.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/meilisearch/go.sum b/modules/meilisearch/go.sum new file mode 100644 index 0000000000..58b56bee63 --- /dev/null +++ b/modules/meilisearch/go.sum @@ -0,0 +1,189 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/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 v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/meilisearch/meilisearch.go b/modules/meilisearch/meilisearch.go new file mode 100644 index 0000000000..687a1d61ca --- /dev/null +++ b/modules/meilisearch/meilisearch.go @@ -0,0 +1,118 @@ +package meilisearch + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + defaultMasterKey = "just-a-master-key-for-test" + defaultHTTPPort = "7700/tcp" + masterKeyEnvVar = "MEILI_MASTER_KEY" +) + +// MeilisearchContainer represents the Meilisearch container type used in the module +type MeilisearchContainer struct { + testcontainers.Container + masterKey string +} + +// MasterKey retrieves the master key of the Meilisearch container +func (c *MeilisearchContainer) MasterKey() string { + return c.masterKey +} + +// Run creates an instance of the Meilisearch container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MeilisearchContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{defaultHTTPPort}, + Env: map[string]string{ + masterKeyEnvVar: defaultMasterKey, + }, + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + // Gather all config options (defaults and then apply provided options) + settings := defaultOptions() + for _, opt := range opts { + if apply, ok := opt.(Option); ok { + apply(settings) + } + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, fmt.Errorf("customize: %w", err) + } + } + + if settings.DumpDataFilePath != "" { + genericContainerReq.Files = []testcontainers.ContainerFile{ + { + HostFilePath: settings.DumpDataFilePath, + ContainerFilePath: "/dumps/" + settings.DumpDataFileName, + FileMode: 0o755, + }, + } + genericContainerReq.Cmd = []string{"meilisearch", "--import-dump", "/dumps/" + settings.DumpDataFileName} + } + + // the wait strategy does not support TLS at the moment, + // so we need to disable it in the strategy for now. + genericContainerReq.WaitingFor = wait.ForHTTP("/health"). + WithPort(defaultHTTPPort). + WithTLS(false). + WithStartupTimeout(120 * time.Second). + WithStatusCodeMatcher(func(status int) bool { + return status == http.StatusOK + }). + WithResponseMatcher(func(body io.Reader) bool { + decoder := json.NewDecoder(body) + r := struct { + Status string `json:"status"` + }{} + if err := decoder.Decode(&r); err != nil { + return false + } + + return r.Status == "available" + }) + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MeilisearchContainer + if container != nil { + c = &MeilisearchContainer{Container: container, masterKey: req.Env[masterKeyEnvVar]} + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// Address retrieves the address of the Meilisearch container. +// It will use http as protocol, as TLS is not supported at the moment. +func (c *MeilisearchContainer) Address(ctx context.Context) (string, error) { + containerPort, err := c.MappedPort(ctx, defaultHTTPPort) + if err != nil { + return "", fmt.Errorf("mapped port: %w", err) + } + + host, err := c.Host(ctx) + if err != nil { + return "", fmt.Errorf("host: %w", err) + } + + return "http://" + net.JoinHostPort(host, containerPort.Port()), nil +} diff --git a/modules/meilisearch/meilisearch_test.go b/modules/meilisearch/meilisearch_test.go new file mode 100644 index 0000000000..5c351419e9 --- /dev/null +++ b/modules/meilisearch/meilisearch_test.go @@ -0,0 +1,83 @@ +package meilisearch_test + +import ( + "context" + "io" + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/meilisearch" +) + +func TestMeilisearch(t *testing.T) { + ctx := context.Background() + + ctr, err := meilisearch.Run(ctx, "getmeili/meilisearch:v1.10.3") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + address, err := ctr.Address(ctx) + require.NoError(t, err) + + req, err := http.NewRequest(http.MethodGet, address, nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + resp.Body.Close() +} + +func TestMeilisearch_WithDataDump(t *testing.T) { + ctx := context.Background() + + ctr, err := meilisearch.Run(ctx, "getmeili/meilisearch:v1.10.3", + meilisearch.WithDumpImport("testdata/movies.dump"), + meilisearch.WithMasterKey("my-master-key"), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + address, err := ctr.Address(ctx) + require.NoError(t, err) + + client := http.DefaultClient + + req, err := http.NewRequest(http.MethodGet, address, nil) + require.NoError(t, err) + + resp, err := client.Do(req) + require.NoError(t, err) + resp.Body.Close() // not closing the body in a defer as it's not used anymore + + require.EqualValues(t, http.StatusOK, resp.StatusCode) + + path, err := url.JoinPath(address, "/indexes/movies/documents/1212") + require.NoError(t, err) + + req, err = http.NewRequest(http.MethodGet, path, nil) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer my-master-key") + + resp, err = client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.EqualValues(t, http.StatusOK, resp.StatusCode) + + bodyBytes, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + // Assert the response of that document. + require.JSONEq(t, `{ + "movie_id": 1212, + "overview": "When a scientists daughter is kidnapped, American Ninja, attempts to find her, but this time he teams up with a youngster he has trained in the ways of the ninja.", + "poster": "https://image.tmdb.org/t/p/w1280/iuAQVI4mvjI83wnirpD8GVNRVuY.jpg", + "release_date": 725846400, + "title": "American Ninja 5" +}`, string(bodyBytes)) +} diff --git a/modules/meilisearch/options.go b/modules/meilisearch/options.go new file mode 100644 index 0000000000..06df8b4435 --- /dev/null +++ b/modules/meilisearch/options.go @@ -0,0 +1,46 @@ +package meilisearch + +import ( + "path/filepath" + + "github.com/testcontainers/testcontainers-go" +) + +// Options is a struct for specifying options for the Meilisearch container. +type Options struct { + DumpDataFilePath string + DumpDataFileName string +} + +func defaultOptions() *Options { + return &Options{} +} + +// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (*Option)(nil) + +// Option is an option for the Meilisearch container. +type Option func(*Options) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { + // NOOP to satisfy interface. + return nil +} + +// WithDumpImport sets the data dump file path for the Meilisearch container. +// dumpFilePath either relative to where you call meilisearch run or absolute path +func WithDumpImport(dumpFilePath string) Option { + return func(o *Options) { + o.DumpDataFilePath, o.DumpDataFileName = dumpFilePath, filepath.Base(dumpFilePath) + } +} + +// WithMasterKey sets the master key for the Meilisearch container +// it satisfies the testcontainers.ContainerCustomizer interface +func WithMasterKey(masterKey string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env["MEILI_MASTER_KEY"] = masterKey + return nil + } +} diff --git a/modules/meilisearch/testdata/movies.dump b/modules/meilisearch/testdata/movies.dump new file mode 100644 index 0000000000..f6b83419a8 Binary files /dev/null and b/modules/meilisearch/testdata/movies.dump differ diff --git a/modules/milvus/examples_test.go b/modules/milvus/examples_test.go index f4fab25376..a8242f15b2 100644 --- a/modules/milvus/examples_test.go +++ b/modules/milvus/examples_test.go @@ -12,26 +12,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/milvus" ) -func ExampleRunContainer() { +func ExampleRun() { // runMilvusContainer { ctx := context.Background() - milvusContainer, err := milvus.RunContainer(ctx, testcontainers.WithImage("milvusdb/milvus:v2.3.9")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + milvusContainer, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") defer func() { - if err := milvusContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(milvusContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := milvusContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -44,27 +44,28 @@ func ExampleMilvusContainer_collections() { // createCollections { ctx := context.Background() - milvusContainer, err := milvus.RunContainer(ctx, testcontainers.WithImage("milvusdb/milvus:v2.3.9")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + milvusContainer, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") defer func() { - if err := milvusContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(milvusContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } connectionStr, err := milvusContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } // Create a client to interact with the Milvus container milvusClient, err := client.NewGrpcClient(context.Background(), connectionStr) if err != nil { - log.Fatal("failed to connect to Milvus:", err.Error()) + log.Print("failed to connect to Milvus:", err.Error()) + return } defer milvusClient.Close() @@ -102,12 +103,14 @@ func ExampleMilvusContainer_collections() { 2, // shardNum ) if err != nil { - log.Fatalf("failed to create collection: %s", err) // nolint:gocritic + log.Printf("failed to create collection: %s", err) + return } list, err := milvusClient.ListCollections(context.Background()) if err != nil { - log.Fatalf("failed to list collections: %s", err) // nolint:gocritic + log.Printf("failed to list collections: %s", err) + return } // } diff --git a/modules/milvus/go.mod b/modules/milvus/go.mod index dd725046b5..53ac64489c 100644 --- a/modules/milvus/go.mod +++ b/modules/milvus/go.mod @@ -1,26 +1,28 @@ module github.com/testcontainers/testcontainers-go/modules/milvus -go 1.21 +go 1.22 require ( - github.com/milvus-io/milvus-sdk-go/v2 v2.3.6 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/milvus-io/milvus-sdk-go/v2 v2.4.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect github.com/cockroachdb/redact v1.1.3 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -32,12 +34,13 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.0 // indirect 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/milvus-io/milvus-proto/go-api/v2 v2.3.5 // indirect + github.com/milvus-io/milvus-proto/go-api/v2 v2.4.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -46,6 +49,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/rogpeppe/go-internal v1.8.1 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -61,15 +65,14 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/milvus/go.sum b/modules/milvus/go.sum index 0816cf1905..1fe9c4270a 100644 --- a/modules/milvus/go.sum +++ b/modules/milvus/go.sum @@ -10,10 +10,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -32,15 +30,17 @@ github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9D github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -50,10 +50,10 @@ 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/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -165,8 +165,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -195,12 +195,14 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.5 h1:4XDy6ATB2Z0fl4Jn0hS6BT6/8YaE0d+ZUf4uBH+Z0Do= -github.com/milvus-io/milvus-proto/go-api/v2 v2.3.5/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= -github.com/milvus-io/milvus-sdk-go/v2 v2.3.6 h1:JVn9OdaronLGmtpxvamQf523mtn3Z/CRxkSZCMWutV4= -github.com/milvus-io/milvus-sdk-go/v2 v2.3.6/go.mod h1:bYFSXVxEj6A/T8BfiR+xkofKbAVZpWiDvKr3SzYUWiA= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.0 h1:9KsyZR+neMlRvV52C5sIsLB13jthT2AY/+buyiNjcCg= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.0/go.mod h1:1OIl0v5PQeNxIJhCvY+K55CBUOYDZevw9g9380u1Wek= +github.com/milvus-io/milvus-sdk-go/v2 v2.4.0 h1:llESmiYiaFqRh0CUrZCLH0IWWkk5r8/vz0tkaA0YzQo= +github.com/milvus-io/milvus-sdk-go/v2 v2.4.0/go.mod h1:8IKyxVV+kd+RADMuMpo8GXnTDq5ZxrSSWpe9nJieboQ= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -268,8 +270,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -340,8 +343,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -351,8 +354,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -370,8 +371,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -380,8 +381,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -411,22 +410,22 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -440,8 +439,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK 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/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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= @@ -454,11 +451,11 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -466,8 +463,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f h1:rqzndB2lIQGivcXdTuY3Y9NBvr70X+y77woofSRluec= google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f/go.mod h1:gxndsbNG1n4TZcHGgsYEfVGnTxqfEdfiDv6/DADXX9o= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -486,6 +483,8 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= @@ -500,7 +499,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/modules/milvus/milvus.go b/modules/milvus/milvus.go index a33ae27935..b35cc99335 100644 --- a/modules/milvus/milvus.go +++ b/modules/milvus/milvus.go @@ -6,7 +6,7 @@ import ( _ "embed" "fmt" "html/template" - "os" + "io" "time" "github.com/testcontainers/testcontainers-go" @@ -16,7 +16,11 @@ import ( //go:embed mounts/embedEtcd.yaml.tpl var embedEtcdConfigTpl string -const embedEtcdContainerPath string = "/milvus/configs/embedEtcd.yaml" +const ( + embedEtcdContainerPath = "/milvus/configs/embedEtcd.yaml" + defaultClientPort = 2379 + grpcPort = "19530/tcp" +) // MilvusContainer represents the Milvus container type used in the module type MilvusContainer struct { @@ -30,17 +34,28 @@ func (c *MilvusContainer) ConnectionString(ctx context.Context) (string, error) if err != nil { return "", err } - port, err := c.MappedPort(ctx, "19530/tcp") + port, err := c.MappedPort(ctx, grpcPort) if err != nil { return "", err } return fmt.Sprintf("%s:%s", host, port.Port()), nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Milvus container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MilvusContainer, error) { + return Run(ctx, "milvusdb/milvus:v2.3.9", opts...) +} + +// Run creates an instance of the Milvus container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MilvusContainer, error) { + config, err := renderEmbedEtcdConfig(defaultClientPort) + if err != nil { + return nil, fmt.Errorf("render config: %w", err) + } + req := testcontainers.ContainerRequest{ - Image: "milvusdb/milvus:v2.3.9", + Image: img, ExposedPorts: []string{"19530/tcp", "9091/tcp", "2379/tcp"}, Env: map[string]string{ "ETCD_USE_EMBED": "true", @@ -48,16 +63,13 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize "ETCD_CONFIG_PATH": embedEtcdContainerPath, "COMMON_STORAGETYPE": "local", }, - Cmd: []string{"milvus", "run", "standalone"}, - WaitingFor: wait.ForHTTP("/healthz").WithPort("9091").WithStartupTimeout(60 * time.Second).WithPollInterval(30 * time.Second), - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ - { - PostCreates: []testcontainers.ContainerHook{ - // Copy the default embed etcd config to container after it's created. - // Otherwise the milvus container will panic on startup. - createDefaultEmbedEtcdConfig, - }, - }, + Cmd: []string{"milvus", "run", "standalone"}, + WaitingFor: wait.ForHTTP("/healthz"). + WithPort("9091"). + WithStartupTimeout(time.Minute). + WithPollInterval(time.Second), + Files: []testcontainers.ContainerFile{ + {ContainerFilePath: embedEtcdContainerPath, Reader: config}, }, } @@ -73,18 +85,25 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MilvusContainer + if container != nil { + c = &MilvusContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &MilvusContainer{Container: container}, nil + return c, nil } type embedEtcdConfigTplParams struct { Port int } -func renderEmbedEtcdConfig(port int) ([]byte, error) { +// renderEmbedEtcdConfig renders the embed etcd config template with the given port +// and returns it as an io.Reader. +func renderEmbedEtcdConfig(port int) (io.Reader, error) { tplParams := embedEtcdConfigTplParams{ Port: port, } @@ -99,34 +118,5 @@ func renderEmbedEtcdConfig(port int) ([]byte, error) { return nil, fmt.Errorf("failed to render embed etcd config template: %w", err) } - return embedEtcdYaml.Bytes(), nil -} - -// createDefaultEmbedEtcdConfig creates a default embed etcd config file, -// using the default port 2379 as the advertised port. The file is then copied to the container. -func createDefaultEmbedEtcdConfig(ctx context.Context, c testcontainers.Container) error { - // Otherwise the milvus container will panic on startup. - defaultEmbedEtcdConfig, err := renderEmbedEtcdConfig(2379) - if err != nil { - return fmt.Errorf("failed to render default config: %w", err) - } - - tmpDir := os.TempDir() - defaultEmbedEtcdConfigPath := fmt.Sprintf("%s/embedEtcd.yaml", tmpDir) - - if err := os.WriteFile(defaultEmbedEtcdConfigPath, defaultEmbedEtcdConfig, 0o644); err != nil { - return fmt.Errorf("failed to write default embed etcd config to a temporary dir: %w", err) - } - - if err != nil { - return fmt.Errorf("can't create default embed etcd config: %w", err) - } - defer os.Remove(defaultEmbedEtcdConfigPath) - - err = c.CopyFileToContainer(ctx, defaultEmbedEtcdConfigPath, embedEtcdContainerPath, 0o644) - if err != nil { - return fmt.Errorf("can't copy %s to container: %w", defaultEmbedEtcdConfigPath, err) - } - - return nil + return &embedEtcdYaml, nil } diff --git a/modules/milvus/milvus_test.go b/modules/milvus/milvus_test.go index 43465ffa77..c1ad0a070e 100644 --- a/modules/milvus/milvus_test.go +++ b/modules/milvus/milvus_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/milvus-io/milvus-sdk-go/v2/client" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/milvus" @@ -13,36 +14,23 @@ import ( func TestMilvus(t *testing.T) { ctx := context.Background() - container, err := milvus.RunContainer(ctx, testcontainers.WithImage("milvusdb/milvus:v2.3.9")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("Connect to Milvus with gRPC", func(tt *testing.T) { // connectionString { - connectionStr, err := container.ConnectionString(ctx) + connectionStr, err := ctr.ConnectionString(ctx) // } - if err != nil { - tt.Fatal(err) - } + require.NoError(t, err) milvusClient, err := client.NewGrpcClient(context.Background(), connectionStr) - if err != nil { - tt.Fatal("failed to connect to Milvus:", err.Error()) - } + require.NoError(t, err) + defer milvusClient.Close() v, err := milvusClient.GetVersion(ctx) - if err != nil { - tt.Fatal("failed to get version:", err.Error()) - } + require.NoError(t, err) tt.Logf("Milvus version: %s", v) }) diff --git a/modules/minio/examples_test.go b/modules/minio/examples_test.go index ce72eb515a..a1e50b6c84 100644 --- a/modules/minio/examples_test.go +++ b/modules/minio/examples_test.go @@ -9,26 +9,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/minio" ) -func ExampleRunContainer() { +func ExampleRun() { // runMinioContainer { ctx := context.Background() - minioContainer, err := minio.RunContainer(ctx, testcontainers.WithImage("minio/minio:RELEASE.2024-01-16T16-07-38Z")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + minioContainer, err := minio.Run(ctx, "minio/minio:RELEASE.2024-01-16T16-07-38Z") defer func() { - if err := minioContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(minioContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := minioContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/minio/go.mod b/modules/minio/go.mod index 5c74f092c3..53e4caa164 100644 --- a/modules/minio/go.mod +++ b/modules/minio/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/minio -go 1.21 +go 1.22 require ( github.com/minio/minio-go/v7 v7.0.68 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -26,15 +28,16 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect + 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/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -45,6 +48,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/rs/xid v1.5.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -57,17 +61,14 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/minio/go.sum b/modules/minio/go.sum index 5bf4678e44..f1ac4ddea7 100644 --- a/modules/minio/go.sum +++ b/modules/minio/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -42,8 +43,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -62,6 +61,10 @@ github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -72,6 +75,8 @@ github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aO github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -97,6 +102,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -110,6 +117,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -144,23 +153,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -172,40 +177,39 @@ golang.org/x/sys v0.5.0/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/minio/minio.go b/modules/minio/minio.go index b81586934a..6547a9003b 100644 --- a/modules/minio/minio.go +++ b/modules/minio/minio.go @@ -2,6 +2,7 @@ package minio import ( "context" + "errors" "fmt" "github.com/testcontainers/testcontainers-go" @@ -11,7 +12,6 @@ import ( const ( defaultUser = "minioadmin" defaultPassword = "minioadmin" - defaultImage = "docker.io/minio/minio:RELEASE.2024-01-16T16-07-38Z" ) // MinioContainer represents the Minio container type used in the module @@ -57,10 +57,16 @@ func (c *MinioContainer) ConnectionString(ctx context.Context) (string, error) { return fmt.Sprintf("%s:%s", host, port.Port()), nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Minio container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) { + return Run(ctx, "minio/minio:RELEASE.2024-01-16T16-07-38Z", opts...) +} + +// Run creates an instance of the Minio container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MinioContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"9000/tcp"}, WaitingFor: wait.ForHTTP("/minio/health/live").WithPort("9000"), Env: map[string]string{ @@ -84,13 +90,18 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize username := req.Env["MINIO_ROOT_USER"] password := req.Env["MINIO_ROOT_PASSWORD"] if username == "" || password == "" { - return nil, fmt.Errorf("username or password has not been set") + return nil, errors.New("username or password has not been set") } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MinioContainer + if container != nil { + c = &MinioContainer{Container: container, Username: username, Password: password} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &MinioContainer{Container: container, Username: username, Password: password}, nil + return c, nil } diff --git a/modules/minio/minio_test.go b/modules/minio/minio_test.go index 0cb5a7fc2e..d8ca857cb3 100644 --- a/modules/minio/minio_test.go +++ b/modules/minio/minio_test.go @@ -8,6 +8,7 @@ import ( "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" tcminio "github.com/testcontainers/testcontainers-go/modules/minio" @@ -16,44 +17,30 @@ import ( func TestMinio(t *testing.T) { ctx := context.Background() - container, err := tcminio.RunContainer(ctx, - testcontainers.WithImage("minio/minio:RELEASE.2024-01-16T16-07-38Z"), + ctr, err := tcminio.Run(ctx, + "minio/minio:RELEASE.2024-01-16T16-07-38Z", tcminio.WithUsername("thisismyuser"), tcminio.WithPassword("thisismypassword")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - url, err := container.ConnectionString(ctx) + url, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) minioClient, err := minio.New(url, &minio.Options{ - Creds: credentials.NewStaticV4(container.Username, container.Password, ""), + Creds: credentials.NewStaticV4(ctr.Username, ctr.Password, ""), Secure: false, }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) bucketName := "testcontainers" location := "eu-west-2" // create bucket err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) objectName := "testdata" contentType := "applcation/octet-stream" @@ -61,23 +48,15 @@ func TestMinio(t *testing.T) { contentLength := int64(len(content)) uploadInfo, err := minioClient.PutObject(ctx, bucketName, objectName, strings.NewReader(content), contentLength, minio.PutObjectOptions{ContentType: contentType}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // object is a readSeekCloser object, err := minioClient.GetObject(ctx, uploadInfo.Bucket, uploadInfo.Key, minio.GetObjectOptions{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer object.Close() n, err := io.Copy(io.Discard, object) - if err != nil { - t.Fatal(err) - } - - if n != contentLength { - t.Fatalf("expected %d; got %d", contentLength, n) - } + require.NoError(t, err) + require.Equal(t, contentLength, n) } diff --git a/modules/mockserver/examples_test.go b/modules/mockserver/examples_test.go index 9125114375..a93c8bcbf0 100644 --- a/modules/mockserver/examples_test.go +++ b/modules/mockserver/examples_test.go @@ -14,26 +14,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/mockserver" ) -func ExampleRunContainer() { +func ExampleRun() { // runMockServerContainer { ctx := context.Background() - mockserverContainer, err := mockserver.RunContainer(ctx, testcontainers.WithImage("mockserver/mockserver:5.15.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + mockserverContainer, err := mockserver.Run(ctx, "mockserver/mockserver:5.15.0") defer func() { - if err := mockserverContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mockserverContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mockserverContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -42,25 +42,25 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { // connectToMockServer { ctx := context.Background() - mockserverContainer, err := mockserver.RunContainer(ctx, testcontainers.WithImage("mockserver/mockserver:5.15.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + mockserverContainer, err := mockserver.Run(ctx, "mockserver/mockserver:5.15.0") defer func() { - if err := mockserverContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mockserverContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } url, err := mockserverContainer.URL(ctx) if err != nil { - log.Fatalf("failed to get container URL: %s", err) // nolint:gocritic + log.Printf("failed to get container URL: %s", err) + return } ms := client.NewClientURL(url) // } @@ -72,18 +72,21 @@ func ExampleRunContainer_connect() { requestMatcher = requestMatcher.WithJSONFields(map[string]interface{}{"name": "Tools"}) err = ms.RegisterExpectation(client.NewExpectation(requestMatcher).WithResponse(client.NewResponseOK().WithJSONBody(map[string]any{"test": "value"}))) if err != nil { - log.Fatalf("failed to register expectation: %s", err) + log.Printf("failed to register expectation: %s", err) + return } httpClient := &http.Client{} resp, err := httpClient.Post(url+"/api/categories", "application/json", strings.NewReader(`{"name": "Tools"}`)) if err != nil { - log.Fatalf("failed to send request: %s", err) + log.Printf("failed to send request: %s", err) + return } buf, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("failed to read response: %s", err) + log.Printf("failed to read response: %s", err) + return } resp.Body.Close() diff --git a/modules/mockserver/go.mod b/modules/mockserver/go.mod index ce76725644..2c67de0eb5 100644 --- a/modules/mockserver/go.mod +++ b/modules/mockserver/go.mod @@ -1,23 +1,24 @@ module github.com/testcontainers/testcontainers-go/modules/mockserver -go 1.21 +go 1.22 require ( github.com/BraspagDevelopers/mock-server-client v0.2.2 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -26,11 +27,12 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-resty/resty/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -39,10 +41,12 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -50,14 +54,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mockserver/go.sum b/modules/mockserver/go.sum index 8d2cc3600c..5588ff118a 100644 --- a/modules/mockserver/go.sum +++ b/modules/mockserver/go.sum @@ -6,27 +6,28 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BraspagDevelopers/mock-server-client v0.2.2 h1:Zro0OonNeaDwkkQGIxeJQfYweNKZ+m+8QIlDZAFRc/4= github.com/BraspagDevelopers/mock-server-client v0.2.2/go.mod h1:LHulrZSfbCNeS/CoycaWdhE495FnyeI3iXm6+4Zjz5c= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -44,8 +45,6 @@ github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8 github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -56,12 +55,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -82,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -93,6 +100,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -126,24 +135,20 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -155,38 +160,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/mockserver/mockserver.go b/modules/mockserver/mockserver.go index e3bb5fbd28..b53f164e86 100644 --- a/modules/mockserver/mockserver.go +++ b/modules/mockserver/mockserver.go @@ -8,22 +8,25 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -// defaultImage is the default MockServer container image -const defaultImage = "mockserver/mockserver:5.15.0" - // MockServerContainer represents the MockServer container type used in the module type MockServerContainer struct { testcontainers.Container } +// Deprecated: use Run instead // RunContainer creates an instance of the MockServer container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MockServerContainer, error) { + return Run(ctx, "mockserver/mockserver:5.15.0", opts...) +} + +// Run creates an instance of the MockServer container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MockServerContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"1080/tcp"}, WaitingFor: wait.ForAll( wait.ForLog("started on port: 1080"), - wait.ForListeningPort("1080/tcp"), + wait.ForListeningPort("1080/tcp").SkipInternalCheck(), ), Env: map[string]string{}, } @@ -40,11 +43,15 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MockServerContainer + if container != nil { + c = &MockServerContainer{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &MockServerContainer{Container: container}, nil + return c, nil } // GetURL returns the URL of the MockServer container diff --git a/modules/mongodb/cli.go b/modules/mongodb/cli.go new file mode 100644 index 0000000000..f990bf17c8 --- /dev/null +++ b/modules/mongodb/cli.go @@ -0,0 +1,32 @@ +package mongodb + +import "fmt" + +// mongoCli is cli to interact with MongoDB. If username and password are provided +// it will use credentials to authenticate. +type mongoCli struct { + mongoshBaseCmd string + mongoBaseCmd string +} + +func newMongoCli(username string, password string) mongoCli { + authArgs := "" + if username != "" && password != "" { + authArgs = fmt.Sprintf("--username %s --password %s", username, password) + } + + return mongoCli{ + mongoshBaseCmd: fmt.Sprintf("mongosh %s --quiet", authArgs), + mongoBaseCmd: fmt.Sprintf("mongo %s --quiet", authArgs), + } +} + +func (m mongoCli) eval(command string, args ...any) []string { + command = "\"" + fmt.Sprintf(command, args...) + "\"" + + return []string{ + "sh", + "-c", + m.mongoshBaseCmd + " --eval " + command + " || " + m.mongoBaseCmd + " --eval " + command, + } +} diff --git a/modules/mongodb/examples_test.go b/modules/mongodb/examples_test.go index 8e936a7c64..98a31d61fa 100644 --- a/modules/mongodb/examples_test.go +++ b/modules/mongodb/examples_test.go @@ -14,26 +14,26 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -func ExampleRunContainer() { +func ExampleRun() { // runMongoDBContainer { ctx := context.Background() - mongodbContainer, err := mongodb.RunContainer(ctx, testcontainers.WithImage("mongo:6")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + mongodbContainer, err := mongodb.Run(ctx, "mongo:6") defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mongodbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mongodbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -42,36 +42,38 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { // connectToMongo { ctx := context.Background() - mongodbContainer, err := mongodb.RunContainer(ctx, testcontainers.WithImage("mongo:6")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + mongodbContainer, err := mongodb.Run(ctx, "mongo:6") defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mongodbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := mongodbContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint)) if err != nil { - log.Fatalf("failed to connect to MongoDB: %s", err) + log.Printf("failed to connect to MongoDB: %s", err) + return } // } err = mongoClient.Ping(ctx, nil) if err != nil { - log.Fatalf("failed to ping MongoDB: %s", err) + log.Printf("failed to ping MongoDB: %s", err) + return } fmt.Println(mongoClient.Database("test").Name()) @@ -80,39 +82,41 @@ func ExampleRunContainer_connect() { // test } -func ExampleRunContainer_withCredentials() { +func ExampleRun_withCredentials() { ctx := context.Background() - container, err := mongodb.RunContainer(ctx, - testcontainers.WithImage("mongo:6"), + ctr, err := mongodb.Run(ctx, + "mongo:6", mongodb.WithUsername("root"), mongodb.WithPassword("password"), testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - connStr, err := container.ConnectionString(ctx) + connStr, err := ctr.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(connStr)) if err != nil { - log.Fatalf("failed to connect to MongoDB: %s", err) + log.Printf("failed to connect to MongoDB: %s", err) + return } err = mongoClient.Ping(ctx, nil) if err != nil { - log.Fatalf("failed to ping MongoDB: %s", err) + log.Printf("failed to ping MongoDB: %s", err) + return } fmt.Println(strings.Split(connStr, "@")[0]) diff --git a/modules/mongodb/go.mod b/modules/mongodb/go.mod index c9672f27ce..18d13a4cae 100644 --- a/modules/mongodb/go.mod +++ b/modules/mongodb/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/mongodb -go 1.21 +go 1.22 require ( - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 go.mongodb.org/mongo-driver v1.13.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,12 +27,13 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -55,15 +59,14 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mongodb/go.sum b/modules/mongodb/go.sum index bfdc9013ad..2b2714addd 100644 --- a/modules/mongodb/go.sum +++ b/modules/mongodb/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -56,12 +55,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9K 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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -84,6 +89,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -95,6 +102,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -141,13 +150,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -155,14 +162,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -178,45 +185,44 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/mongodb/mongodb.go b/modules/mongodb/mongodb.go index 565e8bc466..b2fa8bb023 100644 --- a/modules/mongodb/mongodb.go +++ b/modules/mongodb/mongodb.go @@ -1,27 +1,44 @@ package mongodb import ( + "bytes" "context" + _ "embed" + "errors" "fmt" + "time" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// defaultImage is the default MongoDB container image -const defaultImage = "mongo:6" +//go:embed mount/entrypoint-tc.sh +var entrypointContent []byte + +const ( + entrypointPath = "/tmp/entrypoint-tc.sh" + keyFilePath = "/tmp/mongo_keyfile" + replicaSetOptEnvKey = "testcontainers.mongodb.replicaset_name" +) // MongoDBContainer represents the MongoDB container type used in the module type MongoDBContainer struct { testcontainers.Container - username string - password string + username string + password string + replicaSet string } +// Deprecated: use Run instead // RunContainer creates an instance of the MongoDB container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MongoDBContainer, error) { + return Run(ctx, "mongo:6", opts...) +} + +// Run creates an instance of the MongoDB container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MongoDBContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"27017/tcp"}, WaitingFor: wait.ForAll( wait.ForLog("Waiting for connections"), @@ -43,18 +60,27 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize username := req.Env["MONGO_INITDB_ROOT_USERNAME"] password := req.Env["MONGO_INITDB_ROOT_PASSWORD"] if username != "" && password == "" || username == "" && password != "" { - return nil, fmt.Errorf("if you specify username or password, you must provide both of them") + return nil, errors.New("if you specify username or password, you must provide both of them") + } + + replicaSet := req.Env[replicaSetOptEnvKey] + if replicaSet != "" { + if err := configureRequestForReplicaset(username, password, replicaSet, &genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MongoDBContainer + if container != nil { + c = &MongoDBContainer{Container: container, username: username, password: password, replicaSet: replicaSet} } - if username != "" && password != "" { - return &MongoDBContainer{Container: container, username: username, password: password}, nil + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return &MongoDBContainer{Container: container}, nil + + return c, nil } // WithUsername sets the initial username to be created when the container starts @@ -79,6 +105,15 @@ func WithPassword(password string) testcontainers.CustomizeRequestOption { } } +// WithReplicaSet sets the replica set name for Single node MongoDB replica set. +func WithReplicaSet(replSetName string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[replicaSetOptEnvKey] = replSetName + + return nil + } +} + // ConnectionString returns the connection string for the MongoDB container. // If you provide a username and a password, the connection string will also include them. func (c *MongoDBContainer) ConnectionString(ctx context.Context) (string, error) { @@ -95,3 +130,81 @@ func (c *MongoDBContainer) ConnectionString(ctx context.Context) (string, error) } return c.Endpoint(ctx, "mongodb") } + +func setupEntrypointForAuth(req *testcontainers.GenericContainerRequest) { + req.Files = append( + req.Files, testcontainers.ContainerFile{ + Reader: bytes.NewReader(entrypointContent), + ContainerFilePath: entrypointPath, + FileMode: 0o755, + }, + ) + req.Entrypoint = []string{entrypointPath} + req.Env["MONGO_KEYFILE"] = keyFilePath +} + +func configureRequestForReplicaset( + username string, + password string, + replicaSet string, + genericContainerReq *testcontainers.GenericContainerRequest, +) error { + if !(username != "" && password != "") { + return noAuthReplicaSet(replicaSet)(genericContainerReq) + } + + return withAuthReplicaset(replicaSet, username, password)(genericContainerReq) +} + +func noAuthReplicaSet(replSetName string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + cli := newMongoCli("", "") + req.Cmd = append(req.Cmd, "--replSet", replSetName) + initiateReplicaSet(req, cli, replSetName) + + return nil + } +} + +func initiateReplicaSet(req *testcontainers.GenericContainerRequest, cli mongoCli, replSetName string) { + req.WaitingFor = wait.ForAll( + req.WaitingFor, + wait.ForExec(cli.eval("rs.status().ok")), + ).WithDeadline(60 * time.Second) + + req.LifecycleHooks = append( + req.LifecycleHooks, testcontainers.ContainerLifecycleHooks{ + PostStarts: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { + ip, err := c.ContainerIP(ctx) + if err != nil { + return fmt.Errorf("container ip: %w", err) + } + + cmd := cli.eval( + "rs.initiate({ _id: '%s', members: [ { _id: 0, host: '%s:27017' } ] })", + replSetName, + ip, + ) + + return wait.ForExec(cmd).WaitUntilReady(ctx, c) + }, + }, + }, + ) +} + +func withAuthReplicaset( + replSetName string, + username string, + password string, +) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + setupEntrypointForAuth(req) + cli := newMongoCli(username, password) + req.Cmd = append(req.Cmd, "--replSet", replSetName, "--keyFile", keyFilePath) + initiateReplicaSet(req, cli, replSetName) + + return nil + } +} diff --git a/modules/mongodb/mongodb_test.go b/modules/mongodb/mongodb_test.go index 994b4c448c..8cc49e629c 100644 --- a/modules/mongodb/mongodb_test.go +++ b/modules/mongodb/mongodb_test.go @@ -2,9 +2,10 @@ package mongodb_test import ( "context" - "log" "testing" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -14,60 +15,128 @@ import ( func TestMongoDB(t *testing.T) { type tests struct { - name string - image string + name string + img string + opts []testcontainers.ContainerCustomizer } testCases := []tests{ { - name: "From Docker Hub", - image: "mongo:6", + name: "From Docker Hub", + img: "mongo:6", + opts: []testcontainers.ContainerCustomizer{}, }, { - name: "Community Server", - image: "mongodb/mongodb-community-server:7.0.2-ubi8", + name: "Community Server", + img: "mongodb/mongodb-community-server:7.0.2-ubi8", + opts: []testcontainers.ContainerCustomizer{}, }, { - name: "Enterprise Server", - image: "mongodb/mongodb-enterprise-server:7.0.0-ubi8", + name: "Enterprise Server", + img: "mongodb/mongodb-enterprise-server:7.0.0-ubi8", + opts: []testcontainers.ContainerCustomizer{}, + }, + { + name: "with-replica/mongo:4", + img: "mongo:4", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + }, + }, + { + name: "with-replica/mongo:6", + img: "mongo:6", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + }, + }, + { + name: "with-replica/mongo:7", + img: "mongo:7", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + }, + }, + { + name: "with-auth/replica/mongo:7", + img: "mongo:7", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, + }, + { + name: "with-auth/replica/mongo:6", + img: "mongo:6", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, + }, + { + name: "with-auth/mongo:6", + img: "mongo:6", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, + }, + { + name: "with-auth/replica/mongodb-enterprise-server:7.0.0-ubi8", + img: "mongodb/mongodb-enterprise-server:7.0.0-ubi8", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, + }, + { + name: "with-auth/replica/mongodb-community-server:7.0.2-ubi8", + img: "mongodb/mongodb-community-server:7.0.2-ubi8", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, + }, + { + name: "with-auth/replica/mongo:4", + img: "mongo:4", + opts: []testcontainers.ContainerCustomizer{ + mongodb.WithReplicaSet("rs"), + mongodb.WithUsername("tester"), + mongodb.WithPassword("testerpass"), + }, }, } for _, tc := range testCases { - image := tc.image - t.Run(image, func(t *testing.T) { - t.Parallel() + tc := tc + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() ctx := context.Background() - mongodbContainer, err := mongodb.RunContainer(ctx, testcontainers.WithImage(image)) - if err != nil { - t.Fatalf("failed to start container: %s", err) - } - - defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }() + mongodbContainer, err := mongodb.Run(ctx, tc.img, tc.opts...) + testcontainers.CleanupContainer(t, mongodbContainer) + require.NoError(tt, err) endpoint, err := mongodbContainer.ConnectionString(ctx) - if err != nil { - t.Fatalf("failed to get connection string: %s", err) - } + require.NoError(tt, err) - mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint)) - if err != nil { - t.Fatalf("failed to connect to MongoDB: %s", err) - } + // Force direct connection to the container to avoid the replica set + // connection string that is returned by the container itself when + // using the replica set option. + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint).SetDirect(true)) + require.NoError(tt, err) err = mongoClient.Ping(ctx, nil) - if err != nil { - log.Fatalf("failed to ping MongoDB: %s", err) - } + require.NoError(tt, err) + require.Equal(t, "test", mongoClient.Database("test").Name()) - if mongoClient.Database("test").Name() != "test" { - t.Fatalf("failed to connect to the correct database") - } + _, err = mongoClient.Database("testcontainer").Collection("test").InsertOne(context.Background(), bson.M{}) + require.NoError(tt, err) }) } } diff --git a/modules/mongodb/mount/entrypoint-tc.sh b/modules/mongodb/mount/entrypoint-tc.sh new file mode 100644 index 0000000000..1561415aad --- /dev/null +++ b/modules/mongodb/mount/entrypoint-tc.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -Eeuo pipefail + +# detect mongo user and group +function get_user_group() { + user_group=$(cut -d: -f1,5 /etc/passwd | grep mongo) + echo "${user_group}" +} + +# detect the entrypoint +function get_entrypoint() { + entrypoint=$(find /usr/local/bin -name 'docker-entrypoint.*') + if [[ "${entrypoint}" == *.py ]]; then + entrypoint="python3 ${entrypoint}" + else + entrypoint="exec ${entrypoint}" + fi + echo "${entrypoint}" +} + +ENTRYPOINT=$(get_entrypoint) +MONGO_USER_GROUP=$(get_user_group) + +# Create the keyfile +openssl rand -base64 756 > "${MONGO_KEYFILE}" + +# Set the permissions and ownership of the keyfile +chown "${MONGO_USER_GROUP}" "${MONGO_KEYFILE}" +chmod 400 "${MONGO_KEYFILE}" + +${ENTRYPOINT} "$@" diff --git a/modules/mssql/examples_test.go b/modules/mssql/examples_test.go index 2d7af8f0c7..8c1f2c0cac 100644 --- a/modules/mssql/examples_test.go +++ b/modules/mssql/examples_test.go @@ -9,32 +9,32 @@ import ( "github.com/testcontainers/testcontainers-go/modules/mssql" ) -func ExampleRunContainer() { +func ExampleRun() { // runMSSQLServerContainer { ctx := context.Background() password := "SuperStrong@Passw0rd" - mssqlContainer, err := mssql.RunContainer(ctx, - testcontainers.WithImage("mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04"), + mssqlContainer, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", mssql.WithAcceptEULA(), mssql.WithPassword(password), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mssqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mssqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mssqlContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/mssql/go.mod b/modules/mssql/go.mod index 50b42ba97f..93217a8b80 100644 --- a/modules/mssql/go.mod +++ b/modules/mssql/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/mssql -go 1.21 +go 1.22 require ( github.com/microsoft/go-mssqldb v1.7.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -27,11 +29,12 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -51,14 +55,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mssql/go.sum b/modules/mssql/go.sum index 6d06f5c352..a0079e350c 100644 --- a/modules/mssql/go.sum +++ b/modules/mssql/go.sum @@ -16,27 +16,28 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -58,8 +59,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -70,8 +69,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -80,6 +83,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs= github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -102,6 +107,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -113,6 +120,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -146,23 +155,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -173,38 +178,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/mssql/mssql.go b/modules/mssql/mssql.go index 78321beab3..17337bf85b 100644 --- a/modules/mssql/mssql.go +++ b/modules/mssql/mssql.go @@ -10,7 +10,6 @@ import ( ) const ( - defaultImage = "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04" defaultPort = "1433/tcp" defaultUsername = "sa" // default microsoft system administrator defaultPassword = "Strong@Passw0rd" @@ -42,10 +41,16 @@ func WithPassword(password string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the MSSQLServer container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MSSQLServerContainer, error) { + return Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", opts...) +} + +// Run creates an instance of the MSSQLServer container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MSSQLServerContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{defaultPort}, Env: map[string]string{ "MSSQL_SA_PASSWORD": defaultPassword, @@ -65,14 +70,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MSSQLServerContainer + if container != nil { + c = &MSSQLServerContainer{Container: container, password: req.Env["MSSQL_SA_PASSWORD"], username: defaultUsername} } - username := defaultUsername - password := req.Env["MSSQL_SA_PASSWORD"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MSSQLServerContainer{Container: container, password: password, username: username}, nil + return c, nil } func (c *MSSQLServerContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { diff --git a/modules/mssql/mssql_test.go b/modules/mssql/mssql_test.go index b691935eaa..737c97414e 100644 --- a/modules/mssql/mssql_test.go +++ b/modules/mssql/mssql_test.go @@ -6,6 +6,7 @@ import ( "testing" _ "github.com/microsoft/go-mssqldb" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mssql" @@ -15,59 +16,45 @@ import ( func TestMSSQLServer(t *testing.T) { ctx := context.Background() - container, err := mssql.RunContainer(ctx, + ctr, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", mssql.WithAcceptEULA(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) _, err = db.Exec("CREATE TABLE a_table ( " + " [col_1] NVARCHAR(128) NOT NULL, " + " [col_2] NVARCHAR(128) NOT NULL, " + " PRIMARY KEY ([col_1], [col_2]) " + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMSSQLServerWithMissingEulaOption(t *testing.T) { ctx := context.Background() - container, err := mssql.RunContainer(ctx, testcontainers.WithWaitStrategy( - wait.ForLog("The SQL Server End-User License Agreement (EULA) must be accepted"))) - if err != nil { - t.Fatalf("Expected a log to confirm missing EULA but got error: %s", err) - } + ctr, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", + testcontainers.WithWaitStrategy( + wait.ForLog("The SQL Server End-User License Agreement (EULA) must be accepted")), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - state, err := container.State(ctx) - if err != nil { - t.Fatalf("failed to get container state: %s", err) - } + state, err := ctr.State(ctx) + require.NoError(t, err) if !state.Running { t.Log("Success: Confirmed proper handling of missing EULA, so container is not running.") @@ -77,137 +64,67 @@ func TestMSSQLServerWithMissingEulaOption(t *testing.T) { func TestMSSQLServerWithConnectionStringParameters(t *testing.T) { ctx := context.Background() - container, err := mssql.RunContainer(ctx, + ctr, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", mssql.WithAcceptEULA(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx, "encrypt=false", "TrustServerCertificate=true") - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx, "encrypt=false", "TrustServerCertificate=true") + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) _, err = db.Exec("CREATE TABLE a_table ( " + " [col_1] NVARCHAR(128) NOT NULL, " + " [col_2] NVARCHAR(128) NOT NULL, " + " PRIMARY KEY ([col_1], [col_2]) " + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMSSQLServerWithCustomStrongPassword(t *testing.T) { ctx := context.Background() - container, err := mssql.RunContainer(ctx, + ctr, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", mssql.WithAcceptEULA(), mssql.WithPassword("Strong@Passw0rd"), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) } // tests that a weak password is not accepted by the container due to Microsoft's password strength policy func TestMSSQLServerWithInvalidPassword(t *testing.T) { ctx := context.Background() - container, err := mssql.RunContainer(ctx, + ctr, err := mssql.Run(ctx, + "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", testcontainers.WithWaitStrategy( wait.ForLog("Password validation failed")), mssql.WithAcceptEULA(), mssql.WithPassword("weakPassword"), ) - - if err == nil { - t.Log("Success: Received invalid password validation docker log.") - } else { - t.Fatalf("Expected a password validation log but got error: %s", err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) -} - -func TestMSSQLServerWithAlternativeImage(t *testing.T) { - ctx := context.Background() - - container, err := mssql.RunContainer(ctx, - testcontainers.WithImage("mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04"), - mssql.WithAcceptEULA(), - ) - if err != nil { - t.Fatalf("Failed to create the container with alternative image: %s", err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } - - db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } diff --git a/modules/mysql/examples_test.go b/modules/mysql/examples_test.go index a6be342643..61ee33113d 100644 --- a/modules/mysql/examples_test.go +++ b/modules/mysql/examples_test.go @@ -11,33 +11,33 @@ import ( "github.com/testcontainers/testcontainers-go/modules/mysql" ) -func ExampleRunContainer() { +func ExampleRun() { // runMySQLContainer { ctx := context.Background() - mysqlContainer, err := mysql.RunContainer(ctx, - testcontainers.WithImage("mysql:8.0.36"), + mysqlContainer, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithConfigFile(filepath.Join("testdata", "my_8.cnf")), mysql.WithDatabase("foo"), mysql.WithUsername("root"), mysql.WithPassword("password"), mysql.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mysqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mysqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mysqlContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -46,48 +46,56 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { ctx := context.Background() - mysqlContainer, err := mysql.RunContainer(ctx, - testcontainers.WithImage("mysql:8.0.36"), + mysqlContainer, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithConfigFile(filepath.Join("testdata", "my_8.cnf")), mysql.WithDatabase("foo"), mysql.WithUsername("root"), mysql.WithPassword("password"), mysql.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := mysqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mysqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - connectionString, _ := mysqlContainer.ConnectionString(ctx) + connectionString, err := mysqlContainer.ConnectionString(ctx) + if err != nil { + log.Printf("failed to get connection string: %s", err) + return + } db, err := sql.Open("mysql", connectionString) if err != nil { - log.Fatalf("failed to connect to MySQL: %s", err) // nolint:gocritic + log.Printf("failed to connect to MySQL: %s", err) + return } defer db.Close() if err = db.Ping(); err != nil { - log.Fatalf("failed to ping MySQL: %s", err) + log.Printf("failed to ping MySQL: %s", err) + return } stmt, err := db.Prepare("SELECT @@GLOBAL.tmpdir") if err != nil { - log.Fatalf("failed to prepare statement: %s", err) + log.Printf("failed to prepare statement: %s", err) + return } defer stmt.Close() row := stmt.QueryRow() tmpDir := "" err = row.Scan(&tmpDir) if err != nil { - log.Fatalf("failed to scan row: %s", err) + log.Printf("failed to scan row: %s", err) + return } fmt.Println(tmpDir) diff --git a/modules/mysql/go.mod b/modules/mysql/go.mod index 2f86863ef6..a6984fead4 100644 --- a/modules/mysql/go.mod +++ b/modules/mysql/go.mod @@ -1,24 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/mysql -go 1.21 +go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -26,11 +28,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -50,13 +54,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mysql/go.sum b/modules/mysql/go.sum index 757cde4b83..96aef09c5a 100644 --- a/modules/mysql/go.sum +++ b/modules/mysql/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -42,8 +43,6 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +53,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -124,23 +133,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -151,38 +156,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/mysql/mysql.go b/modules/mysql/mysql.go index c4e2bd0b7e..44c3688ab2 100644 --- a/modules/mysql/mysql.go +++ b/modules/mysql/mysql.go @@ -2,6 +2,7 @@ package mysql import ( "context" + "errors" "fmt" "path/filepath" "strings" @@ -17,11 +18,6 @@ const ( defaultDatabaseName = "test" ) -// defaultImage { -const defaultImage = "mysql:8.0.36" - -// } - // MySQLContainer represents the MySQL container type used in the module type MySQLContainer struct { testcontainers.Container @@ -48,10 +44,16 @@ func WithDefaultCredentials() testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the MySQL container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*MySQLContainer, error) { + return Run(ctx, "mysql:8.0.36", opts...) +} + +// Run creates an instance of the MySQL container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*MySQLContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ "MYSQL_USER": defaultUser, @@ -81,22 +83,30 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize password := req.Env["MYSQL_PASSWORD"] if len(password) == 0 && password == "" && !strings.EqualFold(rootUser, username) { - return nil, fmt.Errorf("empty password can be used only with the root user") + return nil, errors.New("empty password can be used only with the root user") } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MySQLContainer + if container != nil { + c = &MySQLContainer{ + Container: container, + password: password, + username: username, + database: req.Env["MYSQL_DATABASE"], + } } - database := req.Env["MYSQL_DATABASE"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MySQLContainer{container, username, password, database}, nil + return c, nil } // MustConnectionString panics if the address cannot be determined. func (c *MySQLContainer) MustConnectionString(ctx context.Context, args ...string) string { - addr, err := c.ConnectionString(ctx,args...) + addr, err := c.ConnectionString(ctx, args...) if err != nil { panic(err) } diff --git a/modules/mysql/mysql_test.go b/modules/mysql/mysql_test.go index cee869f494..364f2a97a8 100644 --- a/modules/mysql/mysql_test.go +++ b/modules/mysql/mysql_test.go @@ -8,148 +8,110 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mysql" ) func TestMySQL(t *testing.T) { ctx := context.Background() - container, err := mysql.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := mysql.Run(ctx, "mysql:8.0.36") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - connectionString, err := container.ConnectionString(ctx, "tls=skip-verify") + connectionString, err := ctr.ConnectionString(ctx, "tls=skip-verify") // } - if err != nil { - t.Fatal(err) - } - mustConnectionString := container.MustConnectionString(ctx,"tls=skip-verify") - if mustConnectionString!=connectionString{ - t.Errorf("ConnectionString was not equal to MustConnectionString") - } + require.NoError(t, err) + + mustConnectionString := ctr.MustConnectionString(ctx, "tls=skip-verify") + require.Equal(t, connectionString, mustConnectionString) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMySQLWithNonRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - _, err := mysql.RunContainer(ctx, + ctr, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithDatabase("foo"), mysql.WithUsername("test"), mysql.WithPassword("")) - if err.Error() != "empty password can be used only with the root user" { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.EqualError(t, err, "empty password can be used only with the root user") } func TestMySQLWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := mysql.RunContainer(ctx, + ctr, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithDatabase("foo"), mysql.WithUsername("root"), mysql.WithPassword("")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, _ := container.ConnectionString(ctx) + connectionString, _ := ctr.ConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMySQLWithScripts(t *testing.T) { ctx := context.Background() - container, err := mysql.RunContainer(ctx, + ctr, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, _ := container.ConnectionString(ctx) + connectionString, _ := ctr.ConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer stmt.Close() + row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/nats/examples_test.go b/modules/nats/examples_test.go index fd95691cc4..b88fba4c4a 100644 --- a/modules/nats/examples_test.go +++ b/modules/nats/examples_test.go @@ -4,35 +4,35 @@ import ( "context" "fmt" "log" + "time" natsgo "github.com/nats-io/nats.go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/nats" + "github.com/testcontainers/testcontainers-go/network" ) -func ExampleRunContainer() { +func ExampleRun() { // runNATSContainer { ctx := context.Background() - natsContainer, err := nats.RunContainer(ctx, - testcontainers.WithImage("nats:2.9"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + natsContainer, err := nats.Run(ctx, "nats:2.9") defer func() { - if err := natsContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(natsContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := natsContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -41,30 +41,31 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connectWithCredentials() { +func ExampleRun_connectWithCredentials() { // natsConnect { ctx := context.Background() - container, err := nats.RunContainer(ctx, nats.WithUsername("foo"), nats.WithPassword("bar")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + ctr, err := nats.Run(ctx, "nats:2.9", nats.WithUsername("foo"), nats.WithPassword("bar")) defer func() { - if err := container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - uri, err := container.ConnectionString(ctx) + uri, err := ctr.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } - nc, err := natsgo.Connect(uri, natsgo.UserInfo(container.User, container.Password)) + nc, err := natsgo.Connect(uri, natsgo.UserInfo(ctr.User, ctr.Password)) if err != nil { - log.Fatalf("failed to connect to NATS: %s", err) + log.Printf("failed to connect to NATS: %s", err) + return } defer nc.Close() // } @@ -74,3 +75,179 @@ func ExampleRunContainer_connectWithCredentials() { // Output: // true } + +func ExampleRun_cluster() { + ctx := context.Background() + + nwr, err := network.New(ctx) + if err != nil { + log.Printf("failed to create network: %s", err) + return + } + + defer func() { + if err := nwr.Remove(context.Background()); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + + // withArguments { + natsContainer1, err := nats.Run(ctx, + "nats:2.9", + network.WithNetwork([]string{"nats1"}, nwr), + nats.WithArgument("name", "nats1"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats1:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + // } + defer func() { + if err := testcontainers.TerminateContainer(natsContainer1); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + natsContainer2, err := nats.Run(ctx, + "nats:2.9", + network.WithNetwork([]string{"nats2"}, nwr), + nats.WithArgument("name", "nats2"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats2:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + defer func() { + if err := testcontainers.TerminateContainer(natsContainer2); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + natsContainer3, err := nats.Run(ctx, + "nats:2.9", + network.WithNetwork([]string{"nats3"}, nwr), + nats.WithArgument("name", "nats3"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats3:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + defer func() { + if err := testcontainers.TerminateContainer(natsContainer3); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // cluster URL + servers := natsContainer1.MustConnectionString(ctx) + "," + natsContainer2.MustConnectionString(ctx) + "," + natsContainer3.MustConnectionString(ctx) + + nc, err := natsgo.Connect(servers, natsgo.MaxReconnects(5), natsgo.ReconnectWait(2*time.Second)) + if err != nil { + log.Printf("connecting to nats container failed:\n\t%v\n", err) + return + } + + // Close connection + defer nc.Close() + + { + // Simple Publisher + err = nc.Publish("foo", []byte("Hello World")) + if err != nil { + log.Printf("failed to publish message: %s", err) + return + } + } + + { + // Channel subscriber + ch := make(chan *natsgo.Msg, 64) + sub, err := nc.ChanSubscribe("channel", ch) + if err != nil { + log.Printf("failed to subscribe to message: %s", err) + return + } + + // Request + err = nc.Publish("channel", []byte("Hello NATS Cluster!")) + if err != nil { + log.Printf("failed to publish message: %s", err) + return + } + + msg := <-ch + fmt.Println(string(msg.Data)) + + err = sub.Unsubscribe() + if err != nil { + log.Printf("failed to unsubscribe: %s", err) + return + } + + err = sub.Drain() + if err != nil { + log.Printf("failed to drain: %s", err) + return + } + } + + { + // Responding to a request message + sub, err := nc.Subscribe("request", func(m *natsgo.Msg) { + err1 := m.Respond([]byte("answer is 42")) + if err1 != nil { + log.Printf("failed to respond to message: %s", err1) + return + } + }) + if err != nil { + log.Printf("failed to subscribe to message: %s", err) + return + } + + // Request + msg, err := nc.Request("request", []byte("what is the answer?"), 1*time.Second) + if err != nil { + log.Printf("failed to send request: %s", err) + return + } + + fmt.Println(string(msg.Data)) + + err = sub.Unsubscribe() + if err != nil { + log.Printf("failed to unsubscribe: %s", err) + return + } + + err = sub.Drain() + if err != nil { + log.Printf("failed to drain: %s", err) + return + } + } + + // Drain connection (Preferred for responders) + // Close() not needed if this is called. + err = nc.Drain() + if err != nil { + log.Printf("failed to drain connection: %s", err) + return + } + + // Output: + // Hello NATS Cluster! + // answer is 42 +} diff --git a/modules/nats/go.mod b/modules/nats/go.mod index 3cd41fc6e5..d055e61dd1 100644 --- a/modules/nats/go.mod +++ b/modules/nats/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/nats -go 1.21 +go 1.22 require ( github.com/nats-io/nats.go v1.33.1 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,11 +27,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -51,13 +55,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/nats/go.sum b/modules/nats/go.sum index d15245be01..bb69595f80 100644 --- a/modules/nats/go.sum +++ b/modules/nats/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -84,6 +89,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -95,6 +102,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -128,23 +137,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -155,38 +160,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/nats/nats.go b/modules/nats/nats.go index 3204f57796..cd040c09e2 100644 --- a/modules/nats/nats.go +++ b/modules/nats/nats.go @@ -21,10 +21,16 @@ type NATSContainer struct { Password string } +// Deprecated: use Run instead // RunContainer creates an instance of the NATS container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*NATSContainer, error) { + return Run(ctx, "nats:2.9", opts...) +} + +// Run creates an instance of the NATS container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*NATSContainer, error) { req := testcontainers.ContainerRequest{ - Image: "nats:2.9", + Image: img, ExposedPorts: []string{defaultClientPort, defaultRoutingPort, defaultMonitoringPort}, Cmd: []string{"-DV", "-js"}, WaitingFor: wait.ForLog("Listening for client connections on 0.0.0.0:4222"), @@ -53,21 +59,24 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *NATSContainer + if container != nil { + c = &NATSContainer{ + Container: container, + User: settings.CmdArgs["user"], + Password: settings.CmdArgs["pass"], + } } - natsContainer := NATSContainer{ - Container: container, - User: settings.CmdArgs["user"], - Password: settings.CmdArgs["pass"], + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return &natsContainer, nil + return c, nil } func (c *NATSContainer) MustConnectionString(ctx context.Context, args ...string) string { - addr, err := c.ConnectionString(ctx,args...) + addr, err := c.ConnectionString(ctx, args...) if err != nil { panic(err) } diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index 2fa6a7bf37..ad777c9556 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -1,11 +1,16 @@ package nats_test import ( + "bufio" "context" + "strings" "testing" + "time" "github.com/nats-io/nats.go" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tcnats "github.com/testcontainers/testcontainers-go/modules/nats" ) @@ -13,69 +18,89 @@ func TestNATS(t *testing.T) { ctx := context.Background() // createNATSContainer { - container, err := tcnats.RunContainer(ctx) + ctr, err := tcnats.Run(ctx, "nats:2.9") // } - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - uri, err := container.ConnectionString(ctx) + uri, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatalf("failed to get connection string: %s", err) - } - mustUri := container.MustConnectionString(ctx) - if mustUri!=uri{ - t.Errorf("URI was not equal to MustUri") - } + require.NoError(t, err) + mustUri := ctr.MustConnectionString(ctx) + require.Equal(t, mustUri, uri) // perform assertions nc, err := nats.Connect(uri) - if err != nil { - t.Fatalf("failed to connect to nats: %s", err) - } + require.NoError(t, err) defer nc.Close() js, err := nc.JetStream() - if err != nil { - t.Fatalf("failed to create jetstream context: %s", err) - } + require.NoError(t, err) // add stream to nats - if _, err = js.AddStream(&nats.StreamConfig{ + _, err = js.AddStream(&nats.StreamConfig{ Name: "hello", Subjects: []string{"hello"}, - }); err != nil { - t.Fatalf("failed to add stream: %s", err) - } + }) + require.NoError(t, err) // add subscriber to nats sub, err := js.SubscribeSync("hello", nats.Durable("worker")) - if err != nil { - t.Fatalf("failed to subscribe to hello: %s", err) - } + require.NoError(t, err) // publish a message to nats - if _, err = js.Publish("hello", []byte("hello")); err != nil { - t.Fatalf("failed to publish hello: %s", err) - } + _, err = js.Publish("hello", []byte("hello")) + require.NoError(t, err) // wait for the message to be received msg, err := sub.NextMsgWithContext(ctx) - if err != nil { - t.Fatalf("failed to get message: %s", err) - } + require.NoError(t, err) + + require.Equal(t, "hello", string(msg.Data)) +} + +func TestNATSWithConfigFile(t *testing.T) { + const natsConf = ` +listen: 0.0.0.0:4222 +authorization { + token: "s3cr3t" +} +` + ctx := context.Background() - if string(msg.Data) != "hello" { - t.Fatalf("expected message to be 'hello', got '%s'", msg.Data) + ctr, err := tcnats.Run(ctx, "nats:2.9", tcnats.WithConfigFile(strings.NewReader(natsConf))) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + uri, err := ctr.ConnectionString(ctx) + require.NoError(t, err) + + // connect without a correct token must fail + mallory, err := nats.Connect(uri, nats.Name("Mallory"), nats.Token("secret")) + t.Cleanup(mallory.Close) + require.EqualError(t, err, "nats: Authorization Violation") + + // connect with a correct token must succeed + nc, err := nats.Connect(uri, nats.Name("API Token Test"), nats.Token("s3cr3t")) + t.Cleanup(nc.Close) + require.NoError(t, err) + + // validate /etc/nats.conf mentioned in logs + const expected = "Using configuration file: /etc/nats.conf" + logs, err := ctr.Logs(ctx) + require.NoError(t, err) + sc := bufio.NewScanner(logs) + found := false + time.AfterFunc(5*time.Second, func() { + require.Truef(t, found, "expected log line not found after 5 seconds: %s", expected) + }) + for sc.Scan() { + if strings.Contains(sc.Text(), expected) { + found = true + break + } } + require.Truef(t, found, "expected log line not found: %s", expected) } diff --git a/modules/nats/options.go b/modules/nats/options.go index 38856d68a9..60abd10056 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -1,6 +1,7 @@ package nats import ( + "io" "strings" "github.com/testcontainers/testcontainers-go" @@ -16,7 +17,7 @@ func defaultOptions() options { } } -// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +// Compiler check to ensure that CmdOption implements the testcontainers.ContainerCustomizer interface. var _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) // CmdOption is an option for the NATS container. @@ -49,3 +50,22 @@ func WithArgument(flag string, value string) CmdOption { o.CmdArgs[flag] = value } } + +// WithConfigFile pass a content of io.Reader to the NATS container as /etc/nats.conf +// Changing the connectivity (listen address or ports) can break the container setup. +func WithConfigFile(config io.Reader) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + if config != nil { + req.Cmd = append(req.Cmd, "-config", "/etc/nats.conf") + req.Files = append( + req.Files, + testcontainers.ContainerFile{ + Reader: config, + ContainerFilePath: "/etc/nats.conf", + FileMode: 0o644, + }, + ) + } + return nil + } +} diff --git a/modules/neo4j/config.go b/modules/neo4j/config.go index 91eb3e415b..aff88d4f20 100644 --- a/modules/neo4j/config.go +++ b/modules/neo4j/config.go @@ -35,7 +35,7 @@ func WithAdminPassword(adminPassword string) testcontainers.CustomizeRequestOpti return func(req *testcontainers.GenericContainerRequest) error { pwd := "none" if adminPassword != "" { - pwd = fmt.Sprintf("neo4j/%s", adminPassword) + pwd = "neo4j/" + adminPassword } req.Env["NEO4J_AUTH"] = pwd @@ -127,7 +127,7 @@ func validate(req *testcontainers.GenericContainerRequest) error { func formatNeo4jConfig(name string) string { result := strings.ReplaceAll(name, "_", "__") result = strings.ReplaceAll(result, ".", "_") - return fmt.Sprintf("NEO4J_%s", result) + return "NEO4J_" + result } // WithAcceptCommercialLicenseAgreement sets the environment variable diff --git a/modules/neo4j/examples_test.go b/modules/neo4j/examples_test.go index fcddfa7c41..51b11bf49a 100644 --- a/modules/neo4j/examples_test.go +++ b/modules/neo4j/examples_test.go @@ -9,33 +9,33 @@ import ( "github.com/testcontainers/testcontainers-go/modules/neo4j" ) -func ExampleRunContainer() { +func ExampleRun() { // runNeo4jContainer { ctx := context.Background() testPassword := "letmein!" - neo4jContainer, err := neo4j.RunContainer(ctx, - testcontainers.WithImage("docker.io/neo4j:4.4"), + neo4jContainer, err := neo4j.Run(ctx, + "neo4j:4.4", neo4j.WithAdminPassword(testPassword), neo4j.WithLabsPlugin(neo4j.Apoc), neo4j.WithNeo4jSetting("dbms.tx_log.rotation.size", "42M"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := neo4jContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(neo4jContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := neo4jContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/neo4j/go.mod b/modules/neo4j/go.mod index 86968c5de4..a9ca222a30 100644 --- a/modules/neo4j/go.mod +++ b/modules/neo4j/go.mod @@ -1,35 +1,38 @@ module github.com/testcontainers/testcontainers-go/modules/neo4j -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/neo4j/neo4j-go-driver/v5 v5.18.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -49,13 +53,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/neo4j/go.sum b/modules/neo4j/go.sum index 121b33e952..b7d1b4f201 100644 --- a/modules/neo4j/go.sum +++ b/modules/neo4j/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -124,23 +133,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -151,38 +156,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/neo4j/neo4j.go b/modules/neo4j/neo4j.go index 70566aa0cd..ed9579d46d 100644 --- a/modules/neo4j/neo4j.go +++ b/modules/neo4j/neo4j.go @@ -11,13 +11,6 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -const ( - // defaultImage { - defaultImageName = "neo4j" - defaultTag = "4.4" - // } -) - const ( // containerPorts { defaultBoltPort = "7687" @@ -48,18 +41,24 @@ func (c Neo4jContainer) BoltUrl(ctx context.Context) (string, error) { return fmt.Sprintf("neo4j://%s:%d", host, mappedPort.Int()), nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Neo4j container type -func RunContainer(ctx context.Context, options ...testcontainers.ContainerCustomizer) (*Neo4jContainer, error) { +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Neo4jContainer, error) { + return Run(ctx, "neo4j:4.4", opts...) +} + +// Run creates an instance of the Neo4j container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Neo4jContainer, error) { httpPort, _ := nat.NewPort("tcp", defaultHttpPort) request := testcontainers.ContainerRequest{ - Image: fmt.Sprintf("docker.io/%s:%s", defaultImageName, defaultTag), + Image: img, Env: map[string]string{ "NEO4J_AUTH": "none", }, ExposedPorts: []string{ - fmt.Sprintf("%s/tcp", defaultBoltPort), - fmt.Sprintf("%s/tcp", defaultHttpPort), - fmt.Sprintf("%s/tcp", defaultHttpsPort), + defaultBoltPort + "/tcp", + defaultHttpPort + "/tcp", + defaultHttpsPort + "/tcp", }, WaitingFor: &wait.MultiStrategy{ Strategies: []wait.Strategy{ @@ -78,11 +77,11 @@ func RunContainer(ctx context.Context, options ...testcontainers.ContainerCustom Started: true, } - if len(options) == 0 { - options = append(options, WithoutAuthentication()) + if len(opts) == 0 { + opts = append(opts, WithoutAuthentication()) } - for _, option := range options { + for _, option := range opts { if err := option.Customize(&genericContainerReq); err != nil { return nil, err } @@ -94,11 +93,16 @@ func RunContainer(ctx context.Context, options ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Neo4jContainer + if container != nil { + c = &Neo4jContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &Neo4jContainer{Container: container}, nil + return c, nil } func isHttpOk() func(status int) bool { diff --git a/modules/neo4j/neo4j_test.go b/modules/neo4j/neo4j_test.go index 23717d4585..51a01817ce 100644 --- a/modules/neo4j/neo4j_test.go +++ b/modules/neo4j/neo4j_test.go @@ -3,11 +3,11 @@ package neo4j_test import ( "context" "fmt" - "io" "strings" "testing" neo "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/neo4j" @@ -20,43 +20,33 @@ func TestNeo4j(outer *testing.T) { ctx := context.Background() - container := setupNeo4j(ctx, outer) - - outer.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := setupNeo4j(ctx) + testcontainers.CleanupContainer(outer, ctr) + require.NoError(outer, err) outer.Run("connects via Bolt", func(t *testing.T) { - driver := createDriver(t, ctx, container) + driver := createDriver(t, ctx, ctr) err := driver.VerifyConnectivity(ctx) - if err != nil { - t.Fatalf("should have successfully connected to server but did not: %s", err) - } + require.NoErrorf(t, err, "should have successfully connected to server but did not") }) outer.Run("exercises APOC plugin", func(t *testing.T) { - driver := createDriver(t, ctx, container) + driver := createDriver(t, ctx, ctr) result, err := neo.ExecuteQuery(ctx, driver, "RETURN apoc.number.arabicToRoman(1986) AS output", nil, neo.EagerResultTransformer) - if err != nil { - t.Fatalf("expected APOC query to successfully run but did not: %s", err) - } - if value, _ := result.Records[0].Get("output"); value != "MCMLXXXVI" { - t.Fatalf("did not get expected roman number: %s", value) - } + require.NoErrorf(t, err, "expected APOC query to successfully run but did not") + require.NotEmpty(t, result.Records) + value, _ := result.Records[0].Get("output") + require.Equalf(t, "MCMLXXXVI", value, "did not get expected roman number: %s", value) }) outer.Run("is configured with custom Neo4j settings", func(t *testing.T) { - env := getContainerEnv(t, ctx, container) + env := getContainerEnv(t, ctx, ctr) - if !strings.Contains(env, "NEO4J_dbms_tx__log_rotation_size=42M") { - t.Fatal("expected to custom setting to be exported but was not") - } + require.Containsf(t, env, "NEO4J_dbms_tx__log_rotation_size=42M", "expected to custom setting to be exported but was not") }) } @@ -66,34 +56,25 @@ func TestNeo4jWithEnterpriseLicense(t *testing.T) { ctx := context.Background() images := map[string]string{ - "StandardEdition": "docker.io/neo4j:4.4", - "EnterpriseEdition": "docker.io/neo4j:4.4-enterprise", + "StandardEdition": "neo4j:4.4", + "EnterpriseEdition": "neo4j:4.4-enterprise", } - for edition, image := range images { - edition, image := edition, image + for edition, img := range images { + edition, img := edition, img t.Run(edition, func(t *testing.T) { t.Parallel() - container, err := neo4j.RunContainer(ctx, - testcontainers.WithImage(image), + ctr, err := neo4j.Run(ctx, + img, neo4j.WithAdminPassword(testPassword), neo4j.WithAcceptCommercialLicenseAgreement(), ) - if err != nil { - t.Fatalf("expected container to successfully initialize but did not: %s", err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - env := getContainerEnv(t, ctx, container) + env := getContainerEnv(t, ctx, ctr) - if !strings.Contains(env, "NEO4J_ACCEPT_LICENSE_AGREEMENT=yes") { - t.Fatal("expected to accept license agreement but did not") - } + require.Containsf(t, env, "NEO4J_ACCEPT_LICENSE_AGREEMENT=yes", "expected to accept license agreement but did not") }) } } @@ -104,34 +85,27 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { ctx := context.Background() outer.Run("without authentication", func(t *testing.T) { - container, err := neo4j.RunContainer(ctx) - if err != nil { - t.Fatalf("expected env to successfully run but did not: %s", err) - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := neo4j.Run(ctx, "neo4j:4.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) }) outer.Run("auth setting outside WithAdminPassword raises error", func(t *testing.T) { - container, err := neo4j.RunContainer(ctx, + ctr, err := neo4j.Run(ctx, + "neo4j:4.4", neo4j.WithAdminPassword(testPassword), neo4j.WithNeo4jSetting("AUTH", "neo4j/thisisgonnafail"), ) - if err == nil { - t.Fatalf("expected env to fail due to conflicting auth settings but did not") - } - if container != nil { - t.Fatalf("container must not be created with conflicting auth settings") - } + testcontainers.CleanupContainer(t, ctr) + require.Errorf(t, err, "expected env to fail due to conflicting auth settings but did not") + require.Nilf(t, ctr, "container must not be created with conflicting auth settings") }) outer.Run("warns about overwrites of setting keys", func(t *testing.T) { // withSettings { logger := &inMemoryLogger{} - container, err := neo4j.RunContainer(ctx, + ctr, err := neo4j.Run(ctx, + "neo4j:4.4", neo4j.WithLogger(logger), // needs to go before WithNeo4jSetting and WithNeo4jSettings neo4j.WithAdminPassword(testPassword), neo4j.WithNeo4jSetting("some.key", "value1"), @@ -139,83 +113,52 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { neo4j.WithNeo4jSetting("some.key", "value3"), ) // } - if err != nil { - t.Fatalf("expected env to successfully run but did not: %s", err) - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) errorLogs := logger.Logs() - if !Contains(errorLogs, `setting "some.key" with value "value1" is now overwritten with value "value2"`+"\n") || - !Contains(errorLogs, `setting "some.key" with value "value2" is now overwritten with value "value3"`+"\n") { - t.Fatalf("expected setting overwrites to be logged") - } - if !strings.Contains(getContainerEnv(t, ctx, container), "NEO4J_some_key=value3") { - t.Fatalf("expected custom setting to be set with last value") - } + require.Containsf(t, errorLogs, `setting "some.key" with value "value1" is now overwritten with value "value2"`+"\n", "expected setting overwrites to be logged") + require.Containsf(t, errorLogs, `setting "some.key" with value "value2" is now overwritten with value "value3"`+"\n", "expected setting overwrites to be logged") + require.Containsf(t, getContainerEnv(t, ctx, ctr), "NEO4J_some_key=value3", "expected custom setting to be set with last value") }) outer.Run("rejects nil logger", func(t *testing.T) { - container, err := neo4j.RunContainer(ctx, neo4j.WithLogger(nil)) - - if container != nil { - t.Fatalf("container must not be created with nil logger") - } - if err == nil || err.Error() != "nil logger is not permitted" { - t.Fatalf("expected config validation error but got no error") - } + ctr, err := neo4j.Run(ctx, "neo4j:4.4", neo4j.WithLogger(nil)) + testcontainers.CleanupContainer(t, ctr) + require.Nilf(t, ctr, "container must not be created with nil logger") + require.EqualErrorf(t, err, "nil logger is not permitted", "expected config validation error but got no error") }) } -func setupNeo4j(ctx context.Context, t *testing.T) *neo4j.Neo4jContainer { - container, err := neo4j.RunContainer(ctx, +func setupNeo4j(ctx context.Context) (*neo4j.Neo4jContainer, error) { + return neo4j.Run(ctx, + "neo4j:4.4", neo4j.WithAdminPassword(testPassword), // withLabsPlugin { neo4j.WithLabsPlugin(neo4j.Apoc), // } neo4j.WithNeo4jSetting("dbms.tx_log.rotation.size", "42M"), ) - if err != nil { - t.Fatalf("expected container to successfully initialize but did not: %s", err) - } - return container } func createDriver(t *testing.T, ctx context.Context, container *neo4j.Neo4jContainer) neo.DriverWithContext { + t.Helper() // boltURL { boltUrl, err := container.BoltUrl(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) driver, err := neo.NewDriverWithContext(boltUrl, neo.BasicAuth("neo4j", testPassword, "")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Cleanup(func() { - if err := driver.Close(ctx); err != nil { - t.Fatalf("failed to close neo: %s", err) - } + err := driver.Close(ctx) + require.NoErrorf(t, err, "failed to close neo: %s", err) }) return driver } func getContainerEnv(t *testing.T, ctx context.Context, container *neo4j.Neo4jContainer) string { - exec, reader, err := container.Exec(ctx, []string{"env"}) - if err != nil { - t.Fatalf("expected env to successfully run but did not: %s", err) - } - if exec != 0 { - t.Fatalf("expected env to exit with status 0 but exited with: %d", exec) - } - envVars, err := io.ReadAll(reader) - if err != nil { - t.Fatalf("expected to read all bytes from env output but did not: %s", err) - } - return string(envVars) + t.Helper() + return testcontainers.RequireContainerExec(ctx, t, container, []string{"env"}) } const logSeparator = "---$$$---" diff --git a/modules/ollama/examples_test.go b/modules/ollama/examples_test.go index 683580795f..741db846be 100644 --- a/modules/ollama/examples_test.go +++ b/modules/ollama/examples_test.go @@ -14,26 +14,26 @@ import ( tcollama "github.com/testcontainers/testcontainers-go/modules/ollama" ) -func ExampleRunContainer() { +func ExampleRun() { // runOllamaContainer { ctx := context.Background() - ollamaContainer, err := tcollama.RunContainer(ctx, testcontainers.WithImage("ollama/ollama:0.1.25")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := ollamaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -42,38 +42,39 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_withModel_llama2_http() { +func ExampleRun_withModel_llama2_http() { // withHTTPModelLlama2 { ctx := context.Background() - ollamaContainer, err := tcollama.RunContainer( - ctx, - testcontainers.WithImage("ollama/ollama:0.1.25"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } + ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } model := "llama2" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) + log.Printf("failed to pull model %s: %s", model, err) + return } _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "run", model}) if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) + log.Printf("failed to run model %s: %s", model, err) + return } connectionStr, err := ollamaContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } httpClient := &http.Client{} @@ -84,14 +85,16 @@ func ExampleRunContainer_withModel_llama2_http() { "prompt":"Why is the sky blue?" }` - req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/generate", connectionStr), strings.NewReader(payload)) + req, err := http.NewRequest(http.MethodPost, connectionStr+"/api/generate", strings.NewReader(payload)) if err != nil { - log.Fatalf("failed to create request: %s", err) // nolint:gocritic + log.Printf("failed to create request: %s", err) + return } resp, err := httpClient.Do(req) if err != nil { - log.Fatalf("failed to get response: %s", err) // nolint:gocritic + log.Printf("failed to get response: %s", err) + return } // } @@ -100,38 +103,39 @@ func ExampleRunContainer_withModel_llama2_http() { // Intentionally not asserting the output, as we don't want to run this example in the tests. } -func ExampleRunContainer_withModel_llama2_langchain() { +func ExampleRun_withModel_llama2_langchain() { // withLangchainModelLlama2 { ctx := context.Background() - ollamaContainer, err := tcollama.RunContainer( - ctx, - testcontainers.WithImage("ollama/ollama:0.1.25"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } + ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } model := "llama2" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) + log.Printf("failed to pull model %s: %s", model, err) + return } _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "run", model}) if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) + log.Printf("failed to run model %s: %s", model, err) + return } connectionStr, err := ollamaContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } var llm *langchainollama.LLM @@ -139,7 +143,8 @@ func ExampleRunContainer_withModel_llama2_langchain() { langchainollama.WithModel(model), langchainollama.WithServerURL(connectionStr), ); err != nil { - log.Fatalf("failed to create langchain ollama: %s", err) // nolint:gocritic + log.Printf("failed to create langchain ollama: %s", err) + return } completion, err := llm.Call( @@ -149,7 +154,8 @@ func ExampleRunContainer_withModel_llama2_langchain() { llms.WithTemperature(0.0), // the lower the temperature, the more creative the completion ) if err != nil { - log.Fatalf("failed to create langchain ollama: %s", err) // nolint:gocritic + log.Printf("failed to create langchain ollama: %s", err) + return } words := []string{ diff --git a/modules/ollama/go.mod b/modules/ollama/go.mod index 01de3a03e3..b3a1b1e5c2 100644 --- a/modules/ollama/go.mod +++ b/modules/ollama/go.mod @@ -1,24 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/ollama -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/google/uuid v1.6.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 github.com/tmc/langchaingo v0.1.5 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.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/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.8.1 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -27,10 +29,10 @@ require ( 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/golang/protobuf v1.5.4 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -40,6 +42,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.2 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -51,13 +54,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3 // indirect - google.golang.org/grpc v1.60.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/ollama/go.sum b/modules/ollama/go.sum index 1ed7d4386a..87536fd72c 100644 --- a/modules/ollama/go.sum +++ b/modules/ollama/go.sum @@ -4,29 +4,29 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +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/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -42,8 +42,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +52,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= 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.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +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/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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -82,6 +86,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -93,6 +99,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -128,23 +136,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -155,38 +159,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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 v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg= -google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 h1:EWIeHfGuUf00zrVZGEgYFxok7plSAXBGcH7NNdMAWvA= -google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3 h1:kzJAXnzZoFbe5bhZd4zjUuHos/I31yH4thfMb/13oVY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= -google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k= -google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/ollama/ollama.go b/modules/ollama/ollama.go index cb0a8784b5..203d80103f 100644 --- a/modules/ollama/ollama.go +++ b/modules/ollama/ollama.go @@ -5,15 +5,16 @@ import ( "fmt" "time" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/wait" ) +// Deprecated: it will be removed in the next major version. const DefaultOllamaImage = "ollama/ollama:0.1.25" // OllamaContainer represents the Ollama container type used in the module @@ -47,7 +48,7 @@ func (c *OllamaContainer) Commit(ctx context.Context, targetImage string) error return err } - list, err := cli.ImageList(ctx, types.ImageListOptions{Filters: filters.NewArgs(filters.Arg("reference", targetImage))}) + list, err := cli.ImageList(ctx, image.ListOptions{Filters: filters.NewArgs(filters.Arg("reference", targetImage))}) if err != nil { return fmt.Errorf("listing images %w", err) } @@ -71,10 +72,16 @@ func (c *OllamaContainer) Commit(ctx context.Context, targetImage string) error return nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Ollama container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OllamaContainer, error) { + return Run(ctx, "ollama/ollama:0.1.25", opts...) +} + +// Run creates an instance of the Ollama container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OllamaContainer, error) { req := testcontainers.ContainerRequest{ - Image: DefaultOllamaImage, + Image: img, ExposedPorts: []string{"11434/tcp"}, WaitingFor: wait.ForListeningPort("11434/tcp").WithStartupTimeout(60 * time.Second), } @@ -94,9 +101,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OllamaContainer + if container != nil { + c = &OllamaContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OllamaContainer{Container: container}, nil + return c, nil } diff --git a/modules/ollama/ollama_test.go b/modules/ollama/ollama_test.go index bece78f0a8..94212dc171 100644 --- a/modules/ollama/ollama_test.go +++ b/modules/ollama/ollama_test.go @@ -4,12 +4,12 @@ import ( "context" "fmt" "io" - "log" "net/http" "strings" "testing" "github.com/google/uuid" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/exec" @@ -19,52 +19,34 @@ import ( func TestOllama(t *testing.T) { ctx := context.Background() - container, err := ollama.RunContainer(ctx, testcontainers.WithImage("ollama/ollama:0.1.25")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := ollama.Run(ctx, "ollama/ollama:0.1.25") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("ConnectionString", func(t *testing.T) { // connectionString { - connectionStr, err := container.ConnectionString(ctx) + connectionStr, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) httpClient := &http.Client{} resp, err := httpClient.Get(connectionStr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("Pull and Run Model", func(t *testing.T) { model := "all-minilm" - _, _, err = container.Exec(context.Background(), []string{"ollama", "pull", model}) - if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) - } + _, _, err = ctr.Exec(context.Background(), []string{"ollama", "pull", model}) + require.NoError(t, err) - _, _, err = container.Exec(context.Background(), []string{"ollama", "run", model}) - if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) - } + _, _, err = ctr.Exec(context.Background(), []string{"ollama", "run", model}) + require.NoError(t, err) - assertLoadedModel(t, container) + assertLoadedModel(t, ctr) }) t.Run("Commit to image including model", func(t *testing.T) { @@ -74,24 +56,16 @@ func TestOllama(t *testing.T) { // Users can change the way this is generated, but it should be unique. targetImage := fmt.Sprintf("%s-%s", ollama.DefaultOllamaImage, strings.ToLower(uuid.New().String()[:4])) - err := container.Commit(context.Background(), targetImage) + err := ctr.Commit(context.Background(), targetImage) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - newOllamaContainer, err := ollama.RunContainer( + newOllamaContainer, err := ollama.Run( context.Background(), - testcontainers.WithImage(targetImage), + targetImage, ) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err := newOllamaContainer.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, newOllamaContainer) + require.NoError(t, err) assertLoadedModel(t, newOllamaContainer) }) @@ -101,64 +75,46 @@ func TestOllama(t *testing.T) { // For that, it checks if the response of the /api/tags endpoint // contains the model name. func assertLoadedModel(t *testing.T, c *ollama.OllamaContainer) { + t.Helper() url, err := c.ConnectionString(context.Background()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) httpCli := &http.Client{} resp, err := httpCli.Get(url + "/api/tags") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) bs, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !strings.Contains(string(bs), "all-minilm") { - t.Fatalf("expected response to contain all-minilm, got %s", string(bs)) - } + require.Contains(t, string(bs), "all-minilm") } func TestRunContainer_withModel_error(t *testing.T) { ctx := context.Background() - ollamaContainer, err := ollama.RunContainer( + ollamaContainer, err := ollama.Run( ctx, - testcontainers.WithImage("ollama/ollama:0.1.25"), + "ollama/ollama:0.1.25", ) - if err != nil { - t.Fatalf("expected error to be nil, got %s", err) - } + testcontainers.CleanupContainer(t, ollamaContainer) + require.NoError(t, err) model := "non-existent" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) - if err != nil { - log.Fatalf("expected nil error, got %s", err) - } + require.NoError(t, err) // we need to parse the response here to check if the error message is correct _, r, err := ollamaContainer.Exec(ctx, []string{"ollama", "run", model}, exec.Multiplexed()) - if err != nil { - log.Fatalf("expected nil error, got %s", err) - } + require.NoError(t, err) bs, err := io.ReadAll(r) - if err != nil { - t.Fatalf("failed to run %s model: %s", model, err) - } + require.NoError(t, err) stdOutput := string(bs) - if !strings.Contains(stdOutput, "Error: pull model manifest: file does not exist") { - t.Fatalf("expected output to contain %q, got %s", "Error: pull model manifest: file does not exist", stdOutput) - } + require.Contains(t, stdOutput, "Error: pull model manifest: file does not exist") } diff --git a/modules/openfga/examples_test.go b/modules/openfga/examples_test.go index 9d66965998..cb0443b863 100644 --- a/modules/openfga/examples_test.go +++ b/modules/openfga/examples_test.go @@ -17,26 +17,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/openfga" ) -func ExampleRunContainer() { +func ExampleRun() { // runOpenFGAContainer { ctx := context.Background() - openfgaContainer, err := openfga.RunContainer(ctx, testcontainers.WithImage("openfga/openfga:v1.5.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + openfgaContainer, err := openfga.Run(ctx, "openfga/openfga:v1.5.0") defer func() { - if err := openfgaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := openfgaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -45,23 +45,23 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connectToPlayground() { - openfgaContainer, err := openfga.RunContainer(context.Background(), testcontainers.WithImage("openfga/openfga:v1.5.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container +func ExampleRun_connectToPlayground() { + openfgaContainer, err := openfga.Run(context.Background(), "openfga/openfga:v1.5.0") defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // playgroundEndpoint { playgroundEndpoint, err := openfgaContainer.PlaygroundEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get playground endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get playground endpoint: %s", err) + return } // } @@ -69,7 +69,8 @@ func ExampleRunContainer_connectToPlayground() { resp, err := httpClient.Get(playgroundEndpoint) if err != nil { - log.Fatalf("failed to get playground endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get playground endpoint: %s", err) + return } fmt.Println(resp.StatusCode) @@ -78,23 +79,23 @@ func ExampleRunContainer_connectToPlayground() { // 200 } -func ExampleRunContainer_connectWithSDKClient() { - openfgaContainer, err := openfga.RunContainer(context.Background(), testcontainers.WithImage("openfga/openfga:v1.5.0")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container +func ExampleRun_connectWithSDKClient() { + openfgaContainer, err := openfga.Run(context.Background(), "openfga/openfga:v1.5.0") defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // httpEndpoint { httpEndpoint, err := openfgaContainer.HttpEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get HTTP endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get HTTP endpoint: %s", err) + return } // } @@ -103,26 +104,30 @@ func ExampleRunContainer_connectWithSDKClient() { ApiUrl: httpEndpoint, // required }) if err != nil { - log.Fatalf("failed to create SDK client: %s", err) // nolint:gocritic + log.Printf("failed to create SDK client: %s", err) + return } list, err := fgaClient.ListStores(context.Background()).Execute() if err != nil { - log.Fatalf("failed to list stores: %s", err) // nolint:gocritic + log.Printf("failed to list stores: %s", err) + return } fmt.Println(len(list.Stores)) store, err := fgaClient.CreateStore(context.Background()).Body(client.ClientCreateStoreRequest{Name: "test"}).Execute() if err != nil { - log.Fatalf("failed to create store: %s", err) // nolint:gocritic + log.Printf("failed to create store: %s", err) + return } fmt.Println(store.Name) list, err = fgaClient.ListStores(context.Background()).Execute() if err != nil { - log.Fatalf("failed to list stores: %s", err) // nolint:gocritic + log.Printf("failed to list stores: %s", err) + return } fmt.Println(len(list.Stores)) @@ -133,32 +138,32 @@ func ExampleRunContainer_connectWithSDKClient() { // 1 } -func ExampleRunContainer_writeModel() { +func ExampleRun_writeModel() { // openFGAwriteModel { secret := "openfga-secret" - openfgaContainer, err := openfga.RunContainer( + openfgaContainer, err := openfga.Run( context.Background(), - testcontainers.WithImage("openfga/openfga:v1.5.0"), + "openfga/openfga:v1.5.0", testcontainers.WithEnv(map[string]string{ "OPENFGA_LOG_LEVEL": "warn", "OPENFGA_AUTHN_METHOD": "preshared", "OPENFGA_AUTHN_PRESHARED_KEYS": secret, }), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } httpEndpoint, err := openfgaContainer.HttpEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get HTTP endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get HTTP endpoint: %s", err) + return } fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{ @@ -177,28 +182,33 @@ func ExampleRunContainer_writeModel() { StoreId: "11111111111111111111111111", }) if err != nil { - log.Fatalf("failed to create openfga client: %v", err) + log.Printf("failed to create openfga client: %v", err) + return } f, err := os.Open(filepath.Join("testdata", "authorization_model.json")) if err != nil { - log.Fatalf("failed to open file: %v", err) + log.Printf("failed to open file: %v", err) + return } defer f.Close() bs, err := io.ReadAll(f) if err != nil { - log.Fatalf("failed to read file: %v", err) + log.Printf("failed to read file: %v", err) + return } var body client.ClientWriteAuthorizationModelRequest if err := json.Unmarshal(bs, &body); err != nil { - log.Fatalf("failed to unmarshal json: %v", err) + log.Printf("failed to unmarshal json: %v", err) + return } resp, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(body).Execute() if err != nil { - log.Fatalf("failed to write authorization model: %v", err) + log.Printf("failed to write authorization model: %v", err) + return } // } diff --git a/modules/openfga/go.mod b/modules/openfga/go.mod index 916a96ef62..2a0c0d9df8 100644 --- a/modules/openfga/go.mod +++ b/modules/openfga/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/openfga -go 1.21 +go 1.22 require ( github.com/openfga/go-sdk v0.3.5 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,11 +27,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -49,14 +53,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/openfga/go.sum b/modules/openfga/go.sum index 8504d75909..21cf8ea02c 100644 --- a/modules/openfga/go.sum +++ b/modules/openfga/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +53,18 @@ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInw github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -82,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -93,6 +100,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -126,23 +135,21 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -153,38 +160,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/openfga/openfga.go b/modules/openfga/openfga.go index 1f1fbbd3bd..67f79b30e1 100644 --- a/modules/openfga/openfga.go +++ b/modules/openfga/openfga.go @@ -36,13 +36,19 @@ func (c *OpenFGAContainer) PlaygroundEndpoint(ctx context.Context) (string, erro return "", fmt.Errorf("failed to get playground endpoint: %w", err) } - return fmt.Sprintf("%s/playground", endpoint), nil + return endpoint + "/playground", nil } +// Deprecated: use Run instead // RunContainer creates an instance of the OpenFGA container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenFGAContainer, error) { + return Run(ctx, "openfga/openfga:v1.5.0", opts...) +} + +// Run creates an instance of the OpenFGA container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenFGAContainer, error) { req := testcontainers.ContainerRequest{ - Image: "openfga/openfga:v1.5.0", + Image: img, Cmd: []string{"run"}, ExposedPorts: []string{"3000/tcp", "8080/tcp", "8081/tcp"}, WaitingFor: wait.ForAll( @@ -72,9 +78,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenFGAContainer + if container != nil { + c = &OpenFGAContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenFGAContainer{Container: container}, nil + return c, nil } diff --git a/modules/openfga/openfga_test.go b/modules/openfga/openfga_test.go index 476c077dcc..85e1966198 100644 --- a/modules/openfga/openfga_test.go +++ b/modules/openfga/openfga_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/openfga" ) @@ -11,17 +13,9 @@ import ( func TestOpenFGA(t *testing.T) { ctx := context.Background() - container, err := openfga.RunContainer(ctx, testcontainers.WithImage("openfga/openfga:v1.5.0")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openfga.Run(ctx, "openfga/openfga:v1.5.0") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions } diff --git a/modules/openldap/examples_test.go b/modules/openldap/examples_test.go index 6c6ed6d672..757385cc92 100644 --- a/modules/openldap/examples_test.go +++ b/modules/openldap/examples_test.go @@ -11,26 +11,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/openldap" ) -func ExampleRunContainer() { +func ExampleRun() { // runOpenLDAPContainer { ctx := context.Background() - openldapContainer, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + openldapContainer, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") defer func() { - if err := openldapContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(openldapContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := openldapContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -39,37 +39,39 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connect() { +func ExampleRun_connect() { // connectToOpenLdap { ctx := context.Background() - openldapContainer, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + openldapContainer, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") defer func() { - if err := openldapContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(openldapContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } connectionString, err := openldapContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } client, err := ldap.DialURL(connectionString) if err != nil { - log.Fatalf("failed to connect to LDAP server: %s", err) + log.Printf("failed to connect to LDAP server: %s", err) + return } defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") if err != nil { - log.Fatalf("failed to bind to LDAP server: %s", err) + log.Printf("failed to bind to LDAP server: %s", err) + return } // Search for the given username @@ -83,11 +85,13 @@ func ExampleRunContainer_connect() { sr, err := client.Search(searchRequest) if err != nil { - log.Fatalf("failed to search LDAP server: %s", err) + log.Printf("failed to search LDAP server: %s", err) + return } if len(sr.Entries) != 1 { - log.Fatal("User does not exist or too many entries returned") + log.Print("User does not exist or too many entries returned") + return } fmt.Println(sr.Entries[0].DN) diff --git a/modules/openldap/go.mod b/modules/openldap/go.mod index 2a274acf58..fcf3aa4677 100644 --- a/modules/openldap/go.mod +++ b/modules/openldap/go.mod @@ -1,24 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/openldap -go 1.21 +go 1.22 require ( github.com/go-ldap/ldap/v3 v3.4.6 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -27,11 +29,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -51,13 +55,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/openldap/go.sum b/modules/openldap/go.sum index 84adb106a2..35bec868a2 100644 --- a/modules/openldap/go.sum +++ b/modules/openldap/go.sum @@ -6,29 +6,30 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -48,8 +49,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -61,12 +60,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -87,6 +92,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -98,6 +105,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -134,14 +143,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -150,15 +157,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -175,49 +180,48 @@ 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/openldap/openldap.go b/modules/openldap/openldap.go index 8e0cc271de..5a1bdf24f8 100644 --- a/modules/openldap/openldap.go +++ b/modules/openldap/openldap.go @@ -38,7 +38,7 @@ func (c *OpenLDAPContainer) ConnectionString(ctx context.Context, args ...string return "", err } - connStr := fmt.Sprintf("ldap://%s", net.JoinHostPort(host, containerPort.Port())) + connStr := "ldap://" + net.JoinHostPort(host, containerPort.Port()) return connStr, nil } @@ -122,10 +122,16 @@ func WithInitialLdif(ldif string) testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the OpenLDAP container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenLDAPContainer, error) { + return Run(ctx, "bitnami/openldap:2.6.6", opts...) +} + +// Run creates an instance of the OpenLDAP container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenLDAPContainer, error) { req := testcontainers.ContainerRequest{ - Image: "bitnami/openldap:2.6.6", + Image: img, Env: map[string]string{ "LDAP_ADMIN_USERNAME": defaultUser, "LDAP_ADMIN_PASSWORD": defaultPassword, @@ -155,14 +161,19 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenLDAPContainer + if container != nil { + c = &OpenLDAPContainer{ + Container: container, + adminUsername: req.Env["LDAP_ADMIN_USERNAME"], + adminPassword: req.Env["LDAP_ADMIN_PASSWORD"], + rootDn: req.Env["LDAP_ROOT"], + } + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenLDAPContainer{ - Container: container, - adminUsername: req.Env["LDAP_ADMIN_USERNAME"], - adminPassword: req.Env["LDAP_ADMIN_PASSWORD"], - rootDn: req.Env["LDAP_ROOT"], - }, nil + return c, nil } diff --git a/modules/openldap/openldap_test.go b/modules/openldap/openldap_test.go index 9407a5716a..b73a8dce36 100644 --- a/modules/openldap/openldap_test.go +++ b/modules/openldap/openldap_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/go-ldap/ldap/v3" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/openldap" @@ -14,105 +15,61 @@ import ( func TestOpenLDAP(t *testing.T) { ctx := context.Background() - container, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestOpenLDAPWithAdminUsernameAndPassword(t *testing.T) { ctx := context.Background() - container, err := openldap.RunContainer(ctx, - testcontainers.WithImage("bitnami/openldap:2.6.6"), + ctr, err := openldap.Run(ctx, + "bitnami/openldap:2.6.6", openldap.WithAdminUsername("openldap"), openldap.WithAdminPassword("openldap"), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=openldap,dc=example,dc=org", "openldap") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestOpenLDAPWithDifferentRoot(t *testing.T) { ctx := context.Background() - container, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6"), openldap.WithRoot("dc=mydomain,dc=com")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithRoot("dc=mydomain,dc=com")) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionString, err := container.ConnectionString(ctx) + connectionString, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=mydomain,dc=com", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestOpenLDAPLoadLdif(t *testing.T) { ctx := context.Background() - container, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // loadLdif { ldif := ` @@ -125,28 +82,20 @@ mail: test.user@example.org userPassword: Password1 ` - err = container.LoadLdif(ctx, []byte(ldif)) + err = ctr.LoadLdif(ctx, []byte(ldif)) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := client.Search(&ldap.SearchRequest{ BaseDN: "uid=test.user,ou=users,dc=example,dc=org", @@ -154,16 +103,9 @@ userPassword: Password1 Filter: "(objectClass=*)", Attributes: []string{"dn"}, }) - if err != nil { - t.Fatal(err) - } - - if len(result.Entries) != 1 { - t.Fatal("Invalid number of entries returned", result.Entries) - } - if result.Entries[0].DN != "uid=test.user,ou=users,dc=example,dc=org" { - t.Fatal("Invalid entry returned", result.Entries[0].DN) - } + require.NoError(t, err) + require.Len(t, result.Entries, 1) + require.Equal(t, "uid=test.user,ou=users,dc=example,dc=org", result.Entries[0].DN) } func TestOpenLDAPWithInitialLdif(t *testing.T) { @@ -178,49 +120,29 @@ mail: test.user@example.org userPassword: Password1 ` - f, err := os.CreateTemp("", "test.ldif") - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) + f, err := os.CreateTemp(t.TempDir(), "test.ldif") + require.NoError(t, err) _, err = f.WriteString(ldif) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + err = f.Close() - if err != nil { - t.Fatal(err) - } - - container, err := openldap.RunContainer(ctx, testcontainers.WithImage("bitnami/openldap:2.6.6"), openldap.WithInitialLdif(f.Name())) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithInitialLdif(f.Name())) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := client.Search(&ldap.SearchRequest{ BaseDN: "uid=test.user,ou=users,dc=example,dc=org", @@ -228,14 +150,8 @@ userPassword: Password1 Filter: "(objectClass=*)", Attributes: []string{"dn"}, }) - if err != nil { - t.Fatal(err) - } - - if len(result.Entries) != 1 { - t.Fatal("Invalid number of entries returned", result.Entries) - } - if result.Entries[0].DN != "uid=test.user,ou=users,dc=example,dc=org" { - t.Fatal("Invalid entry returned", result.Entries[0].DN) - } + require.NoError(t, err) + + require.Len(t, result.Entries, 1) + require.Equal(t, "uid=test.user,ou=users,dc=example,dc=org", result.Entries[0].DN) } diff --git a/modules/opensearch/examples_test.go b/modules/opensearch/examples_test.go index bb9a855c2a..d2e4c5807d 100644 --- a/modules/opensearch/examples_test.go +++ b/modules/opensearch/examples_test.go @@ -9,31 +9,31 @@ import ( "github.com/testcontainers/testcontainers-go/modules/opensearch" ) -func ExampleRunContainer() { +func ExampleRun() { // runOpenSearchContainer { ctx := context.Background() - opensearchContainer, err := opensearch.RunContainer( + opensearchContainer, err := opensearch.Run( ctx, - testcontainers.WithImage("opensearchproject/opensearch:2.11.1"), + "opensearchproject/opensearch:2.11.1", opensearch.WithUsername("new-username"), opensearch.WithPassword("new-password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := opensearchContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(opensearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := opensearchContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/opensearch/go.mod b/modules/opensearch/go.mod index e9f614cf6f..0712f43830 100644 --- a/modules/opensearch/go.mod +++ b/modules/opensearch/go.mod @@ -1,34 +1,37 @@ module github.com/testcontainers/testcontainers-go/modules/opensearch -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-units v0.5.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.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/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -37,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -48,13 +52,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/opensearch/go.sum b/modules/opensearch/go.sum index d8ba86a7c1..447eec4038 100644 --- a/modules/opensearch/go.sum +++ b/modules/opensearch/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -89,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -122,23 +131,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -149,38 +154,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/opensearch/opensearch.go b/modules/opensearch/opensearch.go index 61290c9ed2..fcc5a1f714 100644 --- a/modules/opensearch/opensearch.go +++ b/modules/opensearch/opensearch.go @@ -27,10 +27,16 @@ type OpenSearchContainer struct { Password string } +// Deprecated: use Run instead // RunContainer creates an instance of the OpenSearch container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*OpenSearchContainer, error) { + return Run(ctx, "opensearchproject/opensearch:2.11.1", opts...) +} + +// Run creates an instance of the OpenSearch container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*OpenSearchContainer, error) { req := testcontainers.ContainerRequest{ - Image: "opensearchproject/opensearch:2.11.1", + Image: img, ExposedPorts: []string{defaultHTTPPort, "9600/tcp"}, Env: map[string]string{ "discovery.type": "single-node", @@ -112,11 +118,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize }) container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenSearchContainer + if container != nil { + c = &OpenSearchContainer{Container: container, User: username, Password: password} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenSearchContainer{Container: container, User: username, Password: password}, nil + return c, nil } // Address retrieves the address of the OpenSearch container. diff --git a/modules/opensearch/opensearch_test.go b/modules/opensearch/opensearch_test.go index 4d62638af6..15304dbfd6 100644 --- a/modules/opensearch/opensearch_test.go +++ b/modules/opensearch/opensearch_test.go @@ -5,6 +5,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/opensearch" ) @@ -12,35 +14,21 @@ import ( func TestOpenSearch(t *testing.T) { ctx := context.Background() - container, err := opensearch.RunContainer(ctx, testcontainers.WithImage("opensearchproject/opensearch:2.11.1")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := opensearch.Run(ctx, "opensearchproject/opensearch:2.11.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("Connect to Address", func(t *testing.T) { - address, err := container.Address(ctx) - if err != nil { - t.Fatal(err) - } + address, err := ctr.Address(ctx) + require.NoError(t, err) client := &http.Client{} - req, err := http.NewRequest("GET", address, nil) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, address, nil) + require.NoError(t, err) resp, err := client.Do(req) - if err != nil { - t.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() }) } diff --git a/modules/postgres/examples_test.go b/modules/postgres/examples_test.go index 20fc50d696..1f5b0e1c86 100644 --- a/modules/postgres/examples_test.go +++ b/modules/postgres/examples_test.go @@ -12,7 +12,7 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -func ExampleRunContainer() { +func ExampleRun() { // runPostgresContainer { ctx := context.Background() @@ -20,8 +20,8 @@ func ExampleRunContainer() { dbUser := "user" dbPassword := "password" - postgresContainer, err := postgres.RunContainer(ctx, - testcontainers.WithImage("docker.io/postgres:16-alpine"), + postgresContainer, err := postgres.Run(ctx, + "postgres:16-alpine", postgres.WithInitScripts(filepath.Join("testdata", "init-user-db.sh")), postgres.WithConfigFile(filepath.Join("testdata", "my-postgres.conf")), postgres.WithDatabase(dbName), @@ -32,21 +32,21 @@ func ExampleRunContainer() { WithOccurrence(2). WithStartupTimeout(5*time.Second)), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := postgresContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(postgresContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := postgresContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/postgres/go.mod b/modules/postgres/go.mod index a2273829fb..ac70814b3d 100644 --- a/modules/postgres/go.mod +++ b/modules/postgres/go.mod @@ -1,42 +1,43 @@ module github.com/testcontainers/testcontainers-go/modules/postgres -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/jackc/pgx/v5 v5.5.4 github.com/lib/pq v1.10.9 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/klauspost/compress v1.17.4 // indirect 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -57,14 +58,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/postgres/go.sum b/modules/postgres/go.sum index f970cbda46..e12245f686 100644 --- a/modules/postgres/go.sum +++ b/modules/postgres/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -41,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -61,8 +59,8 @@ 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -73,6 +71,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -106,6 +106,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -140,23 +142,21 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -167,33 +167,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -202,5 +199,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/postgres/options.go b/modules/postgres/options.go new file mode 100644 index 0000000000..5779f85c04 --- /dev/null +++ b/modules/postgres/options.go @@ -0,0 +1,39 @@ +package postgres + +import ( + "github.com/testcontainers/testcontainers-go" +) + +type options struct { + // SQLDriverName is the name of the SQL driver to use. + SQLDriverName string + Snapshot string +} + +func defaultOptions() options { + return options{ + SQLDriverName: "postgres", + Snapshot: defaultSnapshotName, + } +} + +// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (Option)(nil) + +// Option is an option for the Redpanda container. +type Option func(*options) + +// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. +func (o Option) Customize(*testcontainers.GenericContainerRequest) error { + // NOOP to satisfy interface. + return nil +} + +// WithSQLDriver sets the SQL driver to use for the container. +// It is passed to sql.Open() to connect to the database when making or restoring snapshots. +// This can be set if your app imports a different postgres driver, f.ex. "pgx" +func WithSQLDriver(driver string) Option { + return func(o *options) { + o.SQLDriverName = driver + } +} diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index f8a3a1c630..ce3719575d 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -2,6 +2,8 @@ package postgres import ( "context" + "database/sql" + "errors" "fmt" "io" "net" @@ -12,10 +14,9 @@ import ( ) const ( - defaultUser = "postgres" - defaultPassword = "postgres" - defaultPostgresImage = "docker.io/postgres:16-alpine" - defaultSnapshotName = "migrated_template" + defaultUser = "postgres" + defaultPassword = "postgres" + defaultSnapshotName = "migrated_template" ) // PostgresContainer represents the postgres container type used in the module @@ -25,6 +26,9 @@ type PostgresContainer struct { user string password string snapshotName string + // sqlDriverName is passed to sql.Open() to connect to the database when making or restoring snapshots. + // This can be set if your app imports a different postgres driver, f.ex. "pgx" + sqlDriverName string } // MustConnectionString panics if the address cannot be determined. @@ -130,10 +134,16 @@ func WithUsername(user string) testcontainers.CustomizeRequestOption { } } -// RunContainer creates an instance of the postgres container type +// Deprecated: use Run instead +// RunContainer creates an instance of the Postgres container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) { + return Run(ctx, "postgres:16-alpine", opts...) +} + +// Run creates an instance of the Postgres container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*PostgresContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultPostgresImage, + Image: img, Env: map[string]string{ "POSTGRES_USER": defaultUser, "POSTGRES_PASSWORD": defaultPassword, @@ -148,22 +158,35 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize Started: true, } + // Gather all config options (defaults and then apply provided options) + settings := defaultOptions() for _, opt := range opts { + if apply, ok := opt.(Option); ok { + apply(&settings) + } if err := opt.Customize(&genericContainerReq); err != nil { return nil, err } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *PostgresContainer + if container != nil { + c = &PostgresContainer{ + Container: container, + dbName: req.Env["POSTGRES_DB"], + password: req.Env["POSTGRES_PASSWORD"], + user: req.Env["POSTGRES_USER"], + sqlDriverName: settings.SQLDriverName, + snapshotName: settings.Snapshot, + } } - user := req.Env["POSTGRES_USER"] - password := req.Env["POSTGRES_PASSWORD"] - dbName := req.Env["POSTGRES_DB"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &PostgresContainer{Container: container, dbName: dbName, password: password, user: user}, nil + return c, nil } type snapshotConfig struct { @@ -187,54 +210,48 @@ func WithSnapshotName(name string) SnapshotOption { // customize the snapshot name with the options. // If a snapshot already exists under the given/default name, it will be overwritten with the new snapshot. func (c *PostgresContainer) Snapshot(ctx context.Context, opts ...SnapshotOption) error { - config := &snapshotConfig{} - for _, opt := range opts { - config = opt(config) - } - - snapshotName := defaultSnapshotName - if config.snapshotName != "" { - snapshotName = config.snapshotName - } - - if c.dbName == "postgres" { - return fmt.Errorf("cannot snapshot the postgres system database as it cannot be dropped to be restored") + snapshotName, err := c.checkSnapshotConfig(opts) + if err != nil { + return err } // execute the commands to create the snapshot, in order - cmds := []string{ - // Drop the snapshot database if it already exists + if err := c.execCommandsSQL(ctx, + // Update pg_database to remove the template flag, then drop the database if it exists. + // This is needed because dropping a template database will fail. + // https://www.postgresql.org/docs/current/manage-ag-templatedbs.html + fmt.Sprintf(`UPDATE pg_database SET datistemplate = FALSE WHERE datname = '%s'`, snapshotName), fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, snapshotName), // Create a copy of the database to another database to use as a template now that it was fully migrated fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s" OWNER "%s"`, snapshotName, c.dbName, c.user), // Snapshot the template database so we can restore it onto our original database going forward fmt.Sprintf(`ALTER DATABASE "%s" WITH is_template = TRUE`, snapshotName), - } - - for _, cmd := range cmds { - exitCode, reader, err := c.Exec(ctx, []string{"psql", "-U", c.user, "-d", c.dbName, "-c", cmd}) - if err != nil { - return err - } - if exitCode != 0 { - buf := new(strings.Builder) - _, err := io.Copy(buf, reader) - if err != nil { - return fmt.Errorf("non-zero exit code for snapshot command, could not read command output: %w", err) - } - - return fmt.Errorf("non-zero exit code for snapshot command: %s", buf.String()) - } + ); err != nil { + return err } c.snapshotName = snapshotName - return nil } // Restore will restore the database to a specific snapshot. By default, it will restore the last snapshot taken on the // database by the Snapshot method. If a snapshot name is provided, it will instead try to restore the snapshot by name. func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) error { + snapshotName, err := c.checkSnapshotConfig(opts) + if err != nil { + return err + } + + // execute the commands to restore the snapshot, in order + return c.execCommandsSQL(ctx, + // Drop the entire database by connecting to the postgres global database + fmt.Sprintf(`DROP DATABASE "%s" with (FORCE)`, c.dbName), + // Then restore the previous snapshot + fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s" OWNER "%s"`, c.dbName, snapshotName, c.user), + ) +} + +func (c *PostgresContainer) checkSnapshotConfig(opts []SnapshotOption) (string, error) { config := &snapshotConfig{} for _, opt := range opts { config = opt(config) @@ -246,17 +263,64 @@ func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) } if c.dbName == "postgres" { - return fmt.Errorf("cannot restore the postgres system database as it cannot be dropped to be restored") + return "", errors.New("cannot restore the postgres system database as it cannot be dropped to be restored") } + return snapshotName, nil +} - // execute the commands to restore the snapshot, in order - cmds := []string{ - // Drop the entire database by connecting to the postgres global database - fmt.Sprintf(`DROP DATABASE "%s" with (FORCE)`, c.dbName), - // Then restore the previous snapshot - fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s" OWNER "%s"`, c.dbName, snapshotName, c.user), +func (c *PostgresContainer) execCommandsSQL(ctx context.Context, cmds ...string) error { + conn, cleanup, err := c.snapshotConnection(ctx) + if err != nil { + testcontainers.Logger.Printf("Could not connect to database to restore snapshot, falling back to `docker exec psql`: %v", err) + return c.execCommandsFallback(ctx, cmds) + } + if cleanup != nil { + defer cleanup() + } + for _, cmd := range cmds { + if _, err := conn.ExecContext(ctx, cmd); err != nil { + return fmt.Errorf("could not execute restore command %s: %w", cmd, err) + } + } + return nil +} + +// snapshotConnection connects to the actual database using the "postgres" sql.DB driver, if it exists. +// The returned function should be called as a defer() to close the pool. +// No need to close the individual connection, that is done as part of the pool close. +// Also, no need to cache the connection pool, since it is a single connection which is very fast to establish. +func (c *PostgresContainer) snapshotConnection(ctx context.Context) (*sql.Conn, func(), error) { + // Connect to the database "postgres" instead of the app one + c2 := &PostgresContainer{ + Container: c.Container, + dbName: "postgres", + user: c.user, + password: c.password, + sqlDriverName: c.sqlDriverName, + } + + // Try to use an actual postgres connection, if the driver is loaded + connStr := c2.MustConnectionString(ctx, "sslmode=disable") + pool, err := sql.Open(c.sqlDriverName, connStr) + if err != nil { + return nil, nil, fmt.Errorf("sql.Open for snapshot connection failed: %w", err) } + cleanupPool := func() { + if err := pool.Close(); err != nil { + testcontainers.Logger.Printf("Could not close database connection pool after restoring snapshot: %v", err) + } + } + + conn, err := pool.Conn(ctx) + if err != nil { + cleanupPool() + return nil, nil, fmt.Errorf("DB.Conn for snapshot connection failed: %w", err) + } + return conn, cleanupPool, nil +} + +func (c *PostgresContainer) execCommandsFallback(ctx context.Context, cmds []string) error { for _, cmd := range cmds { exitCode, reader, err := c.Exec(ctx, []string{"psql", "-v", "ON_ERROR_STOP=1", "-U", c.user, "-d", "postgres", "-c", cmd}) if err != nil { @@ -272,6 +336,5 @@ func (c *PostgresContainer) Restore(ctx context.Context, opts ...SnapshotOption) return fmt.Errorf("non-zero exit code for restore command: %s", buf.String()) } } - return nil } diff --git a/modules/postgres/postgres_test.go b/modules/postgres/postgres_test.go index 9dc2194685..8c02e68476 100644 --- a/modules/postgres/postgres_test.go +++ b/modules/postgres/postgres_test.go @@ -3,7 +3,6 @@ package postgres_test import ( "context" "database/sql" - "errors" "fmt" "path/filepath" "testing" @@ -11,8 +10,8 @@ import ( "github.com/docker/go-connections/nat" "github.com/jackc/pgx/v5" + _ "github.com/jackc/pgx/v5/stdlib" _ "github.com/lib/pq" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -32,85 +31,70 @@ func TestPostgres(t *testing.T) { tests := []struct { name string image string - wait wait.Strategy }{ { name: "Postgres", - image: "docker.io/postgres:15.2-alpine", - wait: wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5 * time.Second), + image: "postgres:15.2-alpine", }, { name: "Timescale", // timescale { - image: "docker.io/timescale/timescaledb:2.1.0-pg11", - wait: wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5 * time.Second), + image: "timescale/timescaledb:2.1.0-pg11", // } }, { name: "Postgis", // postgis { - image: "docker.io/postgis/postgis:12-3.0", - wait: wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(30 * time.Second), + image: "postgis/postgis:12-3.0", // } }, { name: "Pgvector", // pgvector { - image: "docker.io/pgvector/pgvector:pg16", - wait: wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(30 * time.Second), + image: "pgvector/pgvector:pg16", // } }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - container, err := postgres.RunContainer(ctx, - testcontainers.WithImage(tt.image), + ctr, err := postgres.Run(ctx, + tt.image, postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(tt.wait), + postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable", "application_name=test") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable", "application_name=test") // } require.NoError(t, err) - mustConnStr := container.MustConnectionString(ctx, "sslmode=disable", "application_name=test") - if mustConnStr != connStr { - t.Errorf("ConnectionString was not equal to MustConnectionString") - } + mustConnStr := ctr.MustConnectionString(ctx, "sslmode=disable", "application_name=test") + require.Equalf(t, mustConnStr, connStr, "ConnectionString was not equal to MustConnectionString") // Ensure connection string is using generic format - id, err := container.MappedPort(ctx, "5432/tcp") + id, err := ctr.MappedPort(ctx, "5432/tcp") require.NoError(t, err) - assert.Equal(t, fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&application_name=test", user, password, "localhost", id.Port(), dbname), connStr) + require.Equal(t, fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&application_name=test", user, password, "localhost", id.Port(), dbname), connStr) // perform assertions db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() result, err := db.Exec("CREATE TABLE IF NOT EXISTS test (id int, name varchar(255));") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) result, err = db.Exec("INSERT INTO test (id, name) VALUES (1, 'test');") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) }) } } @@ -124,208 +108,188 @@ func TestContainerWithWaitForSQL(t *testing.T) { } t.Run("default query", func(t *testing.T) { - container, err := postgres.RunContainer( + ctr, err := postgres.Run( ctx, + "postgres:16-alpine", postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL)), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NotNil(t, container) + require.NotNil(t, ctr) }) t.Run("custom query", func(t *testing.T) { - container, err := postgres.RunContainer( + ctr, err := postgres.Run( ctx, + "postgres:16-alpine", postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 10")), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NotNil(t, container) + require.NotNil(t, ctr) }) t.Run("custom bad query", func(t *testing.T) { - container, err := postgres.RunContainer( + ctr, err := postgres.Run( ctx, + "postgres:16-alpine", postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 'a' from b")), ) + testcontainers.CleanupContainer(t, ctr) require.Error(t, err) - require.Nil(t, container) }) } func TestWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := postgres.RunContainer(ctx, + ctr, err := postgres.Run(ctx, + "postgres:16-alpine", postgres.WithConfigFile(filepath.Join("testdata", "my-postgres.conf")), postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), + postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() } func TestWithInitScript(t *testing.T) { ctx := context.Background() - container, err := postgres.RunContainer(ctx, - testcontainers.WithImage("docker.io/postgres:15.2-alpine"), + ctr, err := postgres.Run(ctx, + "postgres:15.2-alpine", postgres.WithInitScripts(filepath.Join("testdata", "init-user-db.sh")), postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), + postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() // database created in init script. See testdata/init-user-db.sh result, err := db.Exec("SELECT * FROM testdb;") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) } func TestSnapshot(t *testing.T) { - // snapshotAndReset { - ctx := context.Background() + tests := []struct { + name string + options []postgres.SnapshotOption + }{ + { + name: "snapshot/default", + options: nil, + }, - // 1. Start the postgres container and run any migrations on it - container, err := postgres.RunContainer( - ctx, - testcontainers.WithImage("docker.io/postgres:16-alpine"), - postgres.WithDatabase(dbname), - postgres.WithUsername(user), - postgres.WithPassword(password), - testcontainers.WithWaitStrategy( - wait.ForLog("database system is ready to accept connections"). - WithOccurrence(2). - WithStartupTimeout(5*time.Second)), - ) - if err != nil { - t.Fatal(err) + { + name: "snapshot/custom", + options: []postgres.SnapshotOption{ + postgres.WithSnapshotName("custom-snapshot"), + }, + }, } - // Run any migrations on the database - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) - if err != nil { - t.Fatal(err) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // snapshotAndReset { + ctx := context.Background() - // 2. Create a snapshot of the database to restore later - err = container.Snapshot(ctx, postgres.WithSnapshotName("test-snapshot")) - if err != nil { - t.Fatal(err) - } + // 1. Start the postgres ctr and run any migrations on it + ctr, err := postgres.Run( + ctx, + "postgres:16-alpine", + postgres.WithDatabase(dbname), + postgres.WithUsername(user), + postgres.WithPassword(password), + postgres.BasicWaitStrategies(), + postgres.WithSQLDriver("pgx"), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + // Run any migrations on the database + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) - dbURL, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + // 2. Create a snapshot of the database to restore later + // tt.options comes the test case, it can be specified as e.g. `postgres.WithSnapshotName("custom-snapshot")` or omitted, to use default name + err = ctr.Snapshot(ctx, tt.options...) + require.NoError(t, err) - t.Run("Test inserting a user", func(t *testing.T) { - t.Cleanup(func() { - // 3. In each test, reset the DB to its snapshot state. - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } - }) + dbURL, err := ctr.ConnectionString(ctx) + require.NoError(t, err) - conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } - defer conn.Close(context.Background()) + t.Run("Test inserting a user", func(t *testing.T) { + t.Cleanup(func() { + // 3. In each test, reset the DB to its snapshot state. + err = ctr.Restore(ctx) + require.NoError(t, err) + }) - _, err = conn.Exec(ctx, "INSERT INTO users(name, age) VALUES ($1, $2)", "test", 42) - if err != nil { - t.Fatal(err) - } + conn, err := pgx.Connect(context.Background(), dbURL) + require.NoError(t, err) + defer conn.Close(context.Background()) - var name string - var age int64 - err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if err != nil { - t.Fatal(err) - } - - if name != "test" { - t.Fatalf("Expected %s to equal `test`", name) - } - if age != 42 { - t.Fatalf("Expected %d to equal `42`", age) - } - }) + _, err = conn.Exec(ctx, "INSERT INTO users(name, age) VALUES ($1, $2)", "test", 42) + require.NoError(t, err) - // 4. Run as many tests as you need, they will each get a clean database - t.Run("Test querying empty DB", func(t *testing.T) { - t.Cleanup(func() { - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } - }) + var name string + var age int64 + err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) + require.NoError(t, err) - conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } - defer conn.Close(context.Background()) + require.Equal(t, "test", name) + require.EqualValues(t, 42, age) + }) - var name string - var age int64 - err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if !errors.Is(err, pgx.ErrNoRows) { - t.Fatalf("Expected error to be a NoRows error, since the DB should be empty on every test. Got %s instead", err) - } - }) - // } + // 4. Run as many tests as you need, they will each get a clean database + t.Run("Test querying empty DB", func(t *testing.T) { + t.Cleanup(func() { + err = ctr.Restore(ctx) + require.NoError(t, err) + }) + + conn, err := pgx.Connect(context.Background(), dbURL) + require.NoError(t, err) + defer conn.Close(context.Background()) + + var name string + var age int64 + err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) + require.ErrorIs(t, err, pgx.ErrNoRows) + }) + // } + }) + } } func TestSnapshotWithOverrides(t *testing.T) { @@ -335,68 +299,142 @@ func TestSnapshotWithOverrides(t *testing.T) { user := "other-user" password := "other-password" - container, err := postgres.RunContainer( + ctr, err := postgres.Run( ctx, - testcontainers.WithImage("docker.io/postgres:16-alpine"), + "postgres:16-alpine", postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy( - wait.ForLog("database system is ready to accept connections"). - WithOccurrence(2). - WithStartupTimeout(5*time.Second)), + postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) - if err != nil { - t.Fatal(err) - } - - err = container.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) + require.NoError(t, err) - dbURL, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + dbURL, err := ctr.ConnectionString(ctx) + require.NoError(t, err) t.Run("Test that the restore works when not using defaults", func(t *testing.T) { - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "INSERT INTO users(name, age) VALUES ('test', 42)"}) - if err != nil { - t.Fatal(err) - } + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "INSERT INTO users(name, age) VALUES ('test', 42)"}) + require.NoError(t, err) // Doing the restore before we connect since this resets the pgx connection - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } + err = ctr.Restore(ctx) + require.NoError(t, err) conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer conn.Close(context.Background()) var count int64 err = conn.QueryRow(context.Background(), "SELECT COUNT(1) FROM users").Scan(&count) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if count != 0 { - t.Fatalf("Expected %d to equal `0`", count) - } + require.Zero(t, count) }) } + +func TestSnapshotDuplicate(t *testing.T) { + ctx := context.Background() + + dbname := "other-db" + user := "other-user" + password := "other-password" + + ctr, err := postgres.Run( + ctx, + "postgres:16-alpine", + postgres.WithDatabase(dbname), + postgres.WithUsername(user), + postgres.WithPassword(password), + postgres.BasicWaitStrategies(), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) + + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) + require.NoError(t, err) + + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) + require.NoError(t, err) +} + +func TestSnapshotWithDockerExecFallback(t *testing.T) { + ctx := context.Background() + + // postgresWithSQLDriver { + // 1. Start the postgres container and run any migrations on it + ctr, err := postgres.Run( + ctx, + "postgres:16-alpine", + postgres.WithDatabase(dbname), + postgres.WithUsername(user), + postgres.WithPassword(password), + postgres.BasicWaitStrategies(), + // Tell the postgres module to use a driver that doesn't exist + // This will cause the module to fall back to using docker exec + postgres.WithSQLDriver("DoesNotExist"), + ) + // } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // Run any migrations on the database + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) + + // 2. Create a snapshot of the database to restore later + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("test-snapshot")) + require.NoError(t, err) + + dbURL, err := ctr.ConnectionString(ctx) + require.NoError(t, err) + + t.Run("Test inserting a user", func(t *testing.T) { + t.Cleanup(func() { + // 3. In each test, reset the DB to its snapshot state. + err := ctr.Restore(ctx) + require.NoError(t, err) + }) + + conn, err2 := pgx.Connect(context.Background(), dbURL) + require.NoError(t, err2) + defer conn.Close(context.Background()) + + _, err2 = conn.Exec(ctx, "INSERT INTO users(name, age) VALUES ($1, $2)", "test", 42) + require.NoError(t, err2) + + var name string + var age int64 + err2 = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) + require.NoError(t, err2) + + require.Equal(t, "test", name) + require.EqualValues(t, 42, age) + }) + + t.Run("Test querying empty DB", func(t *testing.T) { + // 4. Run as many tests as you need, they will each get a clean database + t.Cleanup(func() { + err := ctr.Restore(ctx) + require.NoError(t, err) + }) + + conn, err2 := pgx.Connect(context.Background(), dbURL) + require.NoError(t, err2) + defer conn.Close(context.Background()) + + var name string + var age int64 + err2 = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) + require.ErrorIs(t, err2, pgx.ErrNoRows) + }) + // } +} diff --git a/modules/postgres/testdata/my-postgres.conf b/modules/postgres/testdata/my-postgres.conf index 27dd8b527e..128ef1aa8c 100644 --- a/modules/postgres/testdata/my-postgres.conf +++ b/modules/postgres/testdata/my-postgres.conf @@ -1,691 +1 @@ -# ----------------------------- -# PostgreSQL configuration file -# ----------------------------- -# -# This file consists of lines of the form: -# -# name = value -# -# (The "=" is optional.) Whitespace may be used. Comments are introduced with -# "#" anywhere on a line. The complete list of parameter names and allowed -# values can be found in the PostgreSQL documentation. -# -# The commented-out settings shown in this file represent the default values. -# Re-commenting a setting is NOT sufficient to revert it to the default value; -# you need to reload the server. -# -# This file is read on server startup and when the server receives a SIGHUP -# signal. If you edit the file on a running system, you have to SIGHUP the -# server for the changes to take effect, run "pg_ctl reload", or execute -# "SELECT pg_reload_conf()". Some parameters, which are marked below, -# require a server shutdown and restart to take effect. -# -# Any parameter can also be given as a command-line option to the server, e.g., -# "postgres -c log_connections=on". Some parameters can be changed at run time -# with the "SET" SQL command. -# -# Memory units: B = bytes Time units: ms = milliseconds -# kB = kilobytes s = seconds -# MB = megabytes min = minutes -# GB = gigabytes h = hours -# TB = terabytes d = days - - -#------------------------------------------------------------------------------ -# FILE LOCATIONS -#------------------------------------------------------------------------------ - -# The default values of these variables are driven from the -D command-line -# option or PGDATA environment variable, represented here as ConfigDir. - -#data_directory = 'ConfigDir' # use data in another directory - # (change requires restart) -#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file - # (change requires restart) -#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file - # (change requires restart) - -# If external_pid_file is not explicitly set, no extra PID file is written. -#external_pid_file = '' # write an extra PID file - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION -#------------------------------------------------------------------------------ - -# - Connection Settings - - listen_addresses = '*' - # comma-separated list of addresses; - # defaults to 'localhost'; use '*' for all - # (change requires restart) -#port = 5432 # (change requires restart) -#max_connections = 100 # (change requires restart) -#superuser_reserved_connections = 3 # (change requires restart) -#unix_socket_directories = '/tmp' # comma-separated list of directories - # (change requires restart) -#unix_socket_group = '' # (change requires restart) -#unix_socket_permissions = 0777 # begin with 0 to use octal notation - # (change requires restart) -#bonjour = off # advertise server via Bonjour - # (change requires restart) -#bonjour_name = '' # defaults to the computer name - # (change requires restart) - -# - TCP Keepalives - -# see "man 7 tcp" for details - -#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; - # 0 selects the system default -#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; - # 0 selects the system default -#tcp_keepalives_count = 0 # TCP_KEEPCNT; - # 0 selects the system default - -# - Authentication - - -#authentication_timeout = 1min # 1s-600s -#password_encryption = md5 # md5 or scram-sha-256 -#db_user_namespace = off - -# GSSAPI using Kerberos -#krb_server_keyfile = '' -#krb_caseins_users = off - -# - SSL - - -#ssl = off -#ssl_ca_file = '' -#ssl_cert_file = 'server.crt' -#ssl_crl_file = '' -#ssl_key_file = 'server.key' -#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers -#ssl_prefer_server_ciphers = on -#ssl_ecdh_curve = 'prime256v1' -#ssl_dh_params_file = '' -#ssl_passphrase_command = '' -#ssl_passphrase_command_supports_reload = off - - -#------------------------------------------------------------------------------ -# RESOURCE USAGE (except WAL) -#------------------------------------------------------------------------------ - -# - Memory - - -#shared_buffers = 32MB # min 128kB - # (change requires restart) -#huge_pages = try # on, off, or try - # (change requires restart) -#temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature - # (change requires restart) -# Caution: it is not advisable to set max_prepared_transactions nonzero unless -# you actively intend to use prepared transactions. -#work_mem = 4MB # min 64kB -#maintenance_work_mem = 64MB # min 1MB -#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem -#max_stack_depth = 2MB # min 100kB -#dynamic_shared_memory_type = posix # the default is the first option - # supported by the operating system: - # posix - # sysv - # windows - # mmap - # use none to disable dynamic shared memory - # (change requires restart) - -# - Disk - - -#temp_file_limit = -1 # limits per-process temp file space - # in kB, or -1 for no limit - -# - Kernel Resources - - -#max_files_per_process = 1000 # min 25 - # (change requires restart) - -# - Cost-Based Vacuum Delay - - -#vacuum_cost_delay = 0 # 0-100 milliseconds -#vacuum_cost_page_hit = 1 # 0-10000 credits -#vacuum_cost_page_miss = 10 # 0-10000 credits -#vacuum_cost_page_dirty = 20 # 0-10000 credits -#vacuum_cost_limit = 200 # 1-10000 credits - -# - Background Writer - - -#bgwriter_delay = 200ms # 10-10000ms between rounds -#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables -#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round -#bgwriter_flush_after = 0 # measured in pages, 0 disables - -# - Asynchronous Behavior - - -#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching -#max_worker_processes = 8 # (change requires restart) -#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers -#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers -#parallel_leader_participation = on -#max_parallel_workers = 8 # maximum number of max_worker_processes that - # can be used in parallel operations -#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate - # (change requires restart) -#backend_flush_after = 0 # measured in pages, 0 disables - - -#------------------------------------------------------------------------------ -# WRITE-AHEAD LOG -#------------------------------------------------------------------------------ - -# - Settings - - -#wal_level = replica # minimal, replica, or logical - # (change requires restart) -#fsync = on # flush data to disk for crash safety - # (turning this off can cause - # unrecoverable data corruption) -#synchronous_commit = on # synchronization level; - # off, local, remote_write, remote_apply, or on -#wal_sync_method = fsync # the default is the first option - # supported by the operating system: - # open_datasync - # fdatasync (default on Linux and FreeBSD) - # fsync - # fsync_writethrough - # open_sync -#full_page_writes = on # recover from partial page writes -#wal_compression = off # enable compression of full-page writes -#wal_log_hints = off # also do full page writes of non-critical updates - # (change requires restart) -#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers - # (change requires restart) -#wal_writer_delay = 200ms # 1-10000 milliseconds -#wal_writer_flush_after = 1MB # measured in pages, 0 disables - -#commit_delay = 0 # range 0-100000, in microseconds -#commit_siblings = 5 # range 1-1000 - -# - Checkpoints - - -#checkpoint_timeout = 5min # range 30s-1d -#max_wal_size = 1GB -#min_wal_size = 80MB -#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0 -#checkpoint_flush_after = 0 # measured in pages, 0 disables -#checkpoint_warning = 30s # 0 disables - -# - Archiving - - -#archive_mode = off # enables archiving; off, on, or always - # (change requires restart) -#archive_command = '' # command to use to archive a logfile segment - # placeholders: %p = path of file to archive - # %f = file name only - # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' -#archive_timeout = 0 # force a logfile segment switch after this - # number of seconds; 0 disables - - -#------------------------------------------------------------------------------ -# REPLICATION -#------------------------------------------------------------------------------ - -# - Sending Servers - - -# Set these on the master and on any standby that will send replication data. - -#max_wal_senders = 10 # max number of walsender processes - # (change requires restart) -#wal_keep_segments = 0 # in logfile segments; 0 disables -#wal_sender_timeout = 60s # in milliseconds; 0 disables - -#max_replication_slots = 10 # max number of replication slots - # (change requires restart) -#track_commit_timestamp = off # collect timestamp of transaction commit - # (change requires restart) - -# - Master Server - - -# These settings are ignored on a standby server. - -#synchronous_standby_names = '' # standby servers that provide sync rep - # method to choose sync standbys, number of sync standbys, - # and comma-separated list of application_name - # from standby(s); '*' = all -#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed - -# - Standby Servers - - -# These settings are ignored on a master server. - -#hot_standby = on # "off" disallows queries during recovery - # (change requires restart) -#max_standby_archive_delay = 30s # max delay before canceling queries - # when reading WAL from archive; - # -1 allows indefinite delay -#max_standby_streaming_delay = 30s # max delay before canceling queries - # when reading streaming WAL; - # -1 allows indefinite delay -#wal_receiver_status_interval = 10s # send replies at least this often - # 0 disables -#hot_standby_feedback = off # send info from standby to prevent - # query conflicts -#wal_receiver_timeout = 60s # time that receiver waits for - # communication from master - # in milliseconds; 0 disables -#wal_retrieve_retry_interval = 5s # time to wait before retrying to - # retrieve WAL after a failed attempt - -# - Subscribers - - -# These settings are ignored on a publisher. - -#max_logical_replication_workers = 4 # taken from max_worker_processes - # (change requires restart) -#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers - - -#------------------------------------------------------------------------------ -# QUERY TUNING -#------------------------------------------------------------------------------ - -# - Planner Method Configuration - - -#enable_bitmapscan = on -#enable_hashagg = on -#enable_hashjoin = on -#enable_indexscan = on -#enable_indexonlyscan = on -#enable_material = on -#enable_mergejoin = on -#enable_nestloop = on -#enable_parallel_append = on -#enable_seqscan = on -#enable_sort = on -#enable_tidscan = on -#enable_partitionwise_join = off -#enable_partitionwise_aggregate = off -#enable_parallel_hash = on -#enable_partition_pruning = on - -# - Planner Cost Constants - - -#seq_page_cost = 1.0 # measured on an arbitrary scale -#random_page_cost = 4.0 # same scale as above -#cpu_tuple_cost = 0.01 # same scale as above -#cpu_index_tuple_cost = 0.005 # same scale as above -#cpu_operator_cost = 0.0025 # same scale as above -#parallel_tuple_cost = 0.1 # same scale as above -#parallel_setup_cost = 1000.0 # same scale as above - -#jit_above_cost = 100000 # perform JIT compilation if available - # and query more expensive than this; - # -1 disables -#jit_inline_above_cost = 500000 # inline small functions if query is - # more expensive than this; -1 disables -#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if - # query is more expensive than this; - # -1 disables - -#min_parallel_table_scan_size = 8MB -#min_parallel_index_scan_size = 512kB -#effective_cache_size = 4GB - -# - Genetic Query Optimizer - - -#geqo = on -#geqo_threshold = 12 -#geqo_effort = 5 # range 1-10 -#geqo_pool_size = 0 # selects default based on effort -#geqo_generations = 0 # selects default based on effort -#geqo_selection_bias = 2.0 # range 1.5-2.0 -#geqo_seed = 0.0 # range 0.0-1.0 - -# - Other Planner Options - - -#default_statistics_target = 100 # range 1-10000 -#constraint_exclusion = partition # on, off, or partition -#cursor_tuple_fraction = 0.1 # range 0.0-1.0 -#from_collapse_limit = 8 -#join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses -#force_parallel_mode = off -#jit = off # allow JIT compilation - - -#------------------------------------------------------------------------------ -# REPORTING AND LOGGING -#------------------------------------------------------------------------------ - -# - Where to Log - - -#log_destination = 'stderr' # Valid values are combinations of - # stderr, csvlog, syslog, and eventlog, - # depending on platform. csvlog - # requires logging_collector to be on. - -# This is used when logging to stderr: -#logging_collector = off # Enable capturing of stderr and csvlog - # into log files. Required to be on for - # csvlogs. - # (change requires restart) - -# These are only used if logging_collector is on: -#log_directory = 'log' # directory where log files are written, - # can be absolute or relative to PGDATA -#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, - # can include strftime() escapes -#log_file_mode = 0600 # creation mode for log files, - # begin with 0 to use octal notation -#log_truncate_on_rotation = off # If on, an existing log file with the - # same name as the new log file will be - # truncated rather than appended to. - # But such truncation only occurs on - # time-driven rotation, not on restarts - # or size-driven rotation. Default is - # off, meaning append to existing files - # in all cases. -#log_rotation_age = 1d # Automatic rotation of logfiles will - # happen after that time. 0 disables. -#log_rotation_size = 10MB # Automatic rotation of logfiles will - # happen after that much log output. - # 0 disables. - -# These are relevant when logging to syslog: -#syslog_facility = 'LOCAL0' -#syslog_ident = 'postgres' -#syslog_sequence_numbers = on -#syslog_split_messages = on - -# This is only relevant when logging to eventlog (win32): -# (change requires restart) -#event_source = 'PostgreSQL' - -# - When to Log - - -#log_min_messages = warning # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic - -#log_min_error_statement = error # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic (effectively off) - -#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements - # and their durations, > 0 logs only - # statements running at least this number - # of milliseconds - - -# - What to Log - - -#debug_print_parse = off -#debug_print_rewritten = off -#debug_print_plan = off -#debug_pretty_print = on -#log_checkpoints = off -#log_connections = off -#log_disconnections = off -#log_duration = off -#log_error_verbosity = default # terse, default, or verbose messages -#log_hostname = off -#log_line_prefix = '%m [%p] ' # special values: - # %a = application name - # %u = user name - # %d = database name - # %r = remote host and port - # %h = remote host - # %p = process ID - # %t = timestamp without milliseconds - # %m = timestamp with milliseconds - # %n = timestamp with milliseconds (as a Unix epoch) - # %i = command tag - # %e = SQL state - # %c = session ID - # %l = session line number - # %s = session start timestamp - # %v = virtual transaction ID - # %x = transaction ID (0 if none) - # %q = stop here in non-session - # processes - # %% = '%' - # e.g. '<%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout -#log_statement = 'none' # none, ddl, mod, all -#log_replication_commands = off -#log_temp_files = -1 # log temporary files equal or larger - # than the specified size in kilobytes; - # -1 disables, 0 logs all temp files -#log_timezone = 'GMT' - -#------------------------------------------------------------------------------ -# PROCESS TITLE -#------------------------------------------------------------------------------ - -#cluster_name = '' # added to process titles if nonempty - # (change requires restart) -#update_process_title = on - - -#------------------------------------------------------------------------------ -# STATISTICS -#------------------------------------------------------------------------------ - -# - Query and Index Statistics Collector - - -#track_activities = on -#track_counts = on -#track_io_timing = off -#track_functions = none # none, pl, all -#track_activity_query_size = 1024 # (change requires restart) -#stats_temp_directory = 'pg_stat_tmp' - - -# - Monitoring - - -#log_parser_stats = off -#log_planner_stats = off -#log_executor_stats = off -#log_statement_stats = off - - -#------------------------------------------------------------------------------ -# AUTOVACUUM -#------------------------------------------------------------------------------ - -#autovacuum = on # Enable autovacuum subprocess? 'on' - # requires track_counts to also be on. -#log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and - # their durations, > 0 logs only - # actions running at least this number - # of milliseconds. -#autovacuum_max_workers = 3 # max number of autovacuum subprocesses - # (change requires restart) -#autovacuum_naptime = 1min # time between autovacuum runs -#autovacuum_vacuum_threshold = 50 # min number of row updates before - # vacuum -#autovacuum_analyze_threshold = 50 # min number of row updates before - # analyze -#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum -#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze -#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum - # (change requires restart) -#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age - # before forced vacuum - # (change requires restart) -#autovacuum_vacuum_cost_delay = 20ms # default vacuum cost delay for - # autovacuum, in milliseconds; - # -1 means use vacuum_cost_delay -#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for - # autovacuum, -1 means use - # vacuum_cost_limit - - -#------------------------------------------------------------------------------ -# CLIENT CONNECTION DEFAULTS -#------------------------------------------------------------------------------ - -# - Statement Behavior - - -#client_min_messages = notice # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # log - # notice - # warning - # error -#search_path = '"$user", public' # schema names -#row_security = on -#default_tablespace = '' # a tablespace name, '' uses the default -#temp_tablespaces = '' # a list of tablespace names, '' uses - # only default tablespace -#check_function_bodies = on -#default_transaction_isolation = 'read committed' -#default_transaction_read_only = off -#default_transaction_deferrable = off -#session_replication_role = 'origin' -#statement_timeout = 0 # in milliseconds, 0 is disabled -#lock_timeout = 0 # in milliseconds, 0 is disabled -#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled -#vacuum_freeze_min_age = 50000000 -#vacuum_freeze_table_age = 150000000 -#vacuum_multixact_freeze_min_age = 5000000 -#vacuum_multixact_freeze_table_age = 150000000 -#vacuum_cleanup_index_scale_factor = 0.1 # fraction of total number of tuples - # before index cleanup, 0 always performs - # index cleanup -#bytea_output = 'hex' # hex, escape -#xmlbinary = 'base64' -#xmloption = 'content' -#gin_fuzzy_search_limit = 0 -#gin_pending_list_limit = 4MB - -# - Locale and Formatting - - -#datestyle = 'iso, mdy' -#intervalstyle = 'postgres' -#timezone = 'GMT' -#timezone_abbreviations = 'Default' # Select the set of available time zone - # abbreviations. Currently, there are - # Default - # Australia (historical usage) - # India - # You can create your own file in - # share/timezonesets/. -#extra_float_digits = 0 # min -15, max 3 -#client_encoding = sql_ascii # actually, defaults to database - # encoding - -# These settings are initialized by initdb, but they can be changed. -#lc_messages = 'C' # locale for system error message - # strings -#lc_monetary = 'C' # locale for monetary formatting -#lc_numeric = 'C' # locale for number formatting -#lc_time = 'C' # locale for time formatting - -# default configuration for text search -#default_text_search_config = 'pg_catalog.simple' - -# - Shared Library Preloading - - -#shared_preload_libraries = '' # (change requires restart) -#local_preload_libraries = '' -#session_preload_libraries = '' -#jit_provider = 'llvmjit' # JIT library to use - -# - Other Defaults - - -#dynamic_library_path = '$libdir' - - -#------------------------------------------------------------------------------ -# LOCK MANAGEMENT -#------------------------------------------------------------------------------ - -#deadlock_timeout = 1s -#max_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_relation = -2 # negative values mean - # (max_pred_locks_per_transaction - # / -max_pred_locks_per_relation) - 1 -#max_pred_locks_per_page = 2 # min 0 - - -#------------------------------------------------------------------------------ -# VERSION AND PLATFORM COMPATIBILITY -#------------------------------------------------------------------------------ - -# - Previous PostgreSQL Versions - - -#array_nulls = on -#backslash_quote = safe_encoding # on, off, or safe_encoding -#default_with_oids = off -#escape_string_warning = on -#lo_compat_privileges = off -#operator_precedence_warning = off -#quote_all_identifiers = off -#standard_conforming_strings = on -#synchronize_seqscans = on - -# - Other Platforms and Clients - - -#transform_null_equals = off - - -#------------------------------------------------------------------------------ -# ERROR HANDLING -#------------------------------------------------------------------------------ - -#exit_on_error = off # terminate session on any error? -#restart_after_crash = on # reinitialize after backend crash? -#data_sync_retry = off # retry or panic on failure to fsync - # data? - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONFIG FILE INCLUDES -#------------------------------------------------------------------------------ - -# These options allow settings to be loaded from files other than the -# default postgresql.conf. Note that these are directives, not variable -# assignments, so they can usefully be given more than once. - -#include_dir = '...' # include files ending in '.conf' from - # a directory, e.g., 'conf.d' -#include_if_exists = '...' # include file only if it exists -#include = '...' # include file - - -#------------------------------------------------------------------------------ -# CUSTOMIZED OPTIONS -#------------------------------------------------------------------------------ - -# Add settings for extensions here diff --git a/modules/postgres/wait_strategies.go b/modules/postgres/wait_strategies.go new file mode 100644 index 0000000000..e748f4cc3a --- /dev/null +++ b/modules/postgres/wait_strategies.go @@ -0,0 +1,27 @@ +package postgres + +import ( + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// BasicWaitStrategies is a simple but reliable way to wait for postgres to start. +// It returns a two-step wait strategy: +// +// - It will wait for the container to log `database system is ready to accept connections` twice, because it will restart itself after the first startup. +// - It will then wait for docker to actually serve the port on localhost. +// For non-linux OSes like Mac and Windows, Docker or Rancher Desktop will have to start a separate proxy. +// Without this, the tests will be flaky on those OSes! +func BasicWaitStrategies() testcontainers.CustomizeRequestOption { + // waitStrategy { + return testcontainers.WithWaitStrategy( + // First, we wait for the container to log readiness twice. + // This is because it will restart itself after the first startup. + wait.ForLog("database system is ready to accept connections").WithOccurrence(2), + // Then, we wait for docker to actually serve the port on localhost. + // For non-linux OSes like Mac and Windows, Docker or Rancher Desktop will have to start a separate proxy. + // Without this, the tests will be flaky on those OSes! + wait.ForListeningPort("5432/tcp"), + ) + // } +} diff --git a/modules/pulsar/examples_test.go b/modules/pulsar/examples_test.go index 94e76c5f19..9561914207 100644 --- a/modules/pulsar/examples_test.go +++ b/modules/pulsar/examples_test.go @@ -9,28 +9,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/pulsar" ) -func ExampleRunContainer() { +func ExampleRun() { // runPulsarContainer { ctx := context.Background() - pulsarContainer, err := pulsar.RunContainer(ctx, - testcontainers.WithImage("docker.io/apachepulsar/pulsar:2.10.2"), - ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + pulsarContainer, err := pulsar.Run(ctx, "apachepulsar/pulsar:2.10.2") defer func() { - if err := pulsarContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(pulsarContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := pulsarContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/pulsar/go.mod b/modules/pulsar/go.mod index 6c91618cad..e65e5f8da4 100644 --- a/modules/pulsar/go.mod +++ b/modules/pulsar/go.mod @@ -1,13 +1,13 @@ module github.com/testcontainers/testcontainers-go/modules/pulsar -go 1.21 +go 1.22 require ( github.com/apache/pulsar-client-go v0.10.0 - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) replace github.com/testcontainers/testcontainers-go => ../.. @@ -19,19 +19,19 @@ require ( github.com/AthenZ/athenz v1.10.39 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/zstd v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ardielle/ardielle-go v1.5.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.4.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -45,12 +45,13 @@ require ( github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/text v0.2.0 // indirect github.com/linkedin/goavro/v2 v2.9.8 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -79,16 +80,15 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.22.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/pulsar/go.sum b/modules/pulsar/go.sum index 1b4ab1963c..0d29f7e91b 100644 --- a/modules/pulsar/go.sum +++ b/modules/pulsar/go.sum @@ -47,10 +47,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -80,12 +78,14 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -96,10 +96,10 @@ 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/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA= github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -224,8 +224,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -246,6 +246,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -353,6 +355,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -385,8 +388,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -417,6 +421,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -448,12 +453,14 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -461,8 +468,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -473,8 +480,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -518,16 +524,18 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -535,13 +543,14 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -584,8 +593,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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= @@ -612,8 +620,8 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -643,11 +651,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -660,8 +667,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -698,8 +705,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/modules/pulsar/pulsar.go b/modules/pulsar/pulsar.go index bdb45ab69e..be9ea10925 100644 --- a/modules/pulsar/pulsar.go +++ b/modules/pulsar/pulsar.go @@ -13,7 +13,6 @@ import ( ) const ( - defaultPulsarImage = "docker.io/apachepulsar/pulsar:2.10.2" defaultPulsarPort = "6650/tcp" defaultPulsarAdminPort = "8080/tcp" defaultPulsarCmd = "/pulsar/bin/apply-config-from-env.py /pulsar/conf/standalone.conf && bin/pulsar standalone" @@ -130,18 +129,24 @@ func WithTransactions() testcontainers.CustomizeRequestOption { } } -// RunContainer creates an instance of the Pulsar container type, being possible to pass a custom request and options +// Deprecated: use Run instead +// RunContainer creates an instance of the Pulsar container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) { + return Run(ctx, "apachepulsar/pulsar:2.10.2", opts...) +} + +// Run creates an instance of the Pulsar container type, being possible to pass a custom request and options // The created container will use the following defaults: -// - image: docker.io/apachepulsar/pulsar:2.10.2 +// - image: apachepulsar/pulsar:2.10.2 // - exposed ports: 6650/tcp, 8080/tcp // - waiting strategy: wait for all the following strategies: // - the Pulsar admin API ("/admin/v2/clusters") to be ready on port 8080/tcp and return the response `["standalone"]` // - the log message "Successfully updated the policies on namespace public/default" // // - command: "/bin/bash -c /pulsar/bin/apply-config-from-env.py /pulsar/conf/standalone.conf && bin/pulsar standalone --no-functions-worker -nss" -func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) { +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { req := testcontainers.ContainerRequest{ - Image: defaultPulsarImage, + Image: img, Env: map[string]string{}, ExposedPorts: []string{defaultPulsarPort, defaultPulsarAdminPort}, WaitingFor: defaultWaitStrategies, @@ -159,14 +164,15 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } } - c, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Container + if container != nil { + c = &Container{Container: container} } - pc := &Container{ - Container: c, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return pc, nil + return c, nil } diff --git a/modules/pulsar/pulsar_test.go b/modules/pulsar/pulsar_test.go index 30ef07aa74..de2c4bd437 100644 --- a/modules/pulsar/pulsar_test.go +++ b/modules/pulsar/pulsar_test.go @@ -3,7 +3,6 @@ package pulsar_test import ( "context" "encoding/json" - "fmt" "io" "net/http" "strings" @@ -11,6 +10,7 @@ import ( "time" "github.com/apache/pulsar-client-go/pulsar" + "github.com/apache/pulsar-client-go/pulsar/log" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/stretchr/testify/assert" @@ -21,12 +21,20 @@ import ( tcnetwork "github.com/testcontainers/testcontainers-go/network" ) +// noopLogConsumer implements testcontainers.LogConsumer +// and does nothing with the logs. +type noopLogConsumer struct{} + +// Accept implements testcontainers.LogConsumer. +func (*noopLogConsumer) Accept(testcontainers.Log) {} + func TestPulsar(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - nw, err := tcnetwork.New(ctx, tcnetwork.WithCheckDuplicate()) + nw, err := tcnetwork.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, nw) nwName := nw.Name @@ -40,7 +48,6 @@ func TestPulsar(t *testing.T) { { name: "with modifiers", opts: []testcontainers.ContainerCustomizer{ - testcontainers.WithImage("docker.io/apachepulsar/pulsar:2.10.2"), // addPulsarEnv { testcontainerspulsar.WithPulsarEnv("brokerDeduplicationEnabled", "true"), // } @@ -81,7 +88,7 @@ func TestPulsar(t *testing.T) { name: "with log consumers", opts: []testcontainers.ContainerCustomizer{ // withLogconsumers { - testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{}), + testcontainers.WithLogConsumers(&noopLogConsumer{}), // } }, }, @@ -89,15 +96,13 @@ func TestPulsar(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c, err := testcontainerspulsar.RunContainer( + c, err := testcontainerspulsar.Run( ctx, + "apachepulsar/pulsar:2.10.2", tt.opts..., ) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err := c.Terminate(ctx) - require.NoError(t, err) - }() // getBrokerURL { brokerURL, err := c.BrokerURL(ctx) @@ -116,6 +121,7 @@ func TestPulsar(t *testing.T) { URL: brokerURL, OperationTimeout: 30 * time.Second, ConnectionTimeout: 30 * time.Second, + Logger: log.DefaultNopLogger(), }) require.NoError(t, err) t.Cleanup(func() { pc.Close() }) @@ -134,13 +140,13 @@ func TestPulsar(t *testing.T) { go func() { msg, err := consumer.Receive(ctx) if err != nil { - fmt.Println("failed to receive message", err) + t.Log("failed to receive message", err) return } msgChan <- msg.Payload() err = consumer.Ack(msg) if err != nil { - fmt.Println("failed to send ack", err) + t.Log("failed to send ack", err) return } }() @@ -160,9 +166,7 @@ func TestPulsar(t *testing.T) { case <-ticker.C: t.Fatal("did not receive message in time") case msg := <-msgChan: - if string(msg) != "hello world" { - t.Fatal("received unexpected message bytes") - } + require.Equalf(t, "hello world", string(msg), "received unexpected message bytes") } // get topic statistics using the Admin endpoint @@ -188,14 +192,7 @@ func TestPulsar(t *testing.T) { // check that the subscription exists _, ok := subscriptionsMap[subscriptionName] - assert.True(t, ok) + require.True(t, ok) }) } - - // remove the network after the last, so that all containers are already removed - // and there are no active endpoints on the network - t.Cleanup(func() { - err := nw.Remove(context.Background()) - require.NoError(t, err) - }) } diff --git a/modules/qdrant/examples_test.go b/modules/qdrant/examples_test.go index c21f5812ab..85bd2763de 100644 --- a/modules/qdrant/examples_test.go +++ b/modules/qdrant/examples_test.go @@ -14,26 +14,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/qdrant" ) -func ExampleRunContainer() { +func ExampleRun() { // runQdrantContainer { ctx := context.Background() - qdrantContainer, err := qdrant.RunContainer(ctx, testcontainers.WithImage("qdrant/qdrant:v1.7.4")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + qdrantContainer, err := qdrant.Run(ctx, "qdrant/qdrant:v1.7.4") defer func() { - if err := qdrantContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(qdrantContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := qdrantContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -42,28 +42,30 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_createPoints() { +func ExampleRun_createPoints() { // fullExample { - qdrantContainer, err := qdrant.RunContainer(context.Background(), testcontainers.WithImage("qdrant/qdrant:v1.7.4")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - + qdrantContainer, err := qdrant.Run(context.Background(), "qdrant/qdrant:v1.7.4") defer func() { - if err := qdrantContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(qdrantContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } grpcEndpoint, err := qdrantContainer.GRPCEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get gRPC endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC endpoint: %s", err) + return } // Set up a connection to the server. - conn, err := grpc.Dial(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Fatalf("did not connect: %v", err) + log.Printf("did not connect: %v", err) + return } defer conn.Close() @@ -90,7 +92,8 @@ func ExampleRunContainer_createPoints() { }, }) if err != nil { - log.Fatalf("Could not create collection: %v", err) + log.Printf("Could not create collection: %v", err) + return } // 2. Contact the server and print out its response. @@ -98,7 +101,8 @@ func ExampleRunContainer_createPoints() { defer cancel() r, err := collections_client.List(ctx, &pb.ListCollectionsRequest{}) if err != nil { - log.Fatalf("could not get collections: %v", err) + log.Printf("could not get collections: %v", err) + return } fmt.Printf("List of collections: %s\n", r.GetCollections()) @@ -114,7 +118,8 @@ func ExampleRunContainer_createPoints() { FieldType: &fieldIndex1Type, }) if err != nil { - log.Fatalf("Could not create field index: %v", err) + log.Printf("Could not create field index: %v", err) + return } // 5. Create integer field index @@ -126,7 +131,8 @@ func ExampleRunContainer_createPoints() { FieldType: &fieldIndex2Type, }) if err != nil { - log.Fatalf("Could not create field index: %v", err) + log.Printf("Could not create field index: %v", err) + return } // 6. Upsert points @@ -259,7 +265,8 @@ func ExampleRunContainer_createPoints() { Points: upsertPoints, }) if err != nil { - log.Fatalf("Could not upsert points: %v", err) + log.Printf("Could not upsert points: %v", err) + return } // 7. Retrieve points by ids @@ -271,7 +278,8 @@ func ExampleRunContainer_createPoints() { }, }) if err != nil { - log.Fatalf("Could not retrieve points: %v", err) + log.Printf("Could not retrieve points: %v", err) + return } fmt.Printf("Retrieved points: %d\n", len(pointsById.GetResult())) @@ -286,7 +294,8 @@ func ExampleRunContainer_createPoints() { WithPayload: &pb.WithPayloadSelector{SelectorOptions: &pb.WithPayloadSelector_Enable{Enable: true}}, }) if err != nil { - log.Fatalf("Could not search points: %v", err) + log.Printf("Could not search points: %v", err) + return } fmt.Printf("Found points: %d\n", len(unfilteredSearchResult.GetResult())) @@ -314,7 +323,8 @@ func ExampleRunContainer_createPoints() { }, }) if err != nil { - log.Fatalf("Could not search points: %v", err) + log.Printf("Could not search points: %v", err) + return } // } diff --git a/modules/qdrant/go.mod b/modules/qdrant/go.mod index 4291fa74e0..d5b28b795a 100644 --- a/modules/qdrant/go.mod +++ b/modules/qdrant/go.mod @@ -1,24 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/qdrant -go 1.21 +go 1.22 require ( github.com/qdrant/go-client v1.7.0 - github.com/testcontainers/testcontainers-go v0.30.0 - google.golang.org/grpc v1.62.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 + google.golang.org/grpc v1.64.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -26,11 +28,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -50,14 +54,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/qdrant/go.sum b/modules/qdrant/go.sum index b6afcf895d..98e982016f 100644 --- a/modules/qdrant/go.sum +++ b/modules/qdrant/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/qdrant/go-client v1.7.0 h1:2TeeWyZAWIup7vvD7Ne6aAvo0H+F5OUb1pB9Z8Y4pFk= github.com/qdrant/go-client v1.7.0/go.mod h1:680gkxNAsVtre0Z8hAQmtPzJtz1xFAyCu2TUxULtnoE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -124,23 +133,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -151,38 +156,38 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/qdrant/qdrant.go b/modules/qdrant/qdrant.go index c5a4244707..d4eb11b95a 100644 --- a/modules/qdrant/qdrant.go +++ b/modules/qdrant/qdrant.go @@ -2,6 +2,7 @@ package qdrant import ( "context" + "errors" "fmt" "time" @@ -14,10 +15,16 @@ type QdrantContainer struct { testcontainers.Container } +// Deprecated: use Run instead // RunContainer creates an instance of the Qdrant container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*QdrantContainer, error) { + return Run(ctx, "qdrant/qdrant:v1.7.4", opts...) +} + +// Run creates an instance of the Qdrant container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*QdrantContainer, error) { req := testcontainers.ContainerRequest{ - Image: "qdrant/qdrant:v1.7.4", + Image: img, ExposedPorts: []string{"6333/tcp", "6334/tcp"}, WaitingFor: wait.ForAll( wait.ForListeningPort("6333/tcp").WithStartupTimeout(5*time.Second), @@ -37,11 +44,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *QdrantContainer + if container != nil { + c = &QdrantContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &QdrantContainer{Container: container}, nil + return c, nil } // RESTEndpoint returns the REST endpoint of the Qdrant container @@ -53,7 +65,7 @@ func (c *QdrantContainer) RESTEndpoint(ctx context.Context) (string, error) { host, err := c.Host(ctx) if err != nil { - return "", fmt.Errorf("failed to get container host") + return "", errors.New("failed to get container host") } return fmt.Sprintf("http://%s:%s", host, containerPort.Port()), nil @@ -68,7 +80,7 @@ func (c *QdrantContainer) GRPCEndpoint(ctx context.Context) (string, error) { host, err := c.Host(ctx) if err != nil { - return "", fmt.Errorf("failed to get container host") + return "", errors.New("failed to get container host") } return fmt.Sprintf("%s:%s", host, containerPort.Port()), nil diff --git a/modules/qdrant/qdrant_test.go b/modules/qdrant/qdrant_test.go index 7295580c73..63b95d30ea 100644 --- a/modules/qdrant/qdrant_test.go +++ b/modules/qdrant/qdrant_test.go @@ -5,6 +5,7 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -15,70 +16,46 @@ import ( func TestQdrant(t *testing.T) { ctx := context.Background() - container, err := qdrant.RunContainer(ctx, testcontainers.WithImage("qdrant/qdrant:v1.7.4")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := qdrant.Run(ctx, "qdrant/qdrant:v1.7.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("REST Endpoint", func(tt *testing.T) { // restEndpoint { - restEndpoint, err := container.RESTEndpoint(ctx) + restEndpoint, err := ctr.RESTEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(restEndpoint) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("gRPC Endpoint", func(tt *testing.T) { // gRPCEndpoint { - grpcEndpoint, err := container.GRPCEndpoint(ctx) + grpcEndpoint, err := ctr.GRPCEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) - conn, err := grpc.Dial(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - t.Fatalf("did not connect: %v", err) - } + conn, err := grpc.NewClient(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) defer conn.Close() }) t.Run("Web UI", func(tt *testing.T) { // webUIEndpoint { - webUI, err := container.WebUI(ctx) + webUI, err := ctr.WebUI(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(webUI) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) } diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index 55fcd143da..b9c4e9fdf2 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -15,30 +15,30 @@ import ( "github.com/testcontainers/testcontainers-go/modules/rabbitmq" ) -func ExampleRunContainer() { +func ExampleRun() { // runRabbitMQContainer { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.12.11-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.12.11-management-alpine", rabbitmq.WithAdminUsername("admin"), rabbitmq.WithAdminPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := rabbitmqContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -47,36 +47,39 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connectUsingAmqp() { +func ExampleRun_connectUsingAmqp() { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.7.25-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.7.25-management-alpine", rabbitmq.WithAdminUsername("admin"), rabbitmq.WithAdminPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } amqpURL, err := rabbitmqContainer.AmqpURL(ctx) if err != nil { - log.Fatalf("failed to get AMQP URL: %s", err) // nolint:gocritic + log.Printf("failed to get AMQP URL: %s", err) + return } amqpConnection, err := amqp.Dial(amqpURL) if err != nil { - log.Fatalf("failed to connect to RabbitMQ: %s", err) + log.Printf("failed to connect to RabbitMQ: %s", err) + return } defer func() { err := amqpConnection.Close() if err != nil { - log.Fatalf("failed to close connection: %s", err) + log.Printf("failed to close connection: %s", err) } }() @@ -86,18 +89,20 @@ func ExampleRunContainer_connectUsingAmqp() { // false } -func ExampleRunContainer_withSSL() { +func ExampleRun_withSSL() { // enableSSL { ctx := context.Background() tmpDir := os.TempDir() certDirs := tmpDir + "/rabbitmq" - if err := os.MkdirAll(certDirs, 0755); err != nil { - log.Fatalf("failed to create temporary directory: %s", err) + if err := os.MkdirAll(certDirs, 0o755); err != nil { + log.Printf("failed to create temporary directory: %s", err) + return } 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", @@ -105,9 +110,12 @@ func ExampleRunContainer_withSSL() { ParentDir: certDirs, }) if caCert == nil { - log.Fatal("failed to generate CA certificate") + log.Print("failed to generate CA certificate") + return } + // } + // exampleSignSelfSignedCert { cert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "client", Host: "localhost,127.0.0.1", @@ -116,8 +124,10 @@ func ExampleRunContainer_withSSL() { ParentDir: certDirs, }) if cert == nil { - log.Fatal("failed to generate certificate") + log.Print("failed to generate certificate") + return } + // } sslSettings := rabbitmq.SSLSettings{ CACertFile: caCert.CertPath, @@ -128,24 +138,25 @@ func ExampleRunContainer_withSSL() { VerificationDepth: 1, } - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.7.25-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.7.25-management-alpine", rabbitmq.WithSSL(sslSettings), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := rabbitmqContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -154,11 +165,11 @@ func ExampleRunContainer_withSSL() { // true } -func ExampleRunContainer_withPlugins() { +func ExampleRun_withPlugins() { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.7.25-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.7.25-management-alpine", // Multiple test implementations of the Executable interface, specific to RabbitMQ, exist in the types_test.go file. // Please refer to them for more examples. testcontainers.WithAfterReadyCommand( @@ -166,46 +177,53 @@ func ExampleRunContainer_withPlugins() { testcontainers.NewRawCommand([]string{"rabbitmq_random_exchange"}), ), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - fmt.Println(assertPlugins(rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange")) + if err = assertPlugins(rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange"); err != nil { + log.Printf("failed to find plugin: %s", err) + return + } + + fmt.Println(true) // Output: // true } -func ExampleRunContainer_withCustomConfigFile() { +func ExampleRun_withCustomConfigFile() { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.7.25-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.7.25-management-alpine", ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } logs, err := rabbitmqContainer.Logs(ctx) if err != nil { - log.Fatalf("failed to get logs: %s", err) // nolint:gocritic + log.Printf("failed to get logs: %s", err) + return } bytes, err := io.ReadAll(logs) if err != nil { - log.Fatalf("failed to read logs: %s", err) + log.Printf("failed to read logs: %s", err) + return } fmt.Println(strings.Contains(string(bytes), "config file(s) : /etc/rabbitmq/rabbitmq-testcontainers.conf")) @@ -214,25 +232,24 @@ func ExampleRunContainer_withCustomConfigFile() { // true } -func assertPlugins(container testcontainers.Container, plugins ...string) bool { +func assertPlugins(container testcontainers.Container, plugins ...string) error { ctx := context.Background() for _, plugin := range plugins { - _, out, err := container.Exec(ctx, []string{"rabbitmq-plugins", "is_enabled", plugin}) if err != nil { - log.Fatalf("failed to execute command: %s", err) + return fmt.Errorf("failed to execute command: %w", err) } check, err := io.ReadAll(out) if err != nil { - log.Fatalf("failed to read output: %s", err) + return fmt.Errorf("failed to read output: %w", err) } if !strings.Contains(string(check), plugin+" is enabled") { - return false + return fmt.Errorf("plugin %q is not enabled", plugin) } } - return true + return nil } diff --git a/modules/rabbitmq/go.mod b/modules/rabbitmq/go.mod index 1ec1549363..de71df8704 100644 --- a/modules/rabbitmq/go.mod +++ b/modules/rabbitmq/go.mod @@ -1,38 +1,38 @@ module github.com/testcontainers/testcontainers-go/modules/rabbitmq -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/rabbitmq/amqp091-go v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) -require golang.org/x/crypto v0.22.0 // indirect - require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // 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/docker-image-spec v1.3.1 // indirect 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 @@ -41,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -52,12 +53,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index de5f9a6d17..e7d5675b93 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -4,27 +4,27 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +40,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,10 +50,13 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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= @@ -63,6 +64,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V 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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -85,6 +88,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -96,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -131,23 +138,19 @@ go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -158,39 +161,38 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index 612c760d90..32d18c09a9 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -66,10 +66,16 @@ func (c *RabbitMQContainer) HttpsURL(ctx context.Context) (string, error) { return c.PortEndpoint(ctx, nat.Port(DefaultHTTPSPort), "https") } +// Deprecated: use Run instead // RunContainer creates an instance of the RabbitMQ container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RabbitMQContainer, error) { + return Run(ctx, "rabbitmq:3.12.11-management-alpine", opts...) +} + +// Run creates an instance of the RabbitMQ container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RabbitMQContainer, error) { req := testcontainers.ContainerRequest{ - Image: "rabbitmq:3.12.11-management-alpine", + Image: img, Env: map[string]string{ "RABBITMQ_DEFAULT_USER": defaultUser, "RABBITMQ_DEFAULT_PASS": defaultPassword, @@ -127,14 +133,17 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *RabbitMQContainer + if container != nil { + c = &RabbitMQContainer{ + Container: container, + AdminUsername: settings.AdminUsername, + AdminPassword: settings.AdminPassword, + } } - c := &RabbitMQContainer{ - Container: container, - AdminUsername: settings.AdminUsername, - AdminPassword: settings.AdminPassword, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } return c, nil diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index f1f03f8ba9..1cdd7e9137 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -5,13 +5,13 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io" "os" "strings" "testing" "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" @@ -20,30 +20,18 @@ import ( func TestRunContainer_connectUsingAmqp(t *testing.T) { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + rabbitmqContainer, err := rabbitmq.Run(ctx, "rabbitmq:3.12.11-management-alpine") + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) amqpURL, err := rabbitmqContainer.AmqpURL(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) amqpConnection, err := amqp.Dial(amqpURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if err = amqpConnection.Close(); err != nil { - t.Fatal(err) - } + err = amqpConnection.Close() + require.NoError(t, err) } func TestRunContainer_connectUsingAmqps(t *testing.T) { @@ -57,9 +45,7 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { IsCA: true, ParentDir: tmpDir, }) - if caCert == nil { - t.Fatal("failed to generate CA certificate") - } + require.NotNilf(t, caCert, "failed to generate CA certificate") cert := tlscert.SelfSignedFromRequest(tlscert.Request{ Name: "client", @@ -68,9 +54,7 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { Parent: caCert, ParentDir: tmpDir, }) - if cert == nil { - t.Fatal("failed to generate certificate") - } + require.NotNilf(t, cert, "failed to generate certificate") sslSettings := rabbitmq.SSLSettings{ CACertFile: caCert.CertPath, @@ -81,52 +65,34 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { VerificationDepth: 1, } - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, rabbitmq.WithSSL(sslSettings)) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + rabbitmqContainer, err := rabbitmq.Run(ctx, "rabbitmq:3.12.11-management-alpine", rabbitmq.WithSSL(sslSettings)) + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) amqpsURL, err := rabbitmqContainer.AmqpsURL(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !strings.HasPrefix(amqpsURL, "amqps") { - t.Fatal(fmt.Errorf("AMQPS Url should begin with `amqps`")) - } + require.Truef(t, strings.HasPrefix(amqpsURL, "amqps"), "AMQPS Url should begin with `amqps`") certs := x509.NewCertPool() pemData, err := os.ReadFile(sslSettings.CACertFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) certs.AppendCertsFromPEM(pemData) amqpsConnection, err := amqp.DialTLS(amqpsURL, &tls.Config{InsecureSkipVerify: false, RootCAs: certs}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if amqpsConnection.IsClosed() { - t.Fatal(fmt.Errorf("AMQPS Connection unexpectdely closed")) - } - if err = amqpsConnection.Close(); err != nil { - t.Fatal(err) - } + require.Falsef(t, amqpsConnection.IsClosed(), "AMQPS Connection unexpectdely closed") + err = amqpsConnection.Close() + require.NoError(t, err) } func TestRunContainer_withAllSettings(t *testing.T) { ctx := context.Background() - rabbitmqContainer, err := rabbitmq.RunContainer(ctx, - testcontainers.WithImage("rabbitmq:3.12.11-management-alpine"), + rabbitmqContainer, err := rabbitmq.Run(ctx, + "rabbitmq:3.12.11-management-alpine", // addVirtualHosts { testcontainers.WithAfterReadyCommand(VirtualHost{Name: "vhost1"}), testcontainers.WithAfterReadyCommand(VirtualHostLimit{VHost: "vhost1", Name: "max-connections", Value: 1}), @@ -238,66 +204,32 @@ func TestRunContainer_withAllSettings(t *testing.T) { testcontainers.WithAfterReadyCommand(Plugin{Name: "rabbitmq_shovel"}, Plugin{Name: "rabbitmq_random_exchange"}), // } ) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() - - if !assertEntity(t, rabbitmqContainer, "queues", "queue1", "queue2", "queue3", "queue4") { - t.Fatal(err) - } - if !assertEntity(t, rabbitmqContainer, "exchanges", "direct-exchange", "topic-exchange", "topic-exchange-2", "topic-exchange-3", "topic-exchange-4") { - t.Fatal(err) - } - if !assertEntity(t, rabbitmqContainer, "users", "user1", "user2") { - t.Fatal(err) - } - if !assertEntity(t, rabbitmqContainer, "policies", "max length policy", "alternate exchange policy") { - t.Fatal(err) - } - if !assertEntityWithVHost(t, rabbitmqContainer, "policies", 2, "max length policy", "alternate exchange policy") { - t.Fatal(err) - } - if !assertEntity(t, rabbitmqContainer, "operator_policies", "operator policy 1") { - t.Fatal(err) - } - if !assertPluginIsEnabled(t, rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange") { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) + + requireEntity(t, rabbitmqContainer, "queues", "queue1", "queue2", "queue3", "queue4") + requireEntity(t, rabbitmqContainer, "exchanges", "direct-exchange", "topic-exchange", "topic-exchange-2", "topic-exchange-3", "topic-exchange-4") + requireEntity(t, rabbitmqContainer, "users", "user1", "user2") + requireEntity(t, rabbitmqContainer, "policies", "max length policy", "alternate exchange policy") + requireEntityWithVHost(t, rabbitmqContainer, "policies", 2, "max length policy", "alternate exchange policy") + requireEntity(t, rabbitmqContainer, "operator_policies", "operator policy 1") + requirePluginIsEnabled(t, rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange") } -func assertEntity(t *testing.T, container testcontainers.Container, listCommand string, entities ...string) bool { +func requireEntity(t *testing.T, container testcontainers.Container, listCommand string, entities ...string) { t.Helper() ctx := context.Background() cmd := []string{"rabbitmqadmin", "list", listCommand} - _, out, err := container.Exec(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - check, err := io.ReadAll(out) - if err != nil { - t.Fatal(err) - } - + check := testcontainers.RequireContainerExec(ctx, t, container, cmd) for _, e := range entities { - if !strings.Contains(string(check), e) { - return false - } + require.Contains(t, check, e) } - - return true } -func assertEntityWithVHost(t *testing.T, container testcontainers.Container, listCommand string, vhostID int, entities ...string) bool { +func requireEntityWithVHost(t *testing.T, container testcontainers.Container, listCommand string, vhostID int, entities ...string) { t.Helper() ctx := context.Background() @@ -307,46 +239,22 @@ func assertEntityWithVHost(t *testing.T, container testcontainers.Container, lis cmd = append(cmd, fmt.Sprintf("--vhost=vhost%d", vhostID)) } - _, out, err := container.Exec(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - check, err := io.ReadAll(out) - if err != nil { - t.Fatal(err) - } - + check := testcontainers.RequireContainerExec(ctx, t, container, cmd) for _, e := range entities { - if !strings.Contains(string(check), e) { - return false - } + require.Contains(t, check, e) } - - return true } -func assertPluginIsEnabled(t *testing.T, container testcontainers.Container, plugins ...string) bool { +func requirePluginIsEnabled(t *testing.T, container testcontainers.Container, plugins ...string) { t.Helper() ctx := context.Background() for _, plugin := range plugins { - _, out, err := container.Exec(ctx, []string{"rabbitmq-plugins", "is_enabled", plugin}) - if err != nil { - t.Fatal(err) - } + cmd := []string{"rabbitmq-plugins", "is_enabled", plugin} - check, err := io.ReadAll(out) - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(string(check), plugin+" is enabled") { - return false - } + check := testcontainers.RequireContainerExec(ctx, t, container, cmd) + require.Contains(t, check, plugin+" is enabled") } - - return true } diff --git a/modules/rabbitmq/types_test.go b/modules/rabbitmq/types_test.go index 8b607f6632..e29bf2d88a 100644 --- a/modules/rabbitmq/types_test.go +++ b/modules/rabbitmq/types_test.go @@ -46,16 +46,16 @@ func (b Binding) AsCommand() []string { cmd := []string{"rabbitmqadmin"} if b.VHost != "" { - cmd = append(cmd, fmt.Sprintf("--vhost=%s", b.VHost)) + cmd = append(cmd, "--vhost="+b.VHost) } - cmd = append(cmd, "declare", "binding", fmt.Sprintf("source=%s", b.Source), fmt.Sprintf("destination=%s", b.Destination)) + cmd = append(cmd, "declare", "binding", "source="+b.Source, "destination="+b.Destination) if b.DestinationType != "" { - cmd = append(cmd, fmt.Sprintf("destination_type=%s", b.DestinationType)) + cmd = append(cmd, "destination_type="+b.DestinationType) } if b.RoutingKey != "" { - cmd = append(cmd, fmt.Sprintf("routing_key=%s", b.RoutingKey)) + cmd = append(cmd, "routing_key="+b.RoutingKey) } if len(b.Args) > 0 { @@ -92,7 +92,7 @@ func (e Exchange) AsCommand() []string { cmd = append(cmd, "--vhost="+e.VHost) } - cmd = append(cmd, "declare", "exchange", fmt.Sprintf("name=%s", e.Name), fmt.Sprintf("type=%s", e.Type)) + cmd = append(cmd, "declare", "exchange", "name="+e.Name, "type="+e.Type) if e.AutoDelete { cmd = append(cmd, "auto_delete=true") @@ -130,13 +130,13 @@ type OperatorPolicy struct { } func (op OperatorPolicy) AsCommand() []string { - cmd := []string{"rabbitmqadmin", "declare", "operator_policy", fmt.Sprintf("name=%s", op.Name), fmt.Sprintf("pattern=%s", op.Pattern)} + cmd := []string{"rabbitmqadmin", "declare", "operator_policy", "name=" + op.Name, "pattern=" + op.Pattern} if op.Priority > 0 { cmd = append(cmd, fmt.Sprintf("priority=%d", op.Priority)) } if op.ApplyTo != "" { - cmd = append(cmd, fmt.Sprintf("apply-to=%s", op.ApplyTo)) + cmd = append(cmd, "apply-to="+op.ApplyTo) } if len(op.Definition) > 0 { @@ -173,7 +173,7 @@ func NewParameter(component string, name string, value string) Parameter { func (p Parameter) AsCommand() []string { return []string{ "rabbitmqadmin", "declare", "parameter", - fmt.Sprintf("component=%s", p.Component), fmt.Sprintf("name=%s", p.Name), fmt.Sprintf("value=%s", p.Value), + "component=" + p.Component, "name=" + p.Name, "value=" + p.Value, } } @@ -203,8 +203,8 @@ func NewPermission(vhost string, user string, configure string, write string, re func (p Permission) AsCommand() []string { return []string{ "rabbitmqadmin", "declare", "permission", - fmt.Sprintf("vhost=%s", p.VHost), fmt.Sprintf("user=%s", p.User), - fmt.Sprintf("configure=%s", p.Configure), fmt.Sprintf("write=%s", p.Write), fmt.Sprintf("read=%s", p.Read), + "vhost=" + p.VHost, "user=" + p.User, + "configure=" + p.Configure, "write=" + p.Write, "read=" + p.Read, } } @@ -242,13 +242,13 @@ func (p Policy) AsCommand() []string { cmd = append(cmd, "--vhost="+p.VHost) } - cmd = append(cmd, "declare", "policy", fmt.Sprintf("name=%s", p.Name), fmt.Sprintf("pattern=%s", p.Pattern)) + cmd = append(cmd, "declare", "policy", "name="+p.Name, "pattern="+p.Pattern) if p.Priority > 0 { cmd = append(cmd, fmt.Sprintf("priority=%d", p.Priority)) } if p.ApplyTo != "" { - cmd = append(cmd, fmt.Sprintf("apply-to=%s", p.ApplyTo)) + cmd = append(cmd, "apply-to="+p.ApplyTo) } if len(p.Definition) > 0 { @@ -283,7 +283,7 @@ func (q Queue) AsCommand() []string { cmd = append(cmd, "--vhost="+q.VHost) } - cmd = append(cmd, "declare", "queue", fmt.Sprintf("name=%s", q.Name)) + cmd = append(cmd, "declare", "queue", "name="+q.Name) if q.AutoDelete { cmd = append(cmd, "auto_delete=true") @@ -328,8 +328,8 @@ func (u User) AsCommand() []string { return []string{ "rabbitmqadmin", "declare", "user", - fmt.Sprintf("name=%s", u.Name), fmt.Sprintf("password=%s", u.Password), - fmt.Sprintf("tags=%s", strings.Join(uniqueTags, ",")), + "name=" + u.Name, "password=" + u.Password, + "tags=" + strings.Join(uniqueTags, ","), } } @@ -344,7 +344,7 @@ type VirtualHost struct { } func (v VirtualHost) AsCommand() []string { - cmd := []string{"rabbitmqadmin", "declare", "vhost", fmt.Sprintf("name=%s", v.Name)} + cmd := []string{"rabbitmqadmin", "declare", "vhost", "name=" + v.Name} if v.Tracing { cmd = append(cmd, "tracing=true") @@ -361,7 +361,7 @@ type VirtualHostLimit struct { } func (v VirtualHostLimit) AsCommand() []string { - return []string{"rabbitmqadmin", "declare", "vhost_limit", fmt.Sprintf("vhost=%s", v.VHost), fmt.Sprintf("name=%s", v.Name), fmt.Sprintf("value=%d", v.Value)} + return []string{"rabbitmqadmin", "declare", "vhost_limit", "vhost=" + v.VHost, "name=" + v.Name, fmt.Sprintf("value=%d", v.Value)} } // --------- Virtual Hosts --------- diff --git a/modules/redis/examples_test.go b/modules/redis/examples_test.go index 90b6d30bde..86be00be85 100644 --- a/modules/redis/examples_test.go +++ b/modules/redis/examples_test.go @@ -10,31 +10,31 @@ import ( "github.com/testcontainers/testcontainers-go/modules/redis" ) -func ExampleRunContainer() { +func ExampleRun() { // runRedisContainer { ctx := context.Background() - redisContainer, err := redis.RunContainer(ctx, - testcontainers.WithImage("docker.io/redis:7"), + redisContainer, err := redis.Run(ctx, + "redis:7", redis.WithSnapshotting(10, 1), redis.WithLogLevel(redis.LogLevelVerbose), redis.WithConfigFile(filepath.Join("testdata", "redis7.conf")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := redisContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(redisContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := redisContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/redis/go.mod b/modules/redis/go.mod index efcd1da467..774b7e677e 100644 --- a/modules/redis/go.mod +++ b/modules/redis/go.mod @@ -1,12 +1,12 @@ module github.com/testcontainers/testcontainers-go/modules/redis -go 1.21 +go 1.22 require ( github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) @@ -15,17 +15,17 @@ replace github.com/testcontainers/testcontainers-go => ../.. require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // 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.2.0 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -33,11 +33,11 @@ require ( 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/golang/protobuf v1.5.4 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -58,12 +58,10 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/redis/go.sum b/modules/redis/go.sum index 80d81c9444..8ca0c6da81 100644 --- a/modules/redis/go.sum +++ b/modules/redis/go.sum @@ -4,20 +4,20 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -26,10 +26,10 @@ 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.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -49,8 +49,6 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -61,8 +59,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= @@ -71,6 +69,8 @@ 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/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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -111,6 +111,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -144,23 +146,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -171,33 +169,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -210,5 +205,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/redis/redis.go b/modules/redis/redis.go index d24cfac676..d824036642 100644 --- a/modules/redis/redis.go +++ b/modules/redis/redis.go @@ -3,14 +3,12 @@ package redis import ( "context" "fmt" + "strconv" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) -// defaultImage is the default image used for the redis container -const defaultImage = "docker.io/redis:7" - // redisServerProcess is the name of the redis server process const redisServerProcess = "redis-server" @@ -46,10 +44,16 @@ func (c *RedisContainer) ConnectionString(ctx context.Context) (string, error) { return uri, nil } +// Deprecated: use Run instead // RunContainer creates an instance of the Redis container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RedisContainer, error) { + return Run(ctx, "redis:7", opts...) +} + +// Run creates an instance of the Redis container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RedisContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImage, + Image: img, ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("* Ready to accept connections"), } @@ -66,11 +70,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *RedisContainer + if container != nil { + c = &RedisContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &RedisContainer{Container: container}, nil + return c, nil } // WithConfigFile sets the config file to be used for the redis container, and sets the command to run the redis server @@ -127,7 +136,7 @@ func WithSnapshotting(seconds int, changedKeys int) testcontainers.CustomizeRequ } return func(req *testcontainers.GenericContainerRequest) error { - processRedisServerArgs(req, []string{"--save", fmt.Sprintf("%d", seconds), fmt.Sprintf("%d", changedKeys)}) + processRedisServerArgs(req, []string{"--save", strconv.Itoa(seconds), strconv.Itoa(changedKeys)}) return nil } } diff --git a/modules/redis/redis_test.go b/modules/redis/redis_test.go index e66e6e37d5..e2352a1bf6 100644 --- a/modules/redis/redis_test.go +++ b/modules/redis/redis_test.go @@ -18,13 +18,9 @@ import ( func TestIntegrationSetGet(t *testing.T) { ctx := context.Background() - redisContainer, err := tcredis.RunContainer(ctx) + redisContainer, err := tcredis.Run(ctx, "redis:7") + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) } @@ -32,13 +28,9 @@ func TestIntegrationSetGet(t *testing.T) { func TestRedisWithConfigFile(t *testing.T) { ctx := context.Background() - redisContainer, err := tcredis.RunContainer(ctx, tcredis.WithConfigFile(filepath.Join("testdata", "redis7.conf"))) + redisContainer, err := tcredis.Run(ctx, "redis:7", tcredis.WithConfigFile(filepath.Join("testdata", "redis7.conf"))) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) } @@ -52,35 +44,31 @@ func TestRedisWithImage(t *testing.T) { }{ { name: "Redis6", - image: "docker.io/redis:6", + image: "redis:6", }, { name: "Redis7", - image: "docker.io/redis:7", + image: "redis:7", }, { name: "Redis Stack", // redisStackImage { - image: "docker.io/redis/redis-stack:latest", + image: "redis/redis-stack:latest", // } }, { name: "Redis Stack Server", // redisStackServerImage { - image: "docker.io/redis/redis-stack-server:latest", + image: "redis/redis-stack-server:latest", // } }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - redisContainer, err := tcredis.RunContainer(ctx, testcontainers.WithImage(tt.image), tcredis.WithConfigFile(filepath.Join("testdata", "redis6.conf"))) + redisContainer, err := tcredis.Run(ctx, tt.image, tcredis.WithConfigFile(filepath.Join("testdata", "redis6.conf"))) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) }) @@ -90,13 +78,9 @@ func TestRedisWithImage(t *testing.T) { func TestRedisWithLogLevel(t *testing.T) { ctx := context.Background() - redisContainer, err := tcredis.RunContainer(ctx, tcredis.WithLogLevel(tcredis.LogLevelVerbose)) + redisContainer, err := tcredis.Run(ctx, "redis:7", tcredis.WithLogLevel(tcredis.LogLevelVerbose)) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 10) } @@ -104,18 +88,15 @@ func TestRedisWithLogLevel(t *testing.T) { func TestRedisWithSnapshotting(t *testing.T) { ctx := context.Background() - redisContainer, err := tcredis.RunContainer(ctx, tcredis.WithSnapshotting(10, 1)) + redisContainer, err := tcredis.Run(ctx, "redis:7", tcredis.WithSnapshotting(10, 1)) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 10) } func assertSetsGets(t *testing.T, ctx context.Context, redisContainer *tcredis.RedisContainer, keyCount int) { + t.Helper() // connectionString { uri, err := redisContainer.ConnectionString(ctx) // } @@ -129,6 +110,7 @@ func assertSetsGets(t *testing.T, ctx context.Context, redisContainer *tcredis.R client := redis.NewClient(options) defer func(t *testing.T, ctx context.Context, client *redis.Client) { + t.Helper() require.NoError(t, flushRedis(ctx, *client)) }(t, ctx, client) @@ -138,9 +120,7 @@ func assertSetsGets(t *testing.T, ctx context.Context, redisContainer *tcredis.R t.Log("received response from redis") - if pong != "PONG" { - t.Fatalf("received unexpected response from redis: %s", pong) - } + require.Equalf(t, "PONG", pong, "received unexpected response from redis: %s", pong) for i := 0; i < keyCount; i++ { // Set data @@ -155,9 +135,7 @@ func assertSetsGets(t *testing.T, ctx context.Context, redisContainer *tcredis.R savedValue, err := client.Get(ctx, key).Result() require.NoError(t, err) - if savedValue != value { - t.Fatalf("Expected value %s. Got %s.", savedValue, value) - } + require.Equal(t, savedValue, value) } } diff --git a/modules/redis/testdata/Dockerfile b/modules/redis/testdata/Dockerfile index 7157611a13..14cfaf1e23 100644 --- a/modules/redis/testdata/Dockerfile +++ b/modules/redis/testdata/Dockerfile @@ -1 +1 @@ -FROM docker.io/redis:5.0-alpine@sha256:1a3c609295332f1ce603948142a132656c92a08149d7096e203058533c415b8c +FROM redis:5.0-alpine@sha256:1a3c609295332f1ce603948142a132656c92a08149d7096e203058533c415b8c diff --git a/modules/redpanda/examples_test.go b/modules/redpanda/examples_test.go index 21913f95ff..69fb0c9d6a 100644 --- a/modules/redpanda/examples_test.go +++ b/modules/redpanda/examples_test.go @@ -5,38 +5,42 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redpanda" ) -func ExampleRunContainer() { +func ExampleRun() { // runRedpandaContainer { ctx := context.Background() - redpandaContainer, err := redpanda.RunContainer(ctx, + redpandaContainer, err := redpanda.Run(ctx, + "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithEnableWasmTransform(), + redpanda.WithBootstrapConfig("data_transforms_per_core_memory_reservation", 33554432), + redpanda.WithBootstrapConfig("data_transforms_per_function_memory_limit", 16777216), redpanda.WithNewServiceAccount("superuser-1", "test"), redpanda.WithNewServiceAccount("superuser-2", "test"), redpanda.WithNewServiceAccount("no-superuser", "test"), redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := redpandaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(redpandaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := redpandaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/redpanda/go.mod b/modules/redpanda/go.mod index 831f3a0617..9c5065458d 100644 --- a/modules/redpanda/go.mod +++ b/modules/redpanda/go.mod @@ -1,35 +1,40 @@ module github.com/testcontainers/testcontainers-go/modules/redpanda -go 1.21 +go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 github.com/twmb/franz-go v1.16.1 github.com/twmb/franz-go/pkg/kadm v1.11.0 golang.org/x/mod v0.16.0 ) +require ( + github.com/containerd/platforms v0.2.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + golang.org/x/net v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect +) + require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -58,12 +63,9 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/redpanda/go.sum b/modules/redpanda/go.sum index 3e44bab244..740496f896 100644 --- a/modules/redpanda/go.sum +++ b/modules/redpanda/go.sum @@ -4,28 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -41,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -65,6 +63,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V 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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -140,8 +142,8 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -150,13 +152,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -167,33 +167,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -202,5 +199,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/redpanda/mounts/bootstrap.yaml.tpl b/modules/redpanda/mounts/bootstrap.yaml.tpl index 643236bbef..cf5bb6ea5f 100644 --- a/modules/redpanda/mounts/bootstrap.yaml.tpl +++ b/modules/redpanda/mounts/bootstrap.yaml.tpl @@ -22,3 +22,8 @@ data_transforms_enabled: true {{- if .AutoCreateTopics }} auto_create_topics_enabled: true {{- end }} + +{{- range $key, $value := .ExtraBootstrapConfig }} +{{ $key }}: {{ $value }} +{{- end }} + diff --git a/modules/redpanda/options.go b/modules/redpanda/options.go index f78766115b..180edcc48f 100644 --- a/modules/redpanda/options.go +++ b/modules/redpanda/options.go @@ -43,6 +43,10 @@ type options struct { // Listeners is a list of custom listeners that can be provided to access the // containers form within docker networks Listeners []listener + + // ExtraBootstrapConfig is a map of configs to be interpolated into the + // container's bootstrap.yml + ExtraBootstrapConfig map[string]any } func defaultOptions() options { @@ -55,6 +59,7 @@ func defaultOptions() options { AutoCreateTopics: false, EnableTLS: false, Listeners: make([]listener, 0), + ExtraBootstrapConfig: make(map[string]any, 0), } } @@ -134,7 +139,7 @@ func WithTLS(cert, key []byte) Option { // WithListener adds a custom listener to the Redpanda containers. Listener // will be aliases to all networks, so they can be accessed from within docker -// networks. At leas one network must be attached to the container, if not an +// networks. At least one network must be attached to the container, if not an // error will be thrown when starting the container. func WithListener(lis string) Option { host, port, err := net.SplitHostPort(lis) @@ -155,3 +160,13 @@ func WithListener(lis string) Option { }) } } + +// WithBootstrapConfig adds an arbitrary config kvp to the Redpanda container. +// Per the name, this config will be interpolated into the generated bootstrap +// config file, which is particularly useful for configs requiring a restart +// when otherwise applied to a running Redpanda instance. +func WithBootstrapConfig(cfg string, val any) Option { + return func(o *options) { + o.ExtraBootstrapConfig[cfg] = val + } +} diff --git a/modules/redpanda/redpanda.go b/modules/redpanda/redpanda.go index f6b4603fde..21c3ca4c44 100644 --- a/modules/redpanda/redpanda.go +++ b/modules/redpanda/redpanda.go @@ -6,10 +6,10 @@ import ( "crypto/tls" "crypto/x509" _ "embed" + "errors" "fmt" "math" "net/http" - "os" "path/filepath" "strings" "text/template" @@ -52,19 +52,19 @@ type Container struct { urlScheme string } -// RunContainer creates an instance of the Redpanda container type. +// Deprecated: use Run instead +// RunContainer creates an instance of the Redpanda container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) { - tmpDir, err := os.MkdirTemp("", "redpanda") - if err != nil { - return nil, fmt.Errorf("failed to create directory: %w", err) - } - defer os.RemoveAll(tmpDir) + return Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", opts...) +} +// Run creates an instance of the Redpanda container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { // 1. Create container request. // Some (e.g. Image) may be overridden by providing an option argument to this function. req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.redpanda.com/redpandadata/redpanda:v23.3.3", + Image: img, User: "root:root", // Files: Will be added later after we've rendered our YAML templates. ExposedPorts: []string{ @@ -80,6 +80,13 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize "--smp=1", "--memory=1G", }, + WaitingFor: wait.ForAll( + // Wait for the ports to be exposed only as the container needs configuration + // before it will bind to the ports and be ready to serve requests. + wait.ForListeningPort(defaultKafkaAPIPort).SkipInternalCheck(), + wait.ForListeningPort(defaultAdminAPIPort).SkipInternalCheck(), + wait.ForListeningPort(defaultSchemaRegistryPort).SkipInternalCheck(), + ), }, Started: true, } @@ -100,151 +107,149 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize settings.EnableWasmTransform = false } - // 3. Create temporary entrypoint file. We need a custom entrypoint that waits - // until the actual Redpanda node config is mounted. Once the redpanda config is - // mounted we will call the original entrypoint with the same parameters. - // We have to do this kind of two-step process, because we need to know the mapped - // port, so that we can use this in Redpanda's advertised listeners configuration for - // the Kafka API. - entrypointPath := filepath.Join(tmpDir, entrypointFile) - if err := os.WriteFile(entrypointPath, entrypoint, 0o700); err != nil { - return nil, fmt.Errorf("failed to create entrypoint file: %w", err) - } - - // 4. Register extra kafka listeners if provided, network aliases will be + // 3. Register extra kafka listeners if provided, network aliases will be // set - if err := registerListeners(ctx, settings, req); err != nil { - return nil, fmt.Errorf("failed to register listeners: %w", err) + if err := registerListeners(settings, req); err != nil { + return nil, fmt.Errorf("register listeners: %w", err) } // Bootstrap config file contains cluster configurations which will only be considered // the very first time you start a cluster. - bootstrapConfigPath := filepath.Join(tmpDir, bootstrapConfigFile) bootstrapConfig, err := renderBootstrapConfig(settings) if err != nil { - return nil, fmt.Errorf("failed to create bootstrap config file: %w", err) - } - if err := os.WriteFile(bootstrapConfigPath, bootstrapConfig, 0o600); err != nil { - return nil, fmt.Errorf("failed to create bootstrap config file: %w", err) + return nil, err } + // We need a custom entrypoint that waits until the actual Redpanda node config is mounted. + // Once the redpanda config is mounted we will call the original entrypoint with the same parameters. + // We have to do this kind of two-step process, because we need to know the mapped + // port, so that we can use this in Redpanda's advertised listeners configuration for + // the Kafka API. req.Files = append(req.Files, testcontainers.ContainerFile{ - HostFilePath: entrypointPath, + Reader: bytes.NewReader(entrypoint), ContainerFilePath: entrypointFile, FileMode: 700, }, testcontainers.ContainerFile{ - HostFilePath: bootstrapConfigPath, + Reader: bytes.NewReader(bootstrapConfig), ContainerFilePath: filepath.Join(redpandaDir, bootstrapConfigFile), FileMode: 600, }, ) - // 5. Create certificate and key for TLS connections. + // 4. Create certificate and key for TLS connections. if settings.EnableTLS { - certPath := filepath.Join(tmpDir, certFile) - if err := os.WriteFile(certPath, settings.cert, 0o600); err != nil { - return nil, fmt.Errorf("failed to create certificate file: %w", err) - } - keyPath := filepath.Join(tmpDir, keyFile) - if err := os.WriteFile(keyPath, settings.key, 0o600); err != nil { - return nil, fmt.Errorf("failed to create key file: %w", err) - } - req.Files = append(req.Files, testcontainers.ContainerFile{ - HostFilePath: certPath, + Reader: bytes.NewReader(settings.cert), ContainerFilePath: filepath.Join(redpandaDir, certFile), FileMode: 600, }, testcontainers.ContainerFile{ - HostFilePath: keyPath, + Reader: bytes.NewReader(settings.key), ContainerFilePath: filepath.Join(redpandaDir, keyFile), FileMode: 600, }, ) } - container, err := testcontainers.GenericContainer(ctx, req) + ctr, err := testcontainers.GenericContainer(ctx, req) + var c *Container + if ctr != nil { + c = &Container{Container: ctr} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - // 6. Get mapped port for the Kafka API, so that we can render and then mount + // 5. Get mapped port for the Kafka API, so that we can render and then mount // the Redpanda config with the advertised Kafka address. - hostIP, err := container.Host(ctx) + hostIP, err := ctr.Host(ctx) if err != nil { - return nil, fmt.Errorf("failed to get container host: %w", err) + return c, fmt.Errorf("host: %w", err) } - kafkaPort, err := container.MappedPort(ctx, nat.Port(defaultKafkaAPIPort)) + kafkaPort, err := ctr.MappedPort(ctx, nat.Port(defaultKafkaAPIPort)) if err != nil { - return nil, fmt.Errorf("failed to get mapped Kafka port: %w", err) + return c, fmt.Errorf("mapped kafka port: %w", err) } - // 7. Render redpanda.yaml config and mount it. + // 6. Render redpanda.yaml config and mount it. nodeConfig, err := renderNodeConfig(settings, hostIP, kafkaPort.Int()) if err != nil { - return nil, fmt.Errorf("failed to render node config: %w", err) + return c, err } - err = container.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 600) + err = ctr.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 0o600) if err != nil { - return nil, fmt.Errorf("failed to copy redpanda.yaml into container: %w", err) + return c, fmt.Errorf("copy to container: %w", err) } - // 8. Wait until Redpanda is ready to serve requests + // 7. Wait until Redpanda is ready to serve requests. + waitHTTP := wait.ForHTTP(defaultAdminAPIPort). + WithStatusCodeMatcher(func(status int) bool { + // Redpanda's admin API returns 404 for requests to "/". + return status == http.StatusNotFound + }) + + var tlsConfig *tls.Config + if settings.EnableTLS { + cert, err := tls.X509KeyPair(settings.cert, settings.key) + if err != nil { + return c, fmt.Errorf("create admin cert: %w", err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(settings.cert) + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + waitHTTP = waitHTTP.WithTLS(true, tlsConfig) + } err = wait.ForAll( wait.ForListeningPort(defaultKafkaAPIPort), - wait.ForLog("Successfully started Redpanda!").WithPollInterval(100*time.Millisecond)). - WaitUntilReady(ctx, container) + waitHTTP, + wait.ForListeningPort(defaultSchemaRegistryPort), + wait.ForLog("Successfully started Redpanda!"), + ).WaitUntilReady(ctx, ctr) if err != nil { - return nil, fmt.Errorf("failed to wait for Redpanda readiness: %w", err) + return c, fmt.Errorf("wait for readiness: %w", err) } - scheme := "http" + c.urlScheme = "http" if settings.EnableTLS { - scheme += "s" + c.urlScheme += "s" } - // 9. Create Redpanda Service Accounts if configured to do so. + // 8. Create Redpanda Service Accounts if configured to do so. if len(settings.ServiceAccounts) > 0 { - adminAPIPort, err := container.MappedPort(ctx, nat.Port(defaultAdminAPIPort)) + adminAPIPort, err := ctr.MappedPort(ctx, nat.Port(defaultAdminAPIPort)) if err != nil { - return nil, fmt.Errorf("failed to get mapped Admin API port: %w", err) + return c, fmt.Errorf("mapped admin port: %w", err) } - adminAPIUrl := fmt.Sprintf("%s://%v:%d", scheme, hostIP, adminAPIPort.Int()) + adminAPIUrl := fmt.Sprintf("%s://%v:%d", c.urlScheme, hostIP, adminAPIPort.Int()) adminCl := NewAdminAPIClient(adminAPIUrl) if settings.EnableTLS { - cert, err := tls.X509KeyPair(settings.cert, settings.key) - if err != nil { - return nil, fmt.Errorf("failed to create admin client with cert: %w", err) - } - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(settings.cert) adminCl = adminCl.WithHTTPClient(&http.Client{ Timeout: 5 * time.Second, Transport: &http.Transport{ ForceAttemptHTTP2: true, TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - }, + TLSClientConfig: tlsConfig, }, }) } for username, password := range settings.ServiceAccounts { if err := adminCl.CreateUser(ctx, username, password); err != nil { - return nil, fmt.Errorf("failed to create service account with username %q: %w", username, err) + return c, fmt.Errorf("create user %q: %w", username, err) } } } - return &Container{Container: container, urlScheme: scheme}, nil + return c, nil } // KafkaSeedBroker returns the seed broker that should be used for connecting @@ -275,16 +280,17 @@ func renderBootstrapConfig(settings options) ([]byte, error) { KafkaAPIEnableAuthorization: settings.KafkaEnableAuthorization, AutoCreateTopics: settings.AutoCreateTopics, EnableWasmTransform: settings.EnableWasmTransform, + ExtraBootstrapConfig: settings.ExtraBootstrapConfig, } tpl, err := template.New("bootstrap.yaml").Parse(bootstrapConfigTpl) if err != nil { - return nil, fmt.Errorf("failed to parse redpanda config file template: %w", err) + return nil, fmt.Errorf("parse bootstrap template: %w", err) } var bootstrapConfig bytes.Buffer if err := tpl.Execute(&bootstrapConfig, bootstrapTplParams); err != nil { - return nil, fmt.Errorf("failed to render redpanda bootstrap config template: %w", err) + return nil, fmt.Errorf("render bootstrap template: %w", err) } return bootstrapConfig.Bytes(), nil @@ -292,13 +298,13 @@ func renderBootstrapConfig(settings options) ([]byte, error) { // registerListeners validates that the provided listeners are valid and set network aliases for the provided addresses. // The container must be attached to at least one network. -func registerListeners(ctx context.Context, settings options, req testcontainers.GenericContainerRequest) error { +func registerListeners(settings options, req testcontainers.GenericContainerRequest) error { if len(settings.Listeners) == 0 { return nil } if len(req.Networks) == 0 { - return fmt.Errorf("container must be attached to at least one network") + return errors.New("container must be attached to at least one network") } for _, listener := range settings.Listeners { @@ -333,12 +339,12 @@ func renderNodeConfig(settings options, hostIP string, advertisedKafkaPort int) ncTpl, err := template.New("redpanda.yaml").Parse(nodeConfigTpl) if err != nil { - return nil, fmt.Errorf("failed to parse redpanda config file template: %w", err) + return nil, fmt.Errorf("parse node config template: %w", err) } var redpandaYaml bytes.Buffer if err := ncTpl.Execute(&redpandaYaml, tplParams); err != nil { - return nil, fmt.Errorf("failed to render redpanda node config template: %w", err) + return nil, fmt.Errorf("render node config template: %w", err) } return redpandaYaml.Bytes(), nil @@ -349,6 +355,7 @@ type redpandaBootstrapConfigTplParams struct { KafkaAPIEnableAuthorization bool AutoCreateTopics bool EnableWasmTransform bool + ExtraBootstrapConfig map[string]any } type redpandaConfigTplParams struct { @@ -386,11 +393,11 @@ func isAtLeastVersion(image, major string) bool { } if !strings.HasPrefix(version, "v") { - version = fmt.Sprintf("v%s", version) + version = "v" + version } if semver.IsValid(version) { - return semver.Compare(version, fmt.Sprintf("v%s", major)) >= 0 // version >= v8.x + return semver.Compare(version, "v"+major) >= 0 // version >= v8.x } return false diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index 9d412be1c0..112c83f882 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -2,6 +2,7 @@ package redpanda_test import ( "context" + "encoding/json" "fmt" "io" "net/http" @@ -26,18 +27,12 @@ import ( func TestRedpanda(t *testing.T) { ctx := context.Background() - container, err := redpanda.RunContainer(ctx) + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // Test Kafka API - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -49,30 +44,30 @@ func TestRedpanda(t *testing.T) { kafkaAdmCl := kadm.NewClient(kafkaCl) metadata, err := kafkaAdmCl.Metadata(ctx) require.NoError(t, err) - assert.Len(t, metadata.Brokers, 1) + require.Len(t, metadata.Brokers, 1) // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) require.NoError(t, err) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, schemaRegistryURL+"/subjects", nil) require.NoError(t, err) resp, err := httpCl.Do(req) require.NoError(t, err) defer resp.Body.Close() - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) // Test Admin API // adminAPIAddress { - adminAPIURL, err := container.AdminAPIAddress(ctx) + adminAPIURL, err := ctr.AdminAPIAddress(ctx) // } require.NoError(t, err) - req, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/cluster/health_overview", adminAPIURL), nil) + req, err = http.NewRequestWithContext(ctx, http.MethodGet, adminAPIURL+"/v1/cluster/health_overview", nil) require.NoError(t, err) resp, err = httpCl.Do(req) require.NoError(t, err) defer resp.Body.Close() - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) // Test produce to unknown topic results := kafkaCl.ProduceSync(ctx, &kgo.Record{Topic: "test", Value: []byte("test message")}) @@ -82,7 +77,8 @@ func TestRedpanda(t *testing.T) { func TestRedpandaWithAuthentication(t *testing.T) { ctx := context.Background() // redpandaCreateContainer { - container, err := redpanda.RunContainer(ctx, + ctr, err := redpanda.Run(ctx, + "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithEnableWasmTransform(), @@ -92,18 +88,12 @@ func TestRedpandaWithAuthentication(t *testing.T) { redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // kafkaSeedBroker { - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) // } require.NoError(t, err) @@ -167,16 +157,16 @@ func TestRedpandaWithAuthentication(t *testing.T) { // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} // schemaRegistryAddress { - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) // } require.NoError(t, err) // Failed authentication - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, schemaRegistryURL+"/subjects", nil) require.NoError(t, err) resp, err := httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) resp.Body.Close() // Successful authentication @@ -184,7 +174,7 @@ func TestRedpandaWithAuthentication(t *testing.T) { req.SetBasicAuth(user, password) resp, err = httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) resp.Body.Close() } } @@ -193,8 +183,8 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { ctx := context.Background() // redpandaCreateContainer { // this would fail to start if we weren't ignoring wasm transforms for older versions - container, err := redpanda.RunContainer(ctx, - testcontainers.WithImage("redpandadata/redpanda:v23.2.18"), + ctr, err := redpanda.Run(ctx, + "redpandadata/redpanda:v23.2.18", redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithEnableWasmTransform(), @@ -204,18 +194,12 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // kafkaSeedBroker { - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) // } require.NoError(t, err) @@ -296,16 +280,16 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} // schemaRegistryAddress { - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) // } require.NoError(t, err) // Failed authentication - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, schemaRegistryURL+"/subjects", nil) require.NoError(t, err) resp, err := httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) resp.Body.Close() // Successful authentication @@ -321,16 +305,11 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { func TestRedpandaProduceWithAutoCreateTopics(t *testing.T) { ctx := context.Background() - container, err := redpanda.RunContainer(ctx, redpanda.WithAutoCreateTopics()) + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithAutoCreateTopics()) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - brokers, err := container.KafkaSeedBroker(ctx) + brokers, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -355,15 +334,10 @@ func TestRedpandaWithTLS(t *testing.T) { ctx := context.Background() - container, err := redpanda.RunContainer(ctx, redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - tlsConfig := cert.TLSConfig() httpCl := &http.Client{ @@ -376,28 +350,28 @@ func TestRedpandaWithTLS(t *testing.T) { } // Test Admin API - adminAPIURL, err := container.AdminAPIAddress(ctx) + adminAPIURL, err := ctr.AdminAPIAddress(ctx) require.NoError(t, err) require.True(t, strings.HasPrefix(adminAPIURL, "https://"), "AdminAPIAddress should return https url") - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/cluster/health_overview", adminAPIURL), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, adminAPIURL+"/v1/cluster/health_overview", nil) require.NoError(t, err) resp, err := httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) resp.Body.Close() // Test Schema Registry API - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) require.NoError(t, err) require.True(t, strings.HasPrefix(adminAPIURL, "https://"), "SchemaRegistryAddress should return https url") - req, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) + req, err = http.NewRequestWithContext(ctx, http.MethodGet, schemaRegistryURL+"/subjects", nil) require.NoError(t, err) resp, err = httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) resp.Body.Close() - brokers, err := container.KafkaSeedBroker(ctx) + brokers, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -424,24 +398,20 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { ctx := context.Background() - container, err := redpanda.RunContainer(ctx, + ctr, err := redpanda.Run(ctx, + "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithTLS(cert.Bytes, cert.KeyBytes), redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), redpanda.WithNewServiceAccount("superuser-1", "test"), redpanda.WithSuperusers("superuser-1"), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - tlsConfig := cert.TLSConfig() - broker, err := container.KafkaSeedBroker(ctx) + broker, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -463,17 +433,20 @@ func TestRedpandaListener_Simple(t *testing.T) { ctx := context.Background() // 1. Create network - rpNetwork, err := network.New(ctx, network.WithCheckDuplicate()) + rpNetwork, err := network.New(ctx) require.NoError(t, err) - // 2. Start Redpanda container + testcontainers.CleanupNetwork(t, rpNetwork) + + // 2. Start Redpanda ctr // withListenerRP { - container, err := redpanda.RunContainer(ctx, - testcontainers.WithImage("redpandadata/redpanda:v23.2.18"), + ctr, err := redpanda.Run(ctx, + "redpandadata/redpanda:v23.2.18", network.WithNetwork([]string{"redpanda-host"}, rpNetwork), redpanda.WithListener("redpanda:29092"), redpanda.WithAutoCreateTopics(), ) // } + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // 3. Start KCat container @@ -495,7 +468,7 @@ func TestRedpandaListener_Simple(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, kcat) require.NoError(t, err) // 4. Copy message to kcat @@ -516,58 +489,90 @@ func TestRedpandaListener_Simple(t *testing.T) { // 7. Read Message from stdout out, err := io.ReadAll(stdout) require.NoError(t, err) - require.Contains(t, string(out), "Message produced by kcat") - - t.Cleanup(func() { - if err := kcat.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate kcat container: %s", err) - } - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate redpanda container: %s", err) - } - - if err := rpNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } - }) } func TestRedpandaListener_InvalidPort(t *testing.T) { ctx := context.Background() // 1. Create network - RPNetwork, err := network.New(ctx, network.WithCheckDuplicate()) + RPNetwork, err := network.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, RPNetwork) - // 2. Attempt Start Redpanda container - _, err = redpanda.RunContainer(ctx, - testcontainers.WithImage("redpandadata/redpanda:v23.2.18"), + // 2. Attempt Start Redpanda ctr + ctr, err := redpanda.Run(ctx, + "redpandadata/redpanda:v23.2.18", redpanda.WithListener("redpanda:99092"), network.WithNetwork([]string{"redpanda-host"}, RPNetwork), ) - - require.Error(t, err) - - require.Contains(t, err.Error(), "invalid port on listener redpanda:99092") - - t.Cleanup(func() { - if err := RPNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.ErrorContains(t, err, "invalid port on listener redpanda:99092") } func TestRedpandaListener_NoNetwork(t *testing.T) { ctx := context.Background() - // 1. Attempt Start Redpanda container - _, err := redpanda.RunContainer(ctx, - testcontainers.WithImage("redpandadata/redpanda:v23.2.18"), + // 1. Attempt Start Redpanda ctr + ctr, err := redpanda.Run(ctx, + "redpandadata/redpanda:v23.2.18", redpanda.WithListener("redpanda:99092"), ) + testcontainers.CleanupContainer(t, ctr) + require.ErrorContains(t, err, "container must be attached to at least one network") +} - require.Error(t, err) +func TestRedpandaBootstrapConfig(t *testing.T) { + ctx := context.Background() + + container, err := redpanda.RunContainer(ctx, + redpanda.WithEnableWasmTransform(), + // These configs would require a restart if applied to a live Redpanda instance + redpanda.WithBootstrapConfig("data_transforms_per_core_memory_reservation", 33554432), + redpanda.WithBootstrapConfig("data_transforms_per_function_memory_limit", 16777216), + ) + require.NoError(t, err) + + httpCl := &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: 10 * time.Second, + }, + } + adminAPIUrl, err := container.AdminAPIAddress(ctx) + require.NoError(t, err) - require.Contains(t, err.Error(), "container must be attached to at least one network") + { + // Check that the configs reflect specified values + req, err := http.NewRequestWithContext(ctx, http.MethodGet, adminAPIUrl+"/v1/cluster_config", nil) + require.NoError(t, err) + resp, err := httpCl.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + var data map[string]any + err = json.NewDecoder(resp.Body).Decode(&data) + require.NoError(t, err) + reservation := int(data["data_transforms_per_core_memory_reservation"].(float64)) + require.Equal(t, 33554432, reservation) + pf_limit := int(data["data_transforms_per_function_memory_limit"].(float64)) + require.Equal(t, 16777216, pf_limit) + } + + { + // Check that no restart is required. i.e. that the configs were applied via bootstrap config + req, err := http.NewRequestWithContext(ctx, http.MethodGet, adminAPIUrl+"/v1/cluster_config/status", nil) + require.NoError(t, err) + resp, err := httpCl.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + var data []map[string]any + err = json.NewDecoder(resp.Body).Decode(&data) + require.NoError(t, err) + require.Len(t, data, 1) + needs_restart := data[0]["restart"].(bool) + require.False(t, needs_restart) + } } diff --git a/modules/registry/examples_test.go b/modules/registry/examples_test.go index efb0aff126..8742456eef 100644 --- a/modules/registry/examples_test.go +++ b/modules/registry/examples_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "os" "path/filepath" "github.com/testcontainers/testcontainers-go" @@ -12,24 +11,24 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -func ExampleRunContainer() { +func ExampleRun() { // runRegistryContainer { - registryContainer, err := registry.RunContainer(context.Background(), testcontainers.WithImage("registry:2.8.3")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + registryContainer, err := registry.Run(context.Background(), "registry:2.8.3") defer func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := registryContainer.State(context.Background()) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -38,46 +37,38 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_withAuthentication() { +func ExampleRun_withAuthentication() { // htpasswdFile { - registryContainer, err := registry.RunContainer( - context.Background(), - testcontainers.WithImage("registry:2.8.3"), + ctx := context.Background() + registryContainer, err := registry.Run( + ctx, + "registry:2.8.3", registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "data")), ) // } - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() - - registryPort, err := registryContainer.MappedPort(context.Background(), "5000/tcp") if err != nil { - log.Fatalf("failed to get mapped port: %s", err) // nolint:gocritic + log.Printf("failed to start container: %s", err) + return } - strPort := registryPort.Port() - previousAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") + registryHost, err := registryContainer.HostAddress(ctx) + if err != nil { + log.Printf("failed to get host: %s", err) + return + } - // make sure the Docker Auth credentials are set - // using the same as in the Docker Registry - // testuser:testpassword - os.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:`+strPort+`": { "username": "testuser", "password": "testpassword", "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } - }, - "credsStore": "desktop" - }`) - defer func() { - // reset the original state after the example. - os.Unsetenv("DOCKER_AUTH_CONFIG") - os.Setenv("DOCKER_AUTH_CONFIG", previousAuthConfig) - }() + cleanup, err := registry.SetDockerAuthConfig(registryHost, "testuser", "testpassword") + if err != nil { + log.Printf("failed to set docker auth config: %s", err) + return + } + defer cleanup() // build a custom redis image from the private registry, // using RegistryName of the container as the registry. @@ -87,9 +78,8 @@ func ExampleRunContainer_withAuthentication() { FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join("testdata", "redis"), BuildArgs: map[string]*string{ - "REGISTRY_PORT": &strPort, + "REGISTRY_HOST": ®istryHost, }, - PrintBuildLog: true, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -97,18 +87,20 @@ func ExampleRunContainer_withAuthentication() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } defer func() { - if err := redisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(redisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := redisC.State(context.Background()) if err != nil { - log.Fatalf("failed to get redis container state: %s", err) // nolint:gocritic + log.Printf("failed to get redis container state: %s", err) + return } fmt.Println(state.Running) @@ -117,52 +109,46 @@ func ExampleRunContainer_withAuthentication() { // true } -func ExampleRunContainer_pushImage() { - registryContainer, err := registry.RunContainer( - context.Background(), - testcontainers.WithImage("registry:2.8.3"), +func ExampleRun_pushImage() { + ctx := context.Background() + registryContainer, err := registry.Run( + ctx, + registry.DefaultImage, registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "data")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() - - registryPort, err := registryContainer.MappedPort(context.Background(), "5000/tcp") if err != nil { - log.Fatalf("failed to get mapped port: %s", err) // nolint:gocritic + log.Printf("failed to start container: %s", err) + return } - strPort := registryPort.Port() - previousAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") + registryHost, err := registryContainer.HostAddress(ctx) + if err != nil { + log.Printf("failed to get host: %s", err) + return + } - // make sure the Docker Auth credentials are set - // using the same as in the Docker Registry - // testuser:testpassword // Besides, we are also setting the authentication // for both the registry and localhost to make sure // the image is pushed to the private registry. - os.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:`+strPort+`": { "username": "testuser", "password": "testpassword", "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" }, - "`+registryContainer.RegistryName+`": { "username": "testuser", "password": "testpassword", "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } - }, - "credsStore": "desktop" - }`) - defer func() { - // reset the original state after the example. - os.Unsetenv("DOCKER_AUTH_CONFIG") - os.Setenv("DOCKER_AUTH_CONFIG", previousAuthConfig) - }() + cleanup, err := registry.SetDockerAuthConfig( + registryHost, "testuser", "testpassword", + registryContainer.RegistryName, "testuser", "testpassword", + ) + if err != nil { + log.Printf("failed to set docker auth config: %s", err) + return + } + defer cleanup() // build a custom redis image from the private registry, // using RegistryName of the container as the registry. - // We are agoing to build the image with a fixed tag + // We are going to build the image with a fixed tag // that matches the private registry, and we are going to // push it again to the registry after the build. @@ -174,11 +160,10 @@ func ExampleRunContainer_pushImage() { FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join("testdata", "redis"), BuildArgs: map[string]*string{ - "REGISTRY_PORT": &strPort, + "REGISTRY_HOST": ®istryHost, }, - Repo: repo, - Tag: tag, - PrintBuildLog: true, + Repo: repo, + Tag: tag, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -186,21 +171,23 @@ func ExampleRunContainer_pushImage() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } defer func() { - if err := redisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(redisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // pushingImage { // repo is localhost:32878/customredis // tag is v1.2.3 err = registryContainer.PushImage(context.Background(), fmt.Sprintf("%s:%s", repo, tag)) if err != nil { - log.Fatalf("failed to push image: %s", err) // nolint:gocritic + log.Printf("failed to push image: %s", err) + return } // } @@ -213,7 +200,8 @@ func ExampleRunContainer_pushImage() { // newImage is customredis:v1.2.3 err = registryContainer.DeleteImage(context.Background(), newImage) if err != nil { - log.Fatalf("failed to delete image: %s", err) // nolint:gocritic + log.Printf("failed to delete image: %s", err) + return } // } @@ -225,18 +213,20 @@ func ExampleRunContainer_pushImage() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container from %s: %s", newImage, err) // nolint:gocritic - } defer func() { - if err := newRedisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(newRedisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container from %s: %s", newImage, err) + return + } state, err := newRedisC.State(context.Background()) if err != nil { - log.Fatalf("failed to get redis container state from %s: %s", newImage, err) // nolint:gocritic + log.Printf("failed to get redis container state from %s: %s", newImage, err) + return } fmt.Println(state.Running) diff --git a/modules/registry/go.mod b/modules/registry/go.mod index ffa2cc2871..c796908087 100644 --- a/modules/registry/go.mod +++ b/modules/registry/go.mod @@ -1,22 +1,24 @@ module github.com/testcontainers/testcontainers-go/modules/registry -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/cpuguy83/dockercfg v0.3.2 + github.com/docker/docker v27.1.1+incompatible + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -24,11 +26,12 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -37,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -48,13 +52,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/registry/go.sum b/modules/registry/go.sum index d8ba86a7c1..447eec4038 100644 --- a/modules/registry/go.sum +++ b/modules/registry/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -52,12 +51,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -89,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -122,23 +131,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -149,38 +154,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/registry/registry.go b/modules/registry/registry.go index 7c77ac594e..1b1c42017c 100644 --- a/modules/registry/registry.go +++ b/modules/registry/registry.go @@ -4,18 +4,30 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" + "net" "net/http" + "os" "strings" "time" - "github.com/docker/docker/api/types" + "github.com/cpuguy83/dockercfg" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) +const ( + // registryPort is the default port used by the Registry container. + registryPort = "5000/tcp" + + // DefaultImage is the default image used by the Registry container. + DefaultImage = "registry:2.8.3" +) + // RegistryContainer represents the Registry container type used in the module type RegistryContainer struct { testcontainers.Container @@ -24,17 +36,56 @@ type RegistryContainer struct { // Address returns the address of the Registry container, using the HTTP protocol func (c *RegistryContainer) Address(ctx context.Context) (string, error) { - port, err := c.MappedPort(ctx, "5000") + host, err := c.HostAddress(ctx) if err != nil { return "", err } - ipAddress, err := c.Host(ctx) + return "http://" + host, nil +} + +// HostAddress returns the host address including port of the Registry container. +func (c *RegistryContainer) HostAddress(ctx context.Context) (string, error) { + port, err := c.MappedPort(ctx, registryPort) + if err != nil { + return "", fmt.Errorf("mapped port: %w", err) + } + + host, err := c.Container.Host(ctx) if err != nil { - return "", err + return "", fmt.Errorf("host: %w", err) + } + + if host == "localhost" { + // This is a workaround for WSL, where localhost is not reachable from Docker. + host, err = localAddress(ctx) + if err != nil { + return "", fmt.Errorf("local ip: %w", err) + } + } + + return net.JoinHostPort(host, port.Port()), nil +} + +// localAddress returns the local address of the machine +// which can be used to connect to the local registry. +// This avoids the issues with localhost on WSL. +func localAddress(ctx context.Context) (string, error) { + if os.Getenv("WSL_DISTRO_NAME") == "" { + return "localhost", nil } - return fmt.Sprintf("http://%s:%s", ipAddress, port.Port()), nil + var d net.Dialer + conn, err := d.DialContext(ctx, "udp", "golang.org:80") + if err != nil { + return "", fmt.Errorf("dial: %w", err) + } + + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP.String(), nil } // getEndpointWithAuth returns the HTTP endpoint of the Registry container, along with the image auth @@ -82,7 +133,6 @@ func (c *RegistryContainer) DeleteImage(ctx context.Context, imageRef string) er return false }). WaitUntilReady(ctx, c) - if err != nil { return fmt.Errorf("failed to get image digest: %w", err) } @@ -136,7 +186,7 @@ func (c *RegistryContainer) PushImage(ctx context.Context, ref string) error { return fmt.Errorf("failed to get image auth: %w", err) } - pushOpts := types.ImagePushOptions{ + pushOpts := image.PushOptions{ All: true, } @@ -156,19 +206,24 @@ func (c *RegistryContainer) PushImage(ctx context.Context, ref string) error { return c.ImageExists(ctx, ref) } +// Deprecated: use Run instead // RunContainer creates an instance of the Registry container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*RegistryContainer, error) { + return Run(ctx, "registry:2.8.3", opts...) +} + +// Run creates an instance of the Registry container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*RegistryContainer, error) { req := testcontainers.ContainerRequest{ - Image: "registry:2.8.3", - ExposedPorts: []string{"5000/tcp"}, + Image: img, + ExposedPorts: []string{registryPort}, Env: map[string]string{ // convenient for testing "REGISTRY_STORAGE_DELETE_ENABLED": "true", }, - WaitingFor: wait.ForAll( - wait.ForExposedPort(), - wait.ForLog("listening on [::]:5000").WithStartupTimeout(10*time.Second), - ), + WaitingFor: wait.ForHTTP("/"). + WithPort(registryPort). + WithStartupTimeout(10 * time.Second), } genericContainerReq := testcontainers.GenericContainerRequest{ @@ -177,22 +232,78 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } for _, opt := range opts { - opt.Customize(&genericContainerReq) + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *RegistryContainer + if container != nil { + c = &RegistryContainer{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - c := &RegistryContainer{Container: container} - address, err := c.Address(ctx) if err != nil { - return c, err + return c, fmt.Errorf("address: %w", err) } c.RegistryName = strings.TrimPrefix(address, "http://") return c, nil } + +// SetDockerAuthConfig sets the DOCKER_AUTH_CONFIG environment variable with +// authentication for the given host, username and password sets. +// It returns a function to reset the environment back to the previous state. +func SetDockerAuthConfig(host, username, password string, additional ...string) (func(), error) { + authConfigs, err := DockerAuthConfig(host, username, password, additional...) + if err != nil { + return nil, fmt.Errorf("docker auth config: %w", err) + } + + auth, err := json.Marshal(dockercfg.Config{AuthConfigs: authConfigs}) + if err != nil { + return nil, fmt.Errorf("marshal auth config: %w", err) + } + + previousAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") + os.Setenv("DOCKER_AUTH_CONFIG", string(auth)) + + return func() { + if previousAuthConfig == "" { + os.Unsetenv("DOCKER_AUTH_CONFIG") + return + } + os.Setenv("DOCKER_AUTH_CONFIG", previousAuthConfig) + }, nil +} + +// DockerAuthConfig returns a map of AuthConfigs including base64 encoded Auth field +// for the provided details. It also accepts additional host, username and password +// triples to add more auth configurations. +func DockerAuthConfig(host, username, password string, additional ...string) (map[string]dockercfg.AuthConfig, error) { + if len(additional)%3 != 0 { + return nil, errors.New("additional must be a multiple of 3") + } + + additional = append(additional, host, username, password) + authConfigs := make(map[string]dockercfg.AuthConfig, len(additional)/3) + for i := 0; i < len(additional); i += 3 { + host, username, password := additional[i], additional[i+1], additional[i+2] + auth := dockercfg.AuthConfig{ + Username: username, + Password: password, + } + + if username != "" || password != "" { + auth.Auth = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + } + authConfigs[host] = auth + } + + return authConfigs, nil +} diff --git a/modules/registry/registry_test.go b/modules/registry/registry_test.go index 62c1ebf6e6..2b647c2c86 100644 --- a/modules/registry/registry_test.go +++ b/modules/registry/registry_test.go @@ -2,149 +2,104 @@ package registry_test import ( "context" + "encoding/json" "net/http" "path/filepath" - "strings" "testing" + "github.com/cpuguy83/dockercfg" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/registry" "github.com/testcontainers/testcontainers-go/wait" ) func TestRegistry_unauthenticated(t *testing.T) { - container, err := registry.RunContainer(context.Background(), testcontainers.WithImage("registry:2.8.3")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctx := context.Background() + ctr, err := registry.Run(ctx, registry.DefaultImage) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - httpAddress, err := container.Address(context.Background()) - if err != nil { - t.Fatal(err) - } + httpAddress, err := ctr.Address(ctx) + require.NoError(t, err) resp, err := http.Get(httpAddress + "/v2/_catalog") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, but got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestRunContainer_authenticated(t *testing.T) { - registryContainer, err := registry.RunContainer( - context.Background(), - testcontainers.WithImage("registry:2.8.3"), + ctx := context.Background() + registryContainer, err := registry.Run( + ctx, + registry.DefaultImage, registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "data")), ) - if err != nil { - t.Fatalf("failed to start container: %s", err) - } - t.Cleanup(func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, registryContainer) + require.NoError(t, err) // httpAddress { - httpAddress, err := registryContainer.Address(context.Background()) + httpAddress, err := registryContainer.Address(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - registryPort, err := registryContainer.MappedPort(context.Background(), "5000/tcp") - if err != nil { - t.Fatalf("failed to get mapped port: %s", err) - } - strPort := registryPort.Port() + registryHost, err := registryContainer.HostAddress(ctx) + require.NoError(t, err) t.Run("HTTP connection without basic auth fails", func(tt *testing.T) { httpCli := http.Client{} - req, err := http.NewRequest("GET", httpAddress+"/v2/_catalog", nil) - if err != nil { - tt.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, httpAddress+"/v2/_catalog", nil) + require.NoError(t, err) resp, err := httpCli.Do(req) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusUnauthorized { - t.Fatalf("expected status code 401, but got %d", resp.StatusCode) - } + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) }) t.Run("HTTP connection with incorrect basic auth fails", func(tt *testing.T) { httpCli := http.Client{} - req, err := http.NewRequest("GET", httpAddress+"/v2/_catalog", nil) - if err != nil { - tt.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, httpAddress+"/v2/_catalog", nil) + require.NoError(t, err) req.SetBasicAuth("foo", "bar") resp, err := httpCli.Do(req) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusUnauthorized { - t.Fatalf("expected status code 401, but got %d", resp.StatusCode) - } + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) }) t.Run("HTTP connection with basic auth succeeds", func(tt *testing.T) { httpCli := http.Client{} - req, err := http.NewRequest("GET", httpAddress+"/v2/_catalog", nil) - if err != nil { - tt.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, httpAddress+"/v2/_catalog", nil) + require.NoError(t, err) req.SetBasicAuth("testuser", "testpassword") resp, err := httpCli.Do(req) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, but got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("build images with wrong credentials fails", func(tt *testing.T) { - // Zm9vOmJhcg== is base64 for foo:bar - tt.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:`+strPort+`": { "username": "foo", "password": "bar", "auth": "Zm9vOmJhcg==" } - }, - "credsStore": "desktop" - }`) + setAuthConfig(tt, registryHost, "foo", "bar") redisC, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join("testdata", "redis"), BuildArgs: map[string]*string{ - "REGISTRY_PORT": &strPort, + "REGISTRY_HOST": ®istryHost, }, - PrintBuildLog: true, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -152,31 +107,13 @@ func TestRunContainer_authenticated(t *testing.T) { }, Started: true, }) - if err == nil { - tt.Fatalf("expected to fail to start container, but it did not") - } - if redisC != nil { - tt.Fatal("redis container should not be running") - tt.Cleanup(func() { - if err := redisC.Terminate(context.Background()); err != nil { - tt.Fatalf("failed to terminate container: %s", err) - } - }) - } - - if !strings.Contains(err.Error(), "unauthorized: authentication required") { - tt.Fatalf("expected error to be 'unauthorized: authentication required' but got '%s'", err.Error()) - } + testcontainers.CleanupContainer(tt, redisC) + require.Error(tt, err) + require.Contains(tt, err.Error(), "unauthorized: authentication required") }) t.Run("build image with valid credentials", func(tt *testing.T) { - // dGVzdHVzZXI6dGVzdHBhc3N3b3Jk is base64 for testuser:testpassword - tt.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:`+strPort+`": { "username": "testuser", "password": "testpassword", "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } - }, - "credsStore": "desktop" - }`) + setAuthConfig(tt, registryHost, "testuser", "testpassword") // build a custom redis image from the private registry, // using RegistryName of the container as the registry. @@ -188,9 +125,8 @@ func TestRunContainer_authenticated(t *testing.T) { FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join("testdata", "redis"), BuildArgs: map[string]*string{ - "REGISTRY_PORT": &strPort, + "REGISTRY_HOST": ®istryHost, }, - PrintBuildLog: true, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -198,97 +134,58 @@ func TestRunContainer_authenticated(t *testing.T) { }, Started: true, }) - if err != nil { - tt.Fatalf("failed to start container: %s", err) - } - - tt.Cleanup(func() { - if err := redisC.Terminate(context.Background()); err != nil { - tt.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(tt, redisC) + require.NoError(tt, err) state, err := redisC.State(context.Background()) - if err != nil { - tt.Fatalf("failed to get redis container state: %s", err) // nolint:gocritic - } - - if !state.Running { - tt.Fatalf("expected redis container to be running, but it is not") - } + require.NoError(tt, err) + require.True(tt, state.Running, "expected redis container to be running, but it is not") }) } func TestRunContainer_authenticated_withCredentials(t *testing.T) { + ctx := context.Background() // htpasswdString { - registryContainer, err := registry.RunContainer( - context.Background(), - testcontainers.WithImage("registry:2.8.3"), + registryContainer, err := registry.Run( + ctx, + registry.DefaultImage, registry.WithHtpasswd("testuser:$2y$05$tTymaYlWwJOqie.bcSUUN.I.kxmo1m5TLzYQ4/ejJ46UMXGtq78EO"), ) // } - if err != nil { - t.Fatalf("failed to start container: %s", err) - } - t.Cleanup(func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, registryContainer) + require.NoError(t, err) - httpAddress, err := registryContainer.Address(context.Background()) - if err != nil { - t.Fatal(err) - } + httpAddress, err := registryContainer.Address(ctx) + require.NoError(t, err) httpCli := http.Client{} - req, err := http.NewRequest("GET", httpAddress+"/v2/_catalog", nil) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest(http.MethodGet, httpAddress+"/v2/_catalog", nil) + require.NoError(t, err) req.SetBasicAuth("testuser", "testpassword") resp, err := httpCli.Do(req) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, but got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestRunContainer_wrongData(t *testing.T) { - registryContainer, err := registry.RunContainer( - context.Background(), - testcontainers.WithImage("registry:2.8.3"), + ctx := context.Background() + registryContainer, err := registry.Run( + ctx, + registry.DefaultImage, registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "wrongdata")), ) - if err != nil { - t.Fatalf("failed to start container: %s", err) - } - t.Cleanup(func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, registryContainer) + require.NoError(t, err) - registryPort, err := registryContainer.MappedPort(context.Background(), "5000/tcp") - if err != nil { - t.Fatalf("failed to get mapped port: %s", err) - } - strPort := registryPort.Port() + registryHost, err := registryContainer.HostAddress(ctx) + require.NoError(t, err) - // dGVzdHVzZXI6dGVzdHBhc3N3b3Jk is base64 for testuser:testpassword - t.Setenv("DOCKER_AUTH_CONFIG", `{ - "auths": { - "localhost:`+strPort+`": { "username": "testuser", "password": "testpassword", "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } - }, - "credsStore": "desktop" - }`) + setAuthConfig(t, registryHost, "testuser", "testpassword") // build a custom redis image from the private registry, // using RegistryName of the container as the registry. @@ -300,9 +197,8 @@ func TestRunContainer_wrongData(t *testing.T) { FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join("testdata", "redis"), BuildArgs: map[string]*string{ - "REGISTRY_PORT": &strPort, + "REGISTRY_HOST": ®istryHost, }, - PrintBuildLog: true, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -310,19 +206,19 @@ func TestRunContainer_wrongData(t *testing.T) { }, Started: true, }) - if err == nil { - t.Fatalf("expected to fail to start container, but it did not") - } - if redisC != nil { - t.Fatal("redis container should not be running") - t.Cleanup(func() { - if err := redisC.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - } + testcontainers.CleanupContainer(t, redisC) + require.ErrorContains(t, err, "manifest unknown") +} + +// setAuthConfig sets the DOCKER_AUTH_CONFIG environment variable with +// authentication for with the given host, username and password. +func setAuthConfig(t *testing.T, host, username, password string) { + t.Helper() + + authConfigs, err := registry.DockerAuthConfig(host, username, password) + require.NoError(t, err) + auth, err := json.Marshal(dockercfg.Config{AuthConfigs: authConfigs}) + require.NoError(t, err) - if !strings.Contains(err.Error(), "manifest unknown") { - t.Fatalf("expected error to be 'manifest unknown' but got '%s'", err.Error()) - } + t.Setenv("DOCKER_AUTH_CONFIG", string(auth)) } diff --git a/modules/registry/testdata/redis/Dockerfile b/modules/registry/testdata/redis/Dockerfile index 502db64261..280c33c827 100644 --- a/modules/registry/testdata/redis/Dockerfile +++ b/modules/registry/testdata/redis/Dockerfile @@ -1,3 +1,3 @@ -ARG REGISTRY_PORT=5000 +ARG REGISTRY_HOST=localhost:5000 -FROM localhost:${REGISTRY_PORT}/redis:5.0-alpine \ No newline at end of file +FROM ${REGISTRY_HOST}/redis:5.0-alpine diff --git a/modules/surrealdb/examples_test.go b/modules/surrealdb/examples_test.go index ad27e3ac0e..7d5c13a598 100644 --- a/modules/surrealdb/examples_test.go +++ b/modules/surrealdb/examples_test.go @@ -9,26 +9,26 @@ import ( "github.com/testcontainers/testcontainers-go/modules/surrealdb" ) -func ExampleRunContainer() { +func ExampleRun() { // runSurrealDBContainer { ctx := context.Background() - surrealdbContainer, err := surrealdb.RunContainer(ctx, testcontainers.WithImage("surrealdb/surrealdb:v1.1.1")) - if err != nil { - log.Fatal(err) - } - - // Clean up the container + surrealdbContainer, err := surrealdb.Run(ctx, "surrealdb/surrealdb:v1.1.1") defer func() { - if err := surrealdbContainer.Terminate(ctx); err != nil { - log.Fatal(err) + if err := testcontainers.TerminateContainer(surrealdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Print(err) + return + } // } state, err := surrealdbContainer.State(ctx) if err != nil { - log.Fatal(err) // nolint:gocritic + log.Print(err) + return } fmt.Println(state.Running) diff --git a/modules/surrealdb/go.mod b/modules/surrealdb/go.mod index 546a4cf5ee..8af514ae28 100644 --- a/modules/surrealdb/go.mod +++ b/modules/surrealdb/go.mod @@ -1,23 +1,25 @@ module github.com/testcontainers/testcontainers-go/modules/surrealdb -go 1.21 +go 1.22 require ( + github.com/stretchr/testify v1.9.0 github.com/surrealdb/surrealdb.go v0.2.1 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -25,12 +27,13 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect 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 @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -50,13 +54,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/surrealdb/go.sum b/modules/surrealdb/go.sum index c0002f2fc9..059e8ac355 100644 --- a/modules/surrealdb/go.sum +++ b/modules/surrealdb/go.sum @@ -4,27 +4,28 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -40,8 +41,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,12 +53,18 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -91,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -126,23 +135,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -153,38 +158,37 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/surrealdb/surrealdb.go b/modules/surrealdb/surrealdb.go index 78d0707fe1..cc9ae744dc 100644 --- a/modules/surrealdb/surrealdb.go +++ b/modules/surrealdb/surrealdb.go @@ -79,10 +79,16 @@ func WithAllowAllCaps() testcontainers.CustomizeRequestOption { } } +// Deprecated: use Run instead // RunContainer creates an instance of the SurrealDB container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*SurrealDBContainer, error) { + return Run(ctx, "surrealdb/surrealdb:v1.1.1", opts...) +} + +// Run creates an instance of the SurrealDB container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*SurrealDBContainer, error) { req := testcontainers.ContainerRequest{ - Image: "surrealdb/surrealdb:v1.1.1", + Image: img, Env: map[string]string{ "SURREAL_USER": "root", "SURREAL_PASS": "root", @@ -110,9 +116,14 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *SurrealDBContainer + if container != nil { + c = &SurrealDBContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &SurrealDBContainer{Container: container}, nil + return c, nil } diff --git a/modules/surrealdb/surrealdb_test.go b/modules/surrealdb/surrealdb_test.go index 27ed38017b..5823ca9e2f 100644 --- a/modules/surrealdb/surrealdb_test.go +++ b/modules/surrealdb/surrealdb_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" "github.com/surrealdb/surrealdb.go" "github.com/testcontainers/testcontainers-go" @@ -12,127 +13,78 @@ import ( func TestSurrealDBSelect(t *testing.T) { ctx := context.Background() - container, err := RunContainer(ctx, testcontainers.WithImage("surrealdb/surrealdb:v1.1.1")) - if err != nil { - t.Fatal(err) - } + ctr, err := Run(ctx, "surrealdb/surrealdb:v1.1.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - url, err := container.URL(ctx) - if err != nil { - t.Fatal(err) - } + url, err := ctr.URL(ctx) + require.NoError(t, err) db, err := surrealdb.New(url) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if _, err := db.Use("test", "test"); err != nil { - t.Fatal(err) - } + _, err = db.Use("test", "test") + require.NoError(t, err) - if _, err := db.Create("person.tobie", map[string]any{ + _, err = db.Create("person.tobie", map[string]any{ "title": "Founder & CEO", "name": map[string]string{ "first": "Tobie", "last": "Morgan Hitchcock", }, "marketing": true, - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err) result, err := db.Select("person.tobie") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resultData := result.([]any)[0].(map[string]interface{}) - if resultData["title"] != "Founder & CEO" { - t.Fatal("title is not Founder & CEO") - } - if resultData["name"].(map[string]interface{})["first"] != "Tobie" { - t.Fatal("name.first is not Tobie") - } - if resultData["name"].(map[string]interface{})["last"] != "Morgan Hitchcock" { - t.Fatal("name.last is not Morgan Hitchcock") - } - if resultData["marketing"] != true { - t.Fatal("marketing is not true") - } + require.Equal(t, "Founder & CEO", resultData["title"]) + require.Equal(t, "Tobie", resultData["name"].(map[string]interface{})["first"]) + require.Equal(t, "Morgan Hitchcock", resultData["name"].(map[string]interface{})["last"]) + require.Equal(t, true, resultData["marketing"]) } func TestSurrealDBWithAuth(t *testing.T) { ctx := context.Background() - container, err := RunContainer(ctx, testcontainers.WithImage("surrealdb/surrealdb:v1.1.1"), WithAuthentication()) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := Run(ctx, "surrealdb/surrealdb:v1.1.1", WithAuthentication()) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // websocketURL { - url, err := container.URL(ctx) + url, err := ctr.URL(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) db, err := surrealdb.New(url) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if _, err := db.Signin(map[string]string{"user": "root", "pass": "root"}); err != nil { - t.Fatal(err) - } + _, err = db.Signin(map[string]string{"user": "root", "pass": "root"}) + require.NoError(t, err) - if _, err := db.Use("test", "test"); err != nil { - t.Fatal(err) - } + _, err = db.Use("test", "test") + require.NoError(t, err) - if _, err := db.Create("person.tobie", map[string]any{ + _, err = db.Create("person.tobie", map[string]any{ "title": "Founder & CEO", "name": map[string]string{ "first": "Tobie", "last": "Morgan Hitchcock", }, "marketing": true, - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err) result, err := db.Select("person.tobie") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resultData := result.([]any)[0].(map[string]interface{}) - if resultData["title"] != "Founder & CEO" { - t.Fatal("title is not Founder & CEO") - } - if resultData["name"].(map[string]interface{})["first"] != "Tobie" { - t.Fatal("name.first is not Tobie") - } - if resultData["name"].(map[string]interface{})["last"] != "Morgan Hitchcock" { - t.Fatal("name.last is not Morgan Hitchcock") - } - if resultData["marketing"] != true { - t.Fatal("marketing is not true") - } + require.Equal(t, "Founder & CEO", resultData["title"]) + require.Equal(t, "Tobie", resultData["name"].(map[string]interface{})["first"]) + require.Equal(t, "Morgan Hitchcock", resultData["name"].(map[string]interface{})["last"]) + require.Equal(t, true, resultData["marketing"]) } diff --git a/modules/valkey/Makefile b/modules/valkey/Makefile new file mode 100644 index 0000000000..5dac4cdf8d --- /dev/null +++ b/modules/valkey/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-valkey diff --git a/modules/valkey/examples_test.go b/modules/valkey/examples_test.go new file mode 100644 index 0000000000..c700e8d3f3 --- /dev/null +++ b/modules/valkey/examples_test.go @@ -0,0 +1,44 @@ +package valkey_test + +import ( + "context" + "fmt" + "log" + "path/filepath" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/valkey" +) + +func ExampleRun() { + // runValkeyContainer { + ctx := context.Background() + + valkeyContainer, err := valkey.Run(ctx, + "valkey/valkey:7.2.5", + valkey.WithSnapshotting(10, 1), + valkey.WithLogLevel(valkey.LogLevelVerbose), + valkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf")), + ) + defer func() { + if err := testcontainers.TerminateContainer(valkeyContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := valkeyContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} diff --git a/modules/valkey/go.mod b/modules/valkey/go.mod new file mode 100644 index 0000000000..1b4105c155 --- /dev/null +++ b/modules/valkey/go.mod @@ -0,0 +1,64 @@ +module github.com/testcontainers/testcontainers-go/modules/valkey + +go 1.22 + +require ( + github.com/google/uuid v1.6.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 + github.com/valkey-io/valkey-go v1.0.41 +) + +replace github.com/testcontainers/testcontainers-go => ../.. + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/containerd/containerd v1.7.19 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/modules/valkey/go.sum b/modules/valkey/go.sum new file mode 100644 index 0000000000..19e158d82c --- /dev/null +++ b/modules/valkey/go.sum @@ -0,0 +1,187 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= +github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/valkey-io/valkey-go v1.0.41 h1:pWgh9MP24Vl0ANZ0KxEMwB/LHvTUKwlm2SPuWIrSlFw= +github.com/valkey-io/valkey-go v1.0.41/go.mod h1:LXqAbjygRuA1YRocojTslAGx2dQB4p8feaseGviWka4= +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/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +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.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/valkey/options_test.go b/modules/valkey/options_test.go new file mode 100644 index 0000000000..1d1dac1149 --- /dev/null +++ b/modules/valkey/options_test.go @@ -0,0 +1,141 @@ +package valkey + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" +) + +func TestWithConfigFile(t *testing.T) { + tests := []struct { + name string + cmds []string + expectedCmds []string + }{ + { + name: "no existing command", + cmds: []string{}, + expectedCmds: []string{valkeyServerProcess, "/usr/local/valkey.conf"}, + }, + { + name: "existing redis-server command as first argument", + cmds: []string{valkeyServerProcess, "a", "b", "c"}, + expectedCmds: []string{valkeyServerProcess, "/usr/local/valkey.conf", "a", "b", "c"}, + }, + { + name: "non existing redis-server command", + cmds: []string{"a", "b", "c"}, + expectedCmds: []string{valkeyServerProcess, "/usr/local/valkey.conf", "a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Cmd: tt.cmds, + }, + } + + err := WithConfigFile("redis.conf")(req) + require.NoError(t, err) + + require.Equal(t, tt.expectedCmds, req.Cmd) + }) + } +} + +func TestWithLogLevel(t *testing.T) { + tests := []struct { + name string + cmds []string + expectedCmds []string + }{ + { + name: "no existing command", + cmds: []string{}, + expectedCmds: []string{valkeyServerProcess, "--loglevel", "debug"}, + }, + { + name: "existing redis-server command as first argument", + cmds: []string{valkeyServerProcess, "a", "b", "c"}, + expectedCmds: []string{valkeyServerProcess, "a", "b", "c", "--loglevel", "debug"}, + }, + { + name: "non existing redis-server command", + cmds: []string{"a", "b", "c"}, + expectedCmds: []string{valkeyServerProcess, "a", "b", "c", "--loglevel", "debug"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Cmd: tt.cmds, + }, + } + + err := WithLogLevel(LogLevelDebug)(req) + require.NoError(t, err) + + require.Equal(t, tt.expectedCmds, req.Cmd) + }) + } +} + +func TestWithSnapshotting(t *testing.T) { + tests := []struct { + name string + cmds []string + expectedCmds []string + seconds int + changedKeys int + }{ + { + name: "no existing command", + cmds: []string{}, + seconds: 60, + changedKeys: 100, + expectedCmds: []string{valkeyServerProcess, "--save", "60", "100"}, + }, + { + name: "existing redis-server command as first argument", + cmds: []string{valkeyServerProcess, "a", "b", "c"}, + seconds: 60, + changedKeys: 100, + expectedCmds: []string{valkeyServerProcess, "a", "b", "c", "--save", "60", "100"}, + }, + { + name: "non existing redis-server command", + cmds: []string{"a", "b", "c"}, + seconds: 60, + changedKeys: 100, + expectedCmds: []string{valkeyServerProcess, "a", "b", "c", "--save", "60", "100"}, + }, + { + name: "existing redis-server command as first argument", + cmds: []string{valkeyServerProcess, "a", "b", "c"}, + seconds: 0, + changedKeys: 0, + expectedCmds: []string{valkeyServerProcess, "a", "b", "c", "--save", "1", "1"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Cmd: tt.cmds, + }, + } + + err := WithSnapshotting(tt.seconds, tt.changedKeys)(req) + require.NoError(t, err) + + require.Equal(t, tt.expectedCmds, req.Cmd) + }) + } +} diff --git a/modules/valkey/testdata/valkey7.conf b/modules/valkey/testdata/valkey7.conf new file mode 100644 index 0000000000..2dbdc28117 --- /dev/null +++ b/modules/valkey/testdata/valkey7.conf @@ -0,0 +1,2276 @@ +# Redis configuration file example. +# +# Note that in order to read the configuration file, Redis must be +# started with the file path as first argument: +# +# ./redis-server /path/to/redis.conf + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis servers but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Note that option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# Included paths may contain wildcards. All files matching the wildcards will +# be included in alphabetical order. +# Note that if an include path contains a wildcards but no files match it when +# the server is started, the include statement will be ignored and no error will +# be emitted. It is safe, therefore, to include wildcard files from empty +# directories. +# +# include /path/to/local.conf +# include /path/to/other.conf +# include /path/to/fragments/*.conf +# + +################################## MODULES ##################################### + +# Load modules at startup. If the server is not able to load modules +# it will abort. It is possible to use multiple loadmodule directives. +# +# loadmodule /path/to/my_module.so +# loadmodule /path/to/other_module.so + +################################## NETWORK ##################################### + +# By default, if no "bind" configuration directive is specified, Redis listens +# for connections from all available network interfaces on the host machine. +# It is possible to listen to just one or multiple selected interfaces using +# the "bind" configuration directive, followed by one or more IP addresses. +# Each address can be prefixed by "-", which means that redis will not fail to +# start if the address is not available. Being not available only refers to +# addresses that does not correspond to any network interface. Addresses that +# are already in use will always fail, and unsupported protocols will always BE +# silently skipped. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses +# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6 +# bind * -::* # like the default, all available interfaces +# +# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the +# internet, binding to all the interfaces is dangerous and will expose the +# instance to everybody on the internet. So by default we uncomment the +# following bind directive, that will force Redis to listen only on the +# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis +# will only be able to accept client connections from the same host that it is +# running on). +# +# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES +# COMMENT OUT THE FOLLOWING LINE. +# +# You will also need to set a password unless you explicitly disable protected +# mode. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#bind 127.0.0.1 -::1 + +# By default, outgoing connections (from replica to master, from Sentinel to +# instances, cluster bus, etc.) are not bound to a specific local address. In +# most cases, this means the operating system will handle that based on routing +# and the interface through which the connection goes out. +# +# Using bind-source-addr it is possible to configure a specific address to bind +# to, which may also affect how the connection gets routed. +# +# Example: +# +# bind-source-addr 10.0.0.1 + +# Protected mode is a layer of security protection, in order to avoid that +# Redis instances left open on the internet are accessed and exploited. +# +# When protected mode is on and the default user has no password, the server +# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address +# (::1) or Unix domain sockets. +# +# By default protected mode is enabled. You should disable it only if +# you are sure you want clients from other hosts to connect to Redis +# even if no authentication is configured. +protected-mode no + +# Redis uses default hardened security configuration directives to reduce the +# attack surface on innocent users. Therefore, several sensitive configuration +# directives are immutable, and some potentially-dangerous commands are blocked. +# +# Configuration directives that control files that Redis writes to (e.g., 'dir' +# and 'dbfilename') and that aren't usually modified during runtime +# are protected by making them immutable. +# +# Commands that can increase the attack surface of Redis and that aren't usually +# called by users are blocked by default. +# +# These can be exposed to either all connections or just local ones by setting +# each of the configs listed below to either of these values: +# +# no - Block for any connection (remain immutable) +# yes - Allow for any connection (no protection) +# local - Allow only for local connections. Ones originating from the +# IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets. +# +# enable-protected-configs no +# enable-debug-command no +# enable-module-command no + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /run/redis.sock +# unixsocketperm 700 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Force network equipment in the middle to consider the connection to be +# alive. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +# Apply OS-specific mechanism to mark the listening socket with the specified +# ID, to support advanced routing and filtering capabilities. +# +# On Linux, the ID represents a connection mark. +# On FreeBSD, the ID represents a socket cookie ID. +# On OpenBSD, the ID represents a route table ID. +# +# The default value is 0, which implies no marking is required. +# socket-mark-id 0 + +################################# TLS/SSL ##################################### + +# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration +# directive can be used to define TLS-listening ports. To enable TLS on the +# default port, use: +# +# port 0 +# tls-port 6379 + +# Configure a X.509 certificate and private key to use for authenticating the +# server to connected clients, masters or cluster peers. These files should be +# PEM formatted. +# +# tls-cert-file redis.crt +# tls-key-file redis.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-key-file-pass secret + +# Normally Redis uses the same certificate for both server functions (accepting +# connections) and client functions (replicating from a master, establishing +# cluster bus connections, etc.). +# +# Sometimes certificates are issued with attributes that designate them as +# client-only or server-only certificates. In that case it may be desired to use +# different certificates for incoming (server) and outgoing (client) +# connections. To do that, use the following directives: +# +# tls-client-cert-file client.crt +# tls-client-key-file client.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-client-key-file-pass secret + +# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange, +# required by older versions of OpenSSL (<3.0). Newer versions do not require +# this configuration and recommend against it. +# +# tls-dh-params-file redis.dh + +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. +# +# tls-ca-cert-file ca.crt +# tls-ca-cert-dir /etc/ssl/certs + +# By default, clients (including replica servers) on a TLS port are required +# to authenticate using valid client side certificates. +# +# If "no" is specified, client certificates are not required and not accepted. +# If "optional" is specified, client certificates are accepted and must be +# valid if provided, but are not required. +# +# tls-auth-clients no +# tls-auth-clients optional + +# By default, a Redis replica does not attempt to establish a TLS connection +# with its master. +# +# Use the following directive to enable TLS on replication links. +# +# tls-replication yes + +# By default, the Redis Cluster bus uses a plain TCP connection. To enable +# TLS for the bus protocol, use the following directive: +# +# tls-cluster yes + +# By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended +# that older formally deprecated versions are kept disabled to reduce the attack surface. +# You can explicitly specify TLS versions to support. +# Allowed values are case insensitive and include "TLSv1", "TLSv1.1", "TLSv1.2", +# "TLSv1.3" (OpenSSL >= 1.1.1) or any combination. +# To enable only TLSv1.2 and TLSv1.3, use: +# +# tls-protocols "TLSv1.2 TLSv1.3" + +# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information +# about the syntax of this string. +# +# Note: this configuration applies only to <= TLSv1.2. +# +# tls-ciphers DEFAULT:!MEDIUM + +# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more +# information about the syntax of this string, and specifically for TLSv1.3 +# ciphersuites. +# +# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + +# When choosing a cipher, use the server's preference instead of the client +# preference. By default, the server follows the client's preference. +# +# tls-prefer-server-ciphers yes + +# By default, TLS session caching is enabled to allow faster and less expensive +# reconnections by clients that support it. Use the following directive to disable +# caching. +# +# tls-session-caching no + +# Change the default number of TLS sessions cached. A zero value sets the cache +# to unlimited size. The default size is 20480. +# +# tls-session-cache-size 5000 + +# Change the default timeout of cached TLS sessions. The default timeout is 300 +# seconds. +# +# tls-session-cache-timeout 60 + +################################# GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +# When Redis is supervised by upstart or systemd, this parameter has no impact. +daemonize no + +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# requires "expect stop" in your upstart job config +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# on startup, and updating Redis status on a regular +# basis. +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous pings back to your supervisor. +# +# The default is "no". To run under upstart/systemd, you can simply uncomment +# the line below: +# +# supervised auto + +# If a pid file is specified, Redis writes it where specified at startup +# and removes it at exit. +# +# When the server runs non daemonized, no pid file is created if none is +# specified in the configuration. When the server is daemonized, the pid file +# is used even if not specified, defaulting to "/var/run/redis.pid". +# +# Creating a pid file is best effort: if Redis is not able to create it +# nothing bad happens, the server will start and run normally. +# +# Note that on modern Linux systems "/run/redis.pid" is more conforming +# and should be used instead. +pidfile /var/run/redis_6379.pid + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# To disable the built in crash log, which will possibly produce cleaner core +# dumps when they are needed, uncomment the following: +# +# crash-log-enabled no + +# To disable the fast memory check that's run as part of the crash log, which +# will possibly let redis terminate sooner, uncomment the following: +# +# crash-memcheck-enabled no + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY and syslog logging is +# disabled. Basically this means that normally a logo is displayed only in +# interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo no + +# By default, Redis modifies the process title (as seen in 'top' and 'ps') to +# provide some runtime information. It is possible to disable this and leave +# the process name as executed by setting the following to no. +set-proc-title yes + +# When changing the process title, Redis uses the following template to construct +# the modified title. +# +# Template variables are specified in curly brackets. The following variables are +# supported: +# +# {title} Name of process as executed if parent, or type of child process. +# {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or +# Unix socket if only that's available. +# {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]". +# {port} TCP port listening on, or 0. +# {tls-port} TLS port listening on, or 0. +# {unixsocket} Unix domain socket listening on, or "". +# {config-file} Name of configuration file used. +# +proc-title-template "{title} {listen-addr} {server-mode}" + +################################ SNAPSHOTTING ################################ + +# Save the DB to disk. +# +# save [ ...] +# +# Redis will save the DB if the given number of seconds elapsed and it +# surpassed the given number of write operations against the DB. +# +# Snapshotting can be completely disabled with a single empty string argument +# as in following example: +# +# save "" +# +# Unless specified otherwise, by default Redis will save the DB: +# * After 3600 seconds (an hour) if at least 1 change was performed +# * After 300 seconds (5 minutes) if at least 100 changes were performed +# * After 60 seconds if at least 10000 changes were performed +# +# You can set these explicitly by uncommenting the following line. +# +# save 3600 1 300 100 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# Enables or disables full sanitization checks for ziplist and listpack etc when +# loading an RDB or RESTORE payload. This reduces the chances of a assertion or +# crash later on while processing commands. +# Options: +# no - Never perform full sanitization +# yes - Always perform full sanitization +# clients - Perform full sanitization only for user connections. +# Excludes: RDB files, RESTORE commands received from the master +# connection, and client connections which have the +# skip-sanitize-payload ACL flag. +# The default should be 'clients' but since it currently affects cluster +# resharding via MIGRATE, it is temporarily set to 'no' by default. +# +# sanitize-dump-payload no + +# The filename where to dump the DB +dbfilename dump.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Replica replication. Use replicaof to make a Redis instance a copy of +# another Redis server. A few things to understand ASAP about Redis replication. +# +# +------------------+ +---------------+ +# | Master | ---> | Replica | +# | (receive writes) | | (exact copy) | +# +------------------+ +---------------+ +# +# 1) Redis replication is asynchronous, but you can configure a master to +# stop accepting writes if it appears to be not connected with at least +# a given number of replicas. +# 2) Redis replicas are able to perform a partial resynchronization with the +# master if the replication link is lost for a relatively small amount of +# time. You may want to configure the replication backlog size (see the next +# sections of this file) with a sensible value depending on your needs. +# 3) Replication is automatic and does not need user intervention. After a +# network partition replicas automatically try to reconnect to masters +# and resynchronize with them. +# +# replicaof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the replica to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the replica request. +# +# masterauth +# +# However this is not enough if you are using Redis ACLs (for Redis version +# 6 or greater), and the default user is not capable of running the PSYNC +# command and/or other commands needed for replication. In this case it's +# better to configure a special user to use with replication, and specify the +# masteruser configuration as such: +# +# masteruser +# +# When masteruser is specified, the replica will authenticate against its +# master using the new AUTH form: AUTH . + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with error +# "MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'" +# to all data access commands, excluding commands such as: +# INFO, REPLICAOF, AUTH, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync yes + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# When diskless replication is enabled with a delay, it is possible to let +# the replication start before the maximum delay is reached if the maximum +# number of replicas expected have connected. Default of 0 means that the +# maximum is not defined and Redis will wait the full delay. +repl-diskless-sync-max-replicas 0 + +# ----------------------------------------------------------------------------- +# WARNING: RDB diskless load is experimental. Since in this setup the replica +# does not immediately store an RDB on disk, it may cause data loss during +# failovers. RDB diskless load + Redis modules not handling I/O reads may also +# cause Redis to abort in case of I/O errors during the initial synchronization +# stage with the master. Use only if you know what you are doing. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and replica buffers). +# However, parsing the RDB file directly from the socket may mean that we have +# to flush the contents of the current database before the full rdb was +# received. For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "on-empty-db" - Use diskless load only when it is completely safe. +# "swapdb" - Keep current db contents in RAM while parsing the data directly +# from the socket. Replicas in this mode can keep serving current +# data set while replication is in progress, except for cases where +# they can't recognize master as having a data set from same +# replication history. +# Note that this requires sufficient memory, if you don't have it, +# you risk an OOM kill. +repl-diskless-load disabled + +# Master send PINGs to its replicas in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# replica data when replicas are disconnected for some time, so that when a +# replica wants to reconnect again, often a full resync is not needed, but a +# partial resync is enough, just passing the portion of data the replica +# missed while disconnected. +# +# The bigger the replication backlog, the longer the replica can endure the +# disconnect and later be able to perform a partial resynchronization. +# +# The backlog is only allocated if there is at least one replica connected. +# +# repl-backlog-size 1mb + +# After a master has no connected replicas for some time, the backlog will be +# freed. The following option configures the amount of seconds that need to +# elapse, starting from the time the last replica disconnected, for the backlog +# buffer to be freed. +# +# Note that replicas never free the backlog for timeout, since they may be +# promoted to masters later, and should be able to correctly "partially +# resynchronize" with other replicas: hence they should always accumulate backlog. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# The propagation error behavior controls how Redis will behave when it is +# unable to handle a command being processed in the replication stream from a master +# or processed while reading from an AOF file. Errors that occur during propagation +# are unexpected, and can cause data inconsistency. However, there are edge cases +# in earlier versions of Redis where it was possible for the server to replicate or persist +# commands that would fail on future versions. For this reason the default behavior +# is to ignore such errors and continue processing commands. +# +# If an application wants to ensure there is no data divergence, this configuration +# should be set to 'panic' instead. The value can also be set to 'panic-on-replicas' +# to only panic when a replica encounters an error on the replication stream. One of +# these two panic values will become the default value in the future once there are +# sufficient safety mechanisms in place to prevent false positive crashes. +# +# propagation-error-behavior ignore + +# Replica ignore disk write errors controls the behavior of a replica when it is +# unable to persist a write command received from its master to disk. By default, +# this configuration is set to 'no' and will crash the replica in this condition. +# It is not recommended to change this default, however in order to be compatible +# with older versions of Redis this config can be toggled to 'yes' which will just +# log a warning and execute the write command it got from the master. +# +# replica-ignore-disk-write-errors no + +# ----------------------------------------------------------------------------- +# By default, Redis Sentinel includes all replicas in its reports. A replica +# can be excluded from Redis Sentinel's announcements. An unannounced replica +# will be ignored by the 'sentinel replicas ' command and won't be +# exposed to Redis Sentinel's clients. +# +# This option does not change the behavior of replica-priority. Even with +# replica-announced set to 'no', the replica can be promoted to master. To +# prevent this behavior, set replica-priority to 0. +# +# replica-announced yes + +# It is possible for a master to stop accepting writes if there are less than +# N replicas connected, having a lag less or equal than M seconds. +# +# The N replicas need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the replica, that is usually sent every second. +# +# This option does not GUARANTEE that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough replicas +# are available, to the specified number of seconds. +# +# For example to require at least 3 replicas with a lag <= 10 seconds use: +# +# min-replicas-to-write 3 +# min-replicas-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-replicas-to-write is set to 0 (feature disabled) and +# min-replicas-max-lag is set to 10. + +# A Redis master is able to list the address and port of the attached +# replicas in different ways. For example the "INFO replication" section +# offers this information, which is used, among other tools, by +# Redis Sentinel in order to discover replica instances. +# Another place where this info is available is in the output of the +# "ROLE" command of a master. +# +# The listed IP address and port normally reported by a replica is +# obtained in the following way: +# +# IP: The address is auto detected by checking the peer address +# of the socket used by the replica to connect with the master. +# +# Port: The port is communicated by the replica during the replication +# handshake, and is normally the port that the replica is using to +# listen for connections. +# +# However when port forwarding or Network Address Translation (NAT) is +# used, the replica may actually be reachable via different IP and port +# pairs. The following two options can be used by a replica in order to +# report to its master a specific set of IP and port, so that both INFO +# and ROLE will report those values. +# +# There is no need to use both the options if you need to override just +# the port or the IP address. +# +# replica-announce-ip 5.5.5.5 +# replica-announce-port 1234 + +############################### KEYS TRACKING ################################# + +# Redis implements server assisted support for client side caching of values. +# This is implemented using an invalidation table that remembers, using +# a radix key indexed by key name, what clients have which keys. In turn +# this is used in order to send invalidation messages to clients. Please +# check this page to understand more about the feature: +# +# https://redis.io/topics/client-side-caching +# +# When tracking is enabled for a client, all the read only queries are assumed +# to be cached: this will force Redis to store information in the invalidation +# table. When keys are modified, such information is flushed away, and +# invalidation messages are sent to the clients. However if the workload is +# heavily dominated by reads, Redis could use more and more memory in order +# to track the keys fetched by many clients. +# +# For this reason it is possible to configure a maximum fill value for the +# invalidation table. By default it is set to 1M of keys, and once this limit +# is reached, Redis will start to evict keys in the invalidation table +# even if they were not modified, just to reclaim memory: this will in turn +# force the clients to invalidate the cached values. Basically the table +# maximum size is a trade off between the memory you want to spend server +# side to track information about who cached what, and the ability of clients +# to retain cached objects in memory. +# +# If you set the value to 0, it means there are no limits, and Redis will +# retain as many keys as needed in the invalidation table. +# In the "stats" INFO section, you can find information about the number of +# keys in the invalidation table at every given moment. +# +# Note: when key tracking is used in broadcasting mode, no memory is used +# in the server side so this setting is useless. +# +# tracking-table-max-keys 1000000 + +################################## SECURITY ################################### + +# Warning: since Redis is pretty fast, an outside user can try up to +# 1 million passwords per second against a modern box. This means that you +# should use very strong passwords, otherwise they will be very easy to break. +# Note that because the password is really a shared secret between the client +# and the server, and should not be memorized by any human, the password +# can be easily a long string from /dev/urandom or whatever, so by using a +# long and unguessable password no brute force attack will be possible. + +# Redis ACL users are defined in the following format: +# +# user ... acl rules ... +# +# For example: +# +# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 +# +# The special username "default" is used for new connections. If this user +# has the "nopass" rule, then new connections will be immediately authenticated +# as the "default" user without the need of any password provided via the +# AUTH command. Otherwise if the "default" user is not flagged with "nopass" +# the connections will start in not authenticated state, and will require +# AUTH (or the HELLO command AUTH option) in order to be authenticated and +# start to work. +# +# The ACL rules that describe what a user can do are the following: +# +# on Enable the user: it is possible to authenticate as this user. +# off Disable the user: it's no longer possible to authenticate +# with this user, however the already authenticated connections +# will still work. +# skip-sanitize-payload RESTORE dump-payload sanitization is skipped. +# sanitize-payload RESTORE dump-payload is sanitized (default). +# + Allow the execution of that command. +# May be used with `|` for allowing subcommands (e.g "+config|get") +# - Disallow the execution of that command. +# May be used with `|` for blocking subcommands (e.g "-config|set") +# +@ Allow the execution of all the commands in such category +# with valid categories are like @admin, @set, @sortedset, ... +# and so forth, see the full list in the server.c file where +# the Redis command table is described and defined. +# The special category @all means all the commands, but currently +# present in the server, and that will be loaded in the future +# via modules. +# +|first-arg Allow a specific first argument of an otherwise +# disabled command. It is only supported on commands with +# no sub-commands, and is not allowed as negative form +# like -SELECT|1, only additive starting with "+". This +# feature is deprecated and may be removed in the future. +# allcommands Alias for +@all. Note that it implies the ability to execute +# all the future commands loaded via the modules system. +# nocommands Alias for -@all. +# ~ Add a pattern of keys that can be mentioned as part of +# commands. For instance ~* allows all the keys. The pattern +# is a glob-style pattern like the one of KEYS. +# It is possible to specify multiple patterns. +# %R~ Add key read pattern that specifies which keys can be read +# from. +# %W~ Add key write pattern that specifies which keys can be +# written to. +# allkeys Alias for ~* +# resetkeys Flush the list of allowed keys patterns. +# & Add a glob-style pattern of Pub/Sub channels that can be +# accessed by the user. It is possible to specify multiple channel +# patterns. +# allchannels Alias for &* +# resetchannels Flush the list of allowed channel patterns. +# > Add this password to the list of valid password for the user. +# For example >mypass will add "mypass" to the list. +# This directive clears the "nopass" flag (see later). +# < Remove this password from the list of valid passwords. +# nopass All the set passwords of the user are removed, and the user +# is flagged as requiring no password: it means that every +# password will work against this user. If this directive is +# used for the default user, every new connection will be +# immediately authenticated with the default user without +# any explicit AUTH command required. Note that the "resetpass" +# directive will clear this condition. +# resetpass Flush the list of allowed passwords. Moreover removes the +# "nopass" status. After "resetpass" the user has no associated +# passwords and there is no way to authenticate without adding +# some password (or setting it as "nopass" later). +# reset Performs the following actions: resetpass, resetkeys, off, +# -@all. The user returns to the same state it has immediately +# after its creation. +# () Create a new selector with the options specified within the +# parentheses and attach it to the user. Each option should be +# space separated. The first character must be ( and the last +# character must be ). +# clearselectors Remove all of the currently attached selectors. +# Note this does not change the "root" user permissions, +# which are the permissions directly applied onto the +# user (outside the parentheses). +# +# ACL rules can be specified in any order: for instance you can start with +# passwords, then flags, or key patterns. However note that the additive +# and subtractive rules will CHANGE MEANING depending on the ordering. +# For instance see the following example: +# +# user alice on +@all -DEBUG ~* >somepassword +# +# This will allow "alice" to use all the commands with the exception of the +# DEBUG command, since +@all added all the commands to the set of the commands +# alice can use, and later DEBUG was removed. However if we invert the order +# of two ACL rules the result will be different: +# +# user alice on -DEBUG +@all ~* >somepassword +# +# Now DEBUG was removed when alice had yet no commands in the set of allowed +# commands, later all the commands are added, so the user will be able to +# execute everything. +# +# Basically ACL rules are processed left-to-right. +# +# The following is a list of command categories and their meanings: +# * keyspace - Writing or reading from keys, databases, or their metadata +# in a type agnostic way. Includes DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE, +# KEYS, EXPIRE, TTL, FLUSHALL, etc. Commands that may modify the keyspace, +# key or metadata will also have `write` category. Commands that only read +# the keyspace, key or metadata will have the `read` category. +# * read - Reading from keys (values or metadata). Note that commands that don't +# interact with keys, will not have either `read` or `write`. +# * write - Writing to keys (values or metadata) +# * admin - Administrative commands. Normal applications will never need to use +# these. Includes REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN, etc. +# * dangerous - Potentially dangerous (each should be considered with care for +# various reasons). This includes FLUSHALL, MIGRATE, RESTORE, SORT, KEYS, +# CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc. +# * connection - Commands affecting the connection or other connections. +# This includes AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc. +# * blocking - Potentially blocking the connection until released by another +# command. +# * fast - Fast O(1) commands. May loop on the number of arguments, but not the +# number of elements in the key. +# * slow - All commands that are not Fast. +# * pubsub - PUBLISH / SUBSCRIBE related +# * transaction - WATCH / MULTI / EXEC related commands. +# * scripting - Scripting related. +# * set - Data type: sets related. +# * sortedset - Data type: zsets related. +# * list - Data type: lists related. +# * hash - Data type: hashes related. +# * string - Data type: strings related. +# * bitmap - Data type: bitmaps related. +# * hyperloglog - Data type: hyperloglog related. +# * geo - Data type: geo related. +# * stream - Data type: streams related. +# +# For more information about ACL configuration please refer to +# the Redis web site at https://redis.io/topics/acl + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +# Using an external ACL file +# +# Instead of configuring users here in this file, it is possible to use +# a stand-alone file just listing users. The two methods cannot be mixed: +# if you configure users here and at the same time you activate the external +# ACL file, the server will refuse to start. +# +# The format of the external ACL user file is exactly the same as the +# format that is used inside redis.conf to describe users. +# +# aclfile /etc/redis/users.acl + +# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility +# layer on top of the new ACL system. The option effect will be just setting +# the password for the default user. Clients will still authenticate using +# AUTH as usually, or more explicitly with AUTH default +# if they follow the new protocol: both will work. +# +# The requirepass is not compatible with aclfile option and the ACL LOAD +# command, these will cause requirepass to be ignored. +# +# requirepass foobared + +# New users are initialized with restrictive permissions by default, via the +# equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it +# is possible to manage access to Pub/Sub channels with ACL rules as well. The +# default Pub/Sub channels permission if new users is controlled by the +# acl-pubsub-default configuration directive, which accepts one of these values: +# +# allchannels: grants access to all Pub/Sub channels +# resetchannels: revokes access to all Pub/Sub channels +# +# From Redis 7.0, acl-pubsub-default defaults to 'resetchannels' permission. +# +# acl-pubsub-default resetchannels + +# Command renaming (DEPRECATED). +# +# ------------------------------------------------------------------------ +# WARNING: avoid using this option if possible. Instead use ACLs to remove +# commands from the default user, and put them only in some admin user you +# create for administrative purposes. +# ------------------------------------------------------------------------ +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to replicas may cause problems. + +################################### CLIENTS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# IMPORTANT: When Redis Cluster is used, the max number of connections is also +# shared with the cluster bus: every node in the cluster will use two +# connections, one incoming and another outgoing. It is important to size the +# limit accordingly in case of very large clusters. +# +# maxclients 10000 + +############################## MEMORY MANAGEMENT ################################ + +# Set a memory usage limit to the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU or LFU cache, or to +# set a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have replicas attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the replicas are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of replicas is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have replicas attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for replica +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select one from the following behaviors: +# +# volatile-lru -> Evict using approximated LRU, only keys with an expire set. +# allkeys-lru -> Evict any key using approximated LRU. +# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. +# allkeys-lfu -> Evict any key using approximated LFU. +# volatile-random -> Remove a random key having an expire set. +# allkeys-random -> Remove a random key, any key. +# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) +# noeviction -> Don't evict anything, just return an error on write operations. +# +# LRU means Least Recently Used +# LFU means Least Frequently Used +# +# Both LRU, LFU and volatile-ttl are implemented using approximated +# randomized algorithms. +# +# Note: with any of the above policies, when there are no suitable keys for +# eviction, Redis will return an error on write operations that require +# more memory. These are usually commands that create new keys, add data or +# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE, +# SORT (due to the STORE argument), and EXEC (if the transaction includes any +# command that requires memory). +# +# The default is: +# +# maxmemory-policy noeviction + +# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can tune it for speed or +# accuracy. By default Redis will check five keys and pick the one that was +# used least recently, you can change the sample size using the following +# configuration directive. +# +# The default of 5 produces good enough results. 10 Approximates very closely +# true LRU but costs more CPU. 3 is faster but not very accurate. +# +# maxmemory-samples 5 + +# Eviction processing is designed to function well with the default setting. +# If there is an unusually large amount of write traffic, this value may need to +# be increased. Decreasing this value may reduce latency at the risk of +# eviction processing effectiveness +# 0 = minimum latency, 10 = default, 100 = process without regard to latency +# +# maxmemory-eviction-tenacity 10 + +# Starting from Redis 5, by default a replica will ignore its maxmemory setting +# (unless it is promoted to master after a failover or manually). It means +# that the eviction of keys will be just handled by the master, sending the +# DEL commands to the replica as keys evict in the master side. +# +# This behavior ensures that masters and replicas stay consistent, and is usually +# what you want, however if your replica is writable, or you want the replica +# to have a different memory setting, and you are sure all the writes performed +# to the replica are idempotent, then you may change this default (but be sure +# to understand what you are doing). +# +# Note that since the replica by default does not evict, it may end using more +# memory than the one set via maxmemory (there are certain buffers that may +# be larger on the replica, or data structures may sometimes take more memory +# and so forth). So make sure you monitor your replicas and make sure they +# have enough memory to never hit a real out-of-memory condition before the +# master hits the configured maxmemory setting. +# +# replica-ignore-maxmemory yes + +# Redis reclaims expired keys in two ways: upon access when those keys are +# found to be expired, and also in background, in what is called the +# "active expire key". The key space is slowly and interactively scanned +# looking for expired keys to reclaim, so that it is possible to free memory +# of keys that are expired and will never be accessed again in a short time. +# +# The default effort of the expire cycle will try to avoid having more than +# ten percent of expired keys still in memory, and will try to avoid consuming +# more than 25% of total memory and to add latency to the system. However +# it is possible to increase the expire "effort" that is normally set to +# "1", to a greater value, up to the value "10". At its maximum value the +# system will use more CPU, longer cycles (and technically may introduce +# more latency), and will tolerate less already expired keys still present +# in the system. It's a tradeoff between memory, CPU and latency. +# +# active-expire-effort 1 + +############################# LAZY FREEING #################################### + +# Redis has two primitives to delete keys. One is called DEL and is a blocking +# deletion of the object. It means that the server stops processing new commands +# in order to reclaim all the memory associated with an object in a synchronous +# way. If the key deleted is associated with a small object, the time needed +# in order to execute the DEL command is very small and comparable to most other +# O(1) or O(log_N) commands in Redis. However if the key is associated with an +# aggregated value containing millions of elements, the server can block for +# a long time (even seconds) in order to complete the operation. +# +# For the above reasons Redis also offers non blocking deletion primitives +# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and +# FLUSHDB commands, in order to reclaim memory in background. Those commands +# are executed in constant time. Another thread will incrementally free the +# object in the background as fast as possible. +# +# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. +# It's up to the design of the application to understand when it is a good +# idea to use one or the other. However the Redis server sometimes has to +# delete keys or flush the whole database as a side effect of other operations. +# Specifically Redis deletes objects independently of a user call in the +# following scenarios: +# +# 1) On eviction, because of the maxmemory and maxmemory policy configurations, +# in order to make room for new data, without going over the specified +# memory limit. +# 2) Because of expire: when a key with an associated time to live (see the +# EXPIRE command) must be deleted from memory. +# 3) Because of a side effect of a command that stores data on a key that may +# already exist. For example the RENAME command may delete the old key +# content when it is replaced with another one. Similarly SUNIONSTORE +# or SORT with STORE option may delete existing keys. The SET command +# itself removes any old content of the specified key in order to replace +# it with the specified string. +# 4) During replication, when a replica performs a full resynchronization with +# its master, the content of the whole database is removed in order to +# load the RDB file just transferred. +# +# In all the above cases the default is to delete objects in a blocking way, +# like if DEL was called. However you can configure each case specifically +# in order to instead release memory in a non-blocking way like if UNLINK +# was called, using the following configuration directives. + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + +# FLUSHDB, FLUSHALL, SCRIPT FLUSH and FUNCTION FLUSH support both asynchronous and synchronous +# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the +# commands. When neither flag is passed, this directive will be used to determine +# if the data should be deleted asynchronously. + +lazyfree-lazy-user-flush no + +################################ THREADED I/O ################################# + +# Redis is mostly single threaded, however there are certain threaded +# operations such as UNLINK, slow I/O accesses and other things that are +# performed on side threads. +# +# Now it is also possible to handle Redis clients socket reads and writes +# in different I/O threads. Since especially writing is so slow, normally +# Redis users use pipelining in order to speed up the Redis performances per +# core, and spawn multiple instances in order to scale more. Using I/O +# threads it is possible to easily speedup two times Redis without resorting +# to pipelining nor sharding of the instance. +# +# By default threading is disabled, we suggest enabling it only in machines +# that have at least 4 or more cores, leaving at least one spare core. +# Using more than 8 threads is unlikely to help much. We also recommend using +# threaded I/O only if you actually have performance problems, with Redis +# instances being able to use a quite big percentage of CPU time, otherwise +# there is no point in using this feature. +# +# So for instance if you have a four cores boxes, try to use 2 or 3 I/O +# threads, if you have a 8 cores, try to use 6 threads. In order to +# enable I/O threads use the following configuration directive: +# +# io-threads 4 +# +# Setting io-threads to 1 will just use the main thread as usual. +# When I/O threads are enabled, we only use threads for writes, that is +# to thread the write(2) syscall and transfer the client buffers to the +# socket. However it is also possible to enable threading of reads and +# protocol parsing using the following configuration directive, by setting +# it to yes: +# +# io-threads-do-reads no +# +# Usually threading reads doesn't help much. +# +# NOTE 1: This configuration directive cannot be changed at runtime via +# CONFIG SET. Also, this feature currently does not work when SSL is +# enabled. +# +# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make +# sure you also run the benchmark itself in threaded mode, using the +# --threads option to match the number of Redis threads, otherwise you'll not +# be able to notice the improvements. + +############################ KERNEL OOM CONTROL ############################## + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports these options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + + +#################### KERNEL transparent hugepage CONTROL ###################### + +# Usually the kernel Transparent Huge Pages control is set to "madvise" or +# or "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which +# case this config has no effect. On systems in which it is set to "always", +# redis will attempt to disable it specifically for the redis process in order +# to avoid latency problems specifically with fork(2) and CoW. +# If for some reason you prefer to keep it enabled, you can set this config to +# "no" and the kernel global to "always". + +disable-thp yes + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check https://redis.io/topics/persistence for more information. + +appendonly no + +# The base name of the append only file. +# +# Redis 7 and newer use a set of append-only files to persist the dataset +# and changes applied to it. There are two basic types of files in use: +# +# - Base files, which are a snapshot representing the complete state of the +# dataset at the time the file was created. Base files can be either in +# the form of RDB (binary serialized) or AOF (textual commands). +# - Incremental files, which contain additional commands that were applied +# to the dataset following the previous file. +# +# In addition, manifest files are used to track the files and the order in +# which they were created and should be applied. +# +# Append-only file names are created by Redis following a specific pattern. +# The file name's prefix is based on the 'appendfilename' configuration +# parameter, followed by additional information about the sequence and type. +# +# For example, if appendfilename is set to appendonly.aof, the following file +# names could be derived: +# +# - appendonly.aof.1.base.rdb as a base file. +# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files. +# - appendonly.aof.manifest as a manifest file. + +appendfilename "appendonly.aof" + +# For convenience, Redis stores all persistent append-only files in a dedicated +# directory. The name of the directory is determined by the appenddirname +# configuration parameter. + +appenddirname "appendonlydir" + +# The fsync() call tells the Operating System to actually write data on disk +# instead of waiting for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log. Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync no". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# An AOF file may be found to be truncated at the end during the Redis +# startup process, when the AOF data gets loaded back into memory. +# This may happen when the system where Redis is running +# crashes, especially when an ext4 filesystem is mounted without the +# data=ordered option (however this can't happen when Redis itself +# crashes or aborts but the operating system still works correctly). +# +# Redis can either exit with an error when this happens, or load as much +# data as possible (the default now) and start if the AOF file is found +# to be truncated at the end. The following option controls this behavior. +# +# If aof-load-truncated is set to yes, a truncated AOF file is loaded and +# the Redis server starts emitting a log to inform the user of the event. +# Otherwise if the option is set to no, the server aborts with an error +# and refuses to start. When the option is set to no, the user requires +# to fix the AOF file using the "redis-check-aof" utility before to restart +# the server. +# +# Note that if the AOF file will be found to be corrupted in the middle +# the server will still exit with an error. This option only applies when +# Redis will try to read more data from the AOF file but not enough bytes +# will be found. +aof-load-truncated yes + +# Redis can create append-only base files in either RDB or AOF formats. Using +# the RDB format is always faster and more efficient, and disabling it is only +# supported for backward compatibility purposes. +aof-use-rdb-preamble yes + +# Redis supports recording timestamp annotations in the AOF to support restoring +# the data from a specific point-in-time. However, using this capability changes +# the AOF format in a way that may not be compatible with existing AOF parsers. +aof-timestamp-enabled no + +################################ SHUTDOWN ##################################### + +# Maximum time to wait for replicas when shutting down, in seconds. +# +# During shut down, a grace period allows any lagging replicas to catch up with +# the latest replication offset before the master exists. This period can +# prevent data loss, especially for deployments without configured disk backups. +# +# The 'shutdown-timeout' value is the grace period's duration in seconds. It is +# only applicable when the instance has replicas. To disable the feature, set +# the value to 0. +# +# shutdown-timeout 10 + +# When Redis receives a SIGINT or SIGTERM, shutdown is initiated and by default +# an RDB snapshot is written to disk in a blocking operation if save points are configured. +# The options used on signaled shutdown can include the following values: +# default: Saves RDB snapshot only if save points are configured. +# Waits for lagging replicas to catch up. +# save: Forces a DB saving operation even if no save points are configured. +# nosave: Prevents DB saving operation even if one or more save points are configured. +# now: Skips waiting for lagging replicas. +# force: Ignores any errors that would normally prevent the server from exiting. +# +# Any combination of values is allowed as long as "save" and "nosave" are not set simultaneously. +# Example: "nosave force now" +# +# shutdown-on-sigint default +# shutdown-on-sigterm default + +################ NON-DETERMINISTIC LONG BLOCKING COMMANDS ##################### + +# Maximum time in milliseconds for EVAL scripts, functions and in some cases +# modules' commands before Redis can start processing or rejecting other clients. +# +# If the maximum execution time is reached Redis will start to reply to most +# commands with a BUSY error. +# +# In this state Redis will only allow a handful of commands to be executed. +# For instance, SCRIPT KILL, FUNCTION KILL, SHUTDOWN NOSAVE and possibly some +# module specific 'allow-busy' commands. +# +# SCRIPT KILL and FUNCTION KILL will only be able to stop a script that did not +# yet call any write commands, so SHUTDOWN NOSAVE may be the only way to stop +# the server in the case a write command was already issued by the script when +# the user doesn't want to wait for the natural termination of the script. +# +# The default is 5 seconds. It is possible to set it to 0 or a negative value +# to disable this mechanism (uninterrupted execution). Note that in the past +# this config had a different name, which is now an alias, so both of these do +# the same: +# lua-time-limit 5000 +# busy-reply-threshold 5000 + +################################ REDIS CLUSTER ############################### + +# Normal Redis instances can't be part of a Redis Cluster; only nodes that are +# started as cluster nodes can. In order to start a Redis instance as a +# cluster node enable the cluster support uncommenting the following: +# +# cluster-enabled yes + +# Every cluster node has a cluster configuration file. This file is not +# intended to be edited by hand. It is created and updated by Redis nodes. +# Every Redis Cluster node requires a different cluster configuration file. +# Make sure that instances running in the same system do not have +# overlapping cluster configuration file names. +# +# cluster-config-file nodes-6379.conf + +# Cluster node timeout is the amount of milliseconds a node must be unreachable +# for it to be considered in failure state. +# Most other internal time limits are a multiple of the node timeout. +# +# cluster-node-timeout 15000 + +# The cluster port is the port that the cluster bus will listen for inbound connections on. When set +# to the default value, 0, it will be bound to the command port + 10000. Setting this value requires +# you to specify the cluster bus port when executing cluster meet. +# cluster-port 0 + +# A replica of a failing master will avoid to start a failover if its data +# looks too old. +# +# There is no simple way for a replica to actually have an exact measure of +# its "data age", so the following two checks are performed: +# +# 1) If there are multiple replicas able to failover, they exchange messages +# in order to try to give an advantage to the replica with the best +# replication offset (more data from the master processed). +# Replicas will try to get their rank by offset, and apply to the start +# of the failover a delay proportional to their rank. +# +# 2) Every single replica computes the time of the last interaction with +# its master. This can be the last ping or command received (if the master +# is still in the "connected" state), or the time that elapsed since the +# disconnection with the master (if the replication link is currently down). +# If the last interaction is too old, the replica will not try to failover +# at all. +# +# The point "2" can be tuned by user. Specifically a replica will not perform +# the failover if, since the last interaction with the master, the time +# elapsed is greater than: +# +# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period +# +# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor +# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the +# replica will not try to failover if it was not able to talk with the master +# for longer than 310 seconds. +# +# A large cluster-replica-validity-factor may allow replicas with too old data to failover +# a master, while a too small value may prevent the cluster from being able to +# elect a replica at all. +# +# For maximum availability, it is possible to set the cluster-replica-validity-factor +# to a value of 0, which means, that replicas will always try to failover the +# master regardless of the last time they interacted with the master. +# (However they'll always try to apply a delay proportional to their +# offset rank). +# +# Zero is the only value able to guarantee that when all the partitions heal +# the cluster will always be able to continue. +# +# cluster-replica-validity-factor 10 + +# Cluster replicas are able to migrate to orphaned masters, that are masters +# that are left without working replicas. This improves the cluster ability +# to resist to failures as otherwise an orphaned master can't be failed over +# in case of failure if it has no working replicas. +# +# Replicas migrate to orphaned masters only if there are still at least a +# given number of other working replicas for their old master. This number +# is the "migration barrier". A migration barrier of 1 means that a replica +# will migrate only if there is at least 1 other working replica for its master +# and so forth. It usually reflects the number of replicas you want for every +# master in your cluster. +# +# Default is 1 (replicas migrate only if their masters remain with at least +# one replica). To disable migration just set it to a very large value or +# set cluster-allow-replica-migration to 'no'. +# A value of 0 can be set but is useful only for debugging and dangerous +# in production. +# +# cluster-migration-barrier 1 + +# Turning off this option allows to use less automatic cluster configuration. +# It both disables migration to orphaned masters and migration from masters +# that became empty. +# +# Default is 'yes' (allow automatic migrations). +# +# cluster-allow-replica-migration yes + +# By default Redis Cluster nodes stop accepting queries if they detect there +# is at least a hash slot uncovered (no available node is serving it). +# This way if the cluster is partially down (for example a range of hash slots +# are no longer covered) all the cluster becomes, eventually, unavailable. +# It automatically returns available as soon as all the slots are covered again. +# +# However sometimes you want the subset of the cluster which is working, +# to continue to accept queries for the part of the key space that is still +# covered. In order to do so, just set the cluster-require-full-coverage +# option to no. +# +# cluster-require-full-coverage yes + +# This option, when set to yes, prevents replicas from trying to failover its +# master during master failures. However the replica can still perform a +# manual failover, if forced to do so. +# +# This is useful in different scenarios, especially in the case of multiple +# data center operations, where we want one side to never be promoted if not +# in the case of a total DC failure. +# +# cluster-replica-no-failover no + +# This option, when set to yes, allows nodes to serve read traffic while the +# cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + +# This option, when set to yes, allows nodes to serve pubsub shard traffic while +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful if the application would like to use the pubsub feature even when +# the cluster global stable state is not OK. If the application wants to make sure only +# one shard is serving a given channel, this feature should be kept as yes. +# +# cluster-allow-pubsubshard-when-down yes + +# Cluster link send buffer limit is the limit on the memory usage of an individual +# cluster bus link's send buffer in bytes. Cluster links would be freed if they exceed +# this limit. This is to primarily prevent send buffers from growing unbounded on links +# toward slow peers (E.g. PubSub messages being piled up). +# This limit is disabled by default. Enable this limit when 'mem_cluster_links' INFO field +# and/or 'send-buffer-allocated' entries in the 'CLUSTER LINKS` command output continuously increase. +# Minimum limit of 1gb is recommended so that cluster link buffer can fit in at least a single +# PubSub message by default. (client-query-buffer-limit default value is 1gb) +# +# cluster-link-sendbuf-limit 0 + +# Clusters can configure their announced hostname using this config. This is a common use case for +# applications that need to use TLS Server Name Indication (SNI) or dealing with DNS based +# routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS +# command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is +# communicated along the clusterbus to all nodes, setting it to an empty string will remove +# the hostname and also propagate the removal. +# +# cluster-announce-hostname "" + +# Clusters can advertise how clients should connect to them using either their IP address, +# a user defined hostname, or by declaring they have no endpoint. Which endpoint is +# shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type +# config with values 'ip', 'hostname', or 'unknown-endpoint'. This value controls how +# the endpoint returned for MOVED/ASKING requests as well as the first field of CLUSTER SLOTS. +# If the preferred endpoint type is set to hostname, but no announced hostname is set, a '?' +# will be returned instead. +# +# When a cluster advertises itself as having an unknown endpoint, it's indicating that +# the server doesn't know how clients can reach the cluster. This can happen in certain +# networking situations where there are multiple possible routes to the node, and the +# server doesn't know which one the client took. In this case, the server is expecting +# the client to reach out on the same endpoint it used for making the last request, but use +# the port provided in the response. +# +# cluster-preferred-endpoint-type ip + +# In order to setup your cluster make sure to read the documentation +# available at https://redis.io web site. + +########################## CLUSTER DOCKER/NAT support ######################## + +# In certain deployments, Redis Cluster nodes address discovery fails, because +# addresses are NAT-ted or because ports are forwarded (the typical case is +# Docker and other containers). +# +# In order to make Redis Cluster working in such environments, a static +# configuration where each node knows its public address is needed. The +# following four options are used for this scope, and are: +# +# * cluster-announce-ip +# * cluster-announce-port +# * cluster-announce-tls-port +# * cluster-announce-bus-port +# +# Each instructs the node about its address, client ports (for connections +# without and with TLS) and cluster message bus port. The information is then +# published in the header of the bus packets so that other nodes will be able to +# correctly map the address of the node publishing the information. +# +# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set +# to zero, then cluster-announce-port refers to the TLS port. Note also that +# cluster-announce-tls-port has no effect if cluster-tls is set to no. +# +# If the above options are not used, the normal Redis Cluster auto-detection +# will be used instead. +# +# Note that when remapped, the bus port may not be at the fixed offset of +# clients port + 10000, so you can specify any port and bus-port depending +# on how they get remapped. If the bus-port is not set, a fixed offset of +# 10000 will be used as usual. +# +# Example: +# +# cluster-announce-ip 10.1.1.5 +# cluster-announce-tls-port 6379 +# cluster-announce-port 0 +# cluster-announce-bus-port 6380 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +################################ LATENCY TRACKING ############################## + +# The Redis extended latency monitoring tracks the per command latencies and enables +# exporting the percentile distribution via the INFO latencystats command, +# and cumulative latency distributions (histograms) via the LATENCY command. +# +# By default, the extended latency monitoring is enabled since the overhead +# of keeping track of the command latency is very small. +# latency-tracking yes + +# By default the exported latency percentiles via the INFO latencystats command +# are the p50, p99, and p999. +# latency-tracking-info-percentiles 50 99 99.9 + +############################# EVENT NOTIFICATION ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at https://redis.io/topics/notifications +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# n New key events (Note: not included in the 'A' class) +# t Stream commands +# d Module key type events +# m Key-miss events (Note: It is not included in the 'A' class) +# A Alias for g$lshzxetd, so that the "AKE" string means all the events +# (Except key-miss events which are excluded from 'A' due to their +# unique nature). +# +# The "notify-keyspace-events" takes as argument a string that is composed +# of zero or multiple characters. The empty string means that notifications +# are disabled. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-listpack-entries 512 +hash-max-listpack-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-listpack-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-listpack-entries 128 +zset-max-listpack-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entries limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# replica -> replica clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and replica clients, since +# subscribers and replicas receive data in a push fashion. +# +# Note that it doesn't make sense to set the replica clients output buffer +# limit lower than the repl-backlog-size config (partial sync will succeed +# and then replica will get disconnected). +# Such a configuration is ignored (the size of repl-backlog-size will be used). +# This doesn't have memory consumption implications since the replica client +# will share the backlog buffers memory. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Client query buffers accumulate new commands. They are limited to a fixed +# amount by default in order to avoid that a protocol desynchronization (for +# instance due to a bug in the client) will lead to unbound memory usage in +# the query buffer. However you can configure it here if you have very special +# needs, such us huge multi/exec requests or alike. +# +# client-query-buffer-limit 1gb + +# In some scenarios client connections can hog up memory leading to OOM +# errors or data eviction. To avoid this we can cap the accumulated memory +# used by all client connections (all pubsub and normal clients). Once we +# reach that limit connections will be dropped by the server freeing up +# memory. The server will attempt to drop the connections using the most +# memory first. We call this mechanism "client eviction". +# +# Client eviction is configured using the maxmemory-clients setting as follows: +# 0 - client eviction is disabled (default) +# +# A memory value can be used for the client eviction threshold, +# for example: +# maxmemory-clients 1g +# +# A percentage value (between 1% and 100%) means the client eviction threshold +# is based on a percentage of the maxmemory setting. For example to set client +# eviction at 5% of maxmemory: +# maxmemory-clients 5% + +# In the Redis protocol, bulk requests, that are, elements representing single +# strings, are normally limited to 512 mb. However you can change this limit +# here, but must be 1mb or greater +# +# proto-max-bulk-len 512mb + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# Normally it is useful to have an HZ value which is proportional to the +# number of clients connected. This is useful in order, for instance, to +# avoid too many clients are processed for each background task invocation +# in order to avoid latency spikes. +# +# Since the default HZ value by default is conservatively set to 10, Redis +# offers, and enables by default, the ability to use an adaptive HZ value +# which will temporarily raise when there are many connected clients. +# +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good +# idea to start with the default settings and only change them after investigating +# how to improve the performances and how the keys LFU change over time, which +# is possible to inspect via the OBJECT FREQ command. +# +# There are two tunable parameters in the Redis LFU implementation: the +# counter logarithm factor and the counter decay time. It is important to +# understand what the two parameters mean before changing them. +# +# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis +# uses a probabilistic increment with logarithmic behavior. Given the value +# of the old counter, when a key is accessed, the counter is incremented in +# this way: +# +# 1. A random number R between 0 and 1 is extracted. +# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). +# 3. The counter is incremented only if R < P. +# +# The default lfu-log-factor is 10. This is a table of how the frequency +# counter changes with a different number of accesses with different +# logarithmic factors: +# +# +--------+------------+------------+------------+------------+------------+ +# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | +# +--------+------------+------------+------------+------------+------------+ +# | 0 | 104 | 255 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 1 | 18 | 49 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 10 | 10 | 18 | 142 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 100 | 8 | 11 | 49 | 143 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# +# NOTE: The above table was obtained by running the following commands: +# +# redis-benchmark -n 1000000 incr foo +# redis-cli object freq foo +# +# NOTE 2: The counter initial value is 5 in order to give new objects a chance +# to accumulate hits. +# +# The counter decay time is the time, in minutes, that must elapse in order +# for the key counter to be divided by two (or decremented if it has a value +# less <= 10). +# +# The default value for the lfu-decay-time is 1. A special value of 0 means to +# decay the counter every time it happens to be scanned. +# +# lfu-log-factor 10 +# lfu-decay-time 1 + +########################### ACTIVE DEFRAGMENTATION ####################### +# +# What is active defragmentation? +# ------------------------------- +# +# Active (online) defragmentation allows a Redis server to compact the +# spaces left between small allocations and deallocations of data in memory, +# thus allowing to reclaim back memory. +# +# Fragmentation is a natural process that happens with every allocator (but +# less so with Jemalloc, fortunately) and certain workloads. Normally a server +# restart is needed in order to lower the fragmentation, or at least to flush +# away all the data and create it again. However thanks to this feature +# implemented by Oran Agra for Redis 4.0 this process can happen at runtime +# in a "hot" way, while the server is running. +# +# Basically when the fragmentation is over a certain level (see the +# configuration options below) Redis will start to create new copies of the +# values in contiguous memory regions by exploiting certain specific Jemalloc +# features (in order to understand if an allocation is causing fragmentation +# and to allocate it in a better place), and at the same time, will release the +# old copies of the data. This process, repeated incrementally for all the keys +# will cause the fragmentation to drop back to normal values. +# +# Important things to understand: +# +# 1. This feature is disabled by default, and only works if you compiled Redis +# to use the copy of Jemalloc we ship with the source code of Redis. +# This is the default with Linux builds. +# +# 2. You never need to enable this feature if you don't have fragmentation +# issues. +# +# 3. Once you experience fragmentation, you can enable this feature when +# needed with the command "CONFIG SET activedefrag yes". +# +# The configuration parameters are able to fine tune the behavior of the +# defragmentation process. If you are not sure about what they mean it is +# a good idea to leave the defaults untouched. + +# Active defragmentation is disabled by default +# activedefrag no + +# Minimum amount of fragmentation waste to start active defrag +# active-defrag-ignore-bytes 100mb + +# Minimum percentage of fragmentation to start active defrag +# active-defrag-threshold-lower 10 + +# Maximum percentage of fragmentation at which we use maximum effort +# active-defrag-threshold-upper 100 + +# Minimal effort for defrag in CPU percentage, to be used when the lower +# threshold is reached +# active-defrag-cycle-min 1 + +# Maximal effort for defrag in CPU percentage, to be used when the upper +# threshold is reached +# active-defrag-cycle-max 25 + +# Maximum number of set/hash/zset/list fields that will be processed from +# the main dictionary scan +# active-defrag-max-scan-fields 1000 + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes + +# It is possible to pin different threads and processes of Redis to specific +# CPUs in your system, in order to maximize the performances of the server. +# This is useful both in order to pin different Redis threads in different +# CPUs, but also in order to make sure that multiple Redis instances running +# in the same host will be pinned to different CPUs. +# +# Normally you can do this using the "taskset" command, however it is also +# possible to this via Redis configuration directly, both in Linux and FreeBSD. +# +# You can pin the server/IO threads, bio threads, aof rewrite child process, and +# the bgsave child process. The syntax to specify the cpu list is the same as +# the taskset command: +# +# Set redis server/io threads to cpu affinity 0,2,4,6: +# server_cpulist 0-7:2 +# +# Set bio threads to cpu affinity 1,3: +# bio_cpulist 1,3 +# +# Set aof rewrite child process to cpu affinity 8,9,10,11: +# aof_rewrite_cpulist 8-11 +# +# Set bgsave child process to cpu affinity 1,10,11 +# bgsave_cpulist 1,10-11 + +# In some cases redis will emit warnings and even refuse to start if it detects +# that the system is in bad state, it is possible to suppress these warnings +# by setting the following config which takes a space delimited list of warnings +# to suppress +# +# ignore-warnings ARM64-COW-BUG \ No newline at end of file diff --git a/modules/valkey/valkey.go b/modules/valkey/valkey.go new file mode 100644 index 0000000000..ac50d54797 --- /dev/null +++ b/modules/valkey/valkey.go @@ -0,0 +1,161 @@ +package valkey + +import ( + "context" + "fmt" + "strconv" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// ValkeyContainer represents the Valkey container type used in the module +type ValkeyContainer struct { + testcontainers.Container +} + +// valkeyServerProcess is the name of the valkey server process +const valkeyServerProcess = "valkey-server" + +type LogLevel string + +const ( + // LogLevelDebug is the debug log level + LogLevelDebug LogLevel = "debug" + // LogLevelVerbose is the verbose log level + LogLevelVerbose LogLevel = "verbose" + // LogLevelNotice is the notice log level + LogLevelNotice LogLevel = "notice" + // LogLevelWarning is the warning log level + LogLevelWarning LogLevel = "warning" +) + +// ConnectionString returns the connection string for the Valkey container +func (c *ValkeyContainer) ConnectionString(ctx context.Context) (string, error) { + mappedPort, err := c.MappedPort(ctx, "6379/tcp") + if err != nil { + return "", err + } + + hostIP, err := c.Host(ctx) + if err != nil { + return "", err + } + + uri := fmt.Sprintf("redis://%s:%s", hostIP, mappedPort.Port()) + return uri, nil +} + +// Deprecated: use Run instead +// RunContainer creates an instance of the Valkey container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*ValkeyContainer, error) { + return Run(ctx, "valkey/valkey:7.2.5", opts...) +} + +// Run creates an instance of the Valkey container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*ValkeyContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{"6379/tcp"}, + WaitingFor: wait.ForLog("* Ready to accept connections"), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ValkeyContainer + if container != nil { + c = &ValkeyContainer{Container: container} + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// WithConfigFile sets the config file to be used for the valkey container, and sets the command to run the valkey server +// using the passed config file +func WithConfigFile(configFile string) testcontainers.CustomizeRequestOption { + const defaultConfigFile = "/usr/local/valkey.conf" + + return func(req *testcontainers.GenericContainerRequest) error { + cf := testcontainers.ContainerFile{ + HostFilePath: configFile, + ContainerFilePath: defaultConfigFile, + FileMode: 0o755, + } + req.Files = append(req.Files, cf) + + if len(req.Cmd) == 0 { + req.Cmd = []string{valkeyServerProcess, defaultConfigFile} + return nil + } + + // prepend the command to run the redis server with the config file, which must be the first argument of the redis server process + if req.Cmd[0] == valkeyServerProcess { + // just insert the config file, then the rest of the args + req.Cmd = append([]string{valkeyServerProcess, defaultConfigFile}, req.Cmd[1:]...) + } else if req.Cmd[0] != valkeyServerProcess { + // prepend the redis server and the config file, then the rest of the args + req.Cmd = append([]string{valkeyServerProcess, defaultConfigFile}, req.Cmd...) + } + + return nil + } +} + +// WithLogLevel sets the log level for the valkey server process +// See https://redis.io/docs/reference/modules/modules-api-ref/#redismodule_log for more information. +func WithLogLevel(level LogLevel) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + processValkeyServerArgs(req, []string{"--loglevel", string(level)}) + + return nil + } +} + +// WithSnapshotting sets the snapshotting configuration for the valkey server process. You can configure Valkey to have it +// save the dataset every N seconds if there are at least M changes in the dataset. +// This method allows Valkey to benefit from copy-on-write semantics. +// See https://redis.io/docs/management/persistence/#snapshotting for more information. +func WithSnapshotting(seconds int, changedKeys int) testcontainers.CustomizeRequestOption { + if changedKeys < 1 { + changedKeys = 1 + } + if seconds < 1 { + seconds = 1 + } + + return func(req *testcontainers.GenericContainerRequest) error { + processValkeyServerArgs(req, []string{"--save", strconv.Itoa(seconds), strconv.Itoa(changedKeys)}) + return nil + } +} + +func processValkeyServerArgs(req *testcontainers.GenericContainerRequest, args []string) { + if len(req.Cmd) == 0 { + req.Cmd = append([]string{valkeyServerProcess}, args...) + return + } + + // prepend the command to run the valkey server with the config file + if req.Cmd[0] == valkeyServerProcess { + // valkey server is already set as the first argument, so just append the config file + req.Cmd = append(req.Cmd, args...) + } else if req.Cmd[0] != valkeyServerProcess { + // valkey server is not set as the first argument, so prepend it alongside the config file + req.Cmd = append([]string{valkeyServerProcess}, req.Cmd...) + req.Cmd = append(req.Cmd, args...) + } +} diff --git a/modules/valkey/valkey_test.go b/modules/valkey/valkey_test.go new file mode 100644 index 0000000000..44412afa4a --- /dev/null +++ b/modules/valkey/valkey_test.go @@ -0,0 +1,139 @@ +package valkey_test + +import ( + "context" + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/valkey-io/valkey-go" + + "github.com/testcontainers/testcontainers-go" + tcvalkey "github.com/testcontainers/testcontainers-go/modules/valkey" +) + +func TestIntegrationSetGet(t *testing.T) { + ctx := context.Background() + + valkeyContainer, err := tcvalkey.Run(ctx, "valkey/valkey:7.2.5") + testcontainers.CleanupContainer(t, valkeyContainer) + require.NoError(t, err) + + assertSetsGets(t, ctx, valkeyContainer, 1) +} + +func TestValkeyWithConfigFile(t *testing.T) { + ctx := context.Background() + + valkeyContainer, err := tcvalkey.Run(ctx, "valkey/valkey:7.2.5", tcvalkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf"))) + testcontainers.CleanupContainer(t, valkeyContainer) + require.NoError(t, err) + + assertSetsGets(t, ctx, valkeyContainer, 1) +} + +func TestValkeyWithImage(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + image string + }{ + // There is only one release of Valkey at the time of writing + { + name: "Valkey7.2.5", + image: "valkey/valkey:7.2.5", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + valkeyContainer, err := tcvalkey.Run(ctx, tt.image, tcvalkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf"))) + testcontainers.CleanupContainer(t, valkeyContainer) + require.NoError(t, err) + + assertSetsGets(t, ctx, valkeyContainer, 1) + }) + } +} + +func TestValkeyWithLogLevel(t *testing.T) { + ctx := context.Background() + + valkeyContainer, err := tcvalkey.Run(ctx, "valkey/valkey:7.2.5", tcvalkey.WithLogLevel(tcvalkey.LogLevelVerbose)) + testcontainers.CleanupContainer(t, valkeyContainer) + require.NoError(t, err) + + assertSetsGets(t, ctx, valkeyContainer, 10) +} + +func TestValkeyWithSnapshotting(t *testing.T) { + ctx := context.Background() + + valkeyContainer, err := tcvalkey.Run(ctx, "valkey/valkey:7.2.5", tcvalkey.WithSnapshotting(10, 1)) + testcontainers.CleanupContainer(t, valkeyContainer) + require.NoError(t, err) + + assertSetsGets(t, ctx, valkeyContainer, 10) +} + +func assertSetsGets(t *testing.T, ctx context.Context, valkeyContainer *tcvalkey.ValkeyContainer, keyCount int) { + t.Helper() + // connectionString { + uri, err := valkeyContainer.ConnectionString(ctx) + // } + require.NoError(t, err) + + // You will likely want to wrap your Valkey package of choice in an + // interface to aid in unit testing and limit lock-in throughout your + // codebase but that's out of scope for this example + options, err := valkey.ParseURL(uri) + require.NoError(t, err) + + client, err := valkey.NewClient(options) + require.NoError(t, err) + defer func(t *testing.T, ctx context.Context, client *valkey.Client) { + t.Helper() + require.NoError(t, flushValkey(ctx, *client)) + }(t, ctx, &client) + + t.Log("pinging valkey") + res := client.Do(ctx, client.B().Ping().Build()) + require.NoError(t, res.Error()) + + t.Log("received response from valkey") + + msg, err := res.ToString() + require.NoError(t, err) + + require.Equalf(t, "PONG", msg, "received unexpected response from valkey: %s", res.String()) + + for i := 0; i < keyCount; i++ { + // Set data + key := fmt.Sprintf("{user.%s}.favoritefood.%d", uuid.NewString(), i) + value := fmt.Sprintf("Cabbage Biscuits %d", i) + + ttl, _ := time.ParseDuration("2h") + + err = client.Do(ctx, client.B().Set().Key(key).Value(value).Exat(time.Now().Add(ttl)).Build()).Error() + require.NoError(t, err) + + err = client.Do(ctx, client.B().Expire().Key(key).Seconds(int64(ttl.Seconds())).Build()).Error() + require.NoError(t, err) + + // Get data + resp := client.Do(ctx, client.B().Get().Key(key).Build()) + require.NoError(t, resp.Error()) + + retVal, err := resp.ToString() + require.NoError(t, err) + require.Equal(t, retVal, value) + } +} + +func flushValkey(ctx context.Context, client valkey.Client) error { + return client.Do(ctx, client.B().Flushall().Build()).Error() +} diff --git a/modules/vault/examples_test.go b/modules/vault/examples_test.go index 364b07347b..0b3d257c06 100644 --- a/modules/vault/examples_test.go +++ b/modules/vault/examples_test.go @@ -5,30 +5,31 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/modules/vault" ) -func ExampleRunContainer() { +func ExampleRun() { // runVaultContainer { ctx := context.Background() - vaultContainer, err := vault.RunContainer(ctx) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + vaultContainer, err := vault.Run(ctx, "hashicorp/vault:1.13.0") defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -37,26 +38,26 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_withToken() { +func ExampleRun_withToken() { // runVaultContainerWithToken { ctx := context.Background() - vaultContainer, err := vault.RunContainer(ctx, vault.WithToken("MyToKeN")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + vaultContainer, err := vault.Run(ctx, "hashicorp/vault:1.13.0", vault.WithToken("MyToKeN")) defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -66,7 +67,8 @@ func ExampleRunContainer_withToken() { } exitCode, _, err := vaultContainer.Exec(ctx, cmds, exec.Multiplexed()) if err != nil { - log.Fatalf("failed to execute command: %s", err) + log.Printf("failed to execute command: %s", err) + return } fmt.Println(exitCode) @@ -76,32 +78,32 @@ func ExampleRunContainer_withToken() { // 0 } -func ExampleRunContainer_withInitCommand() { +func ExampleRun_withInitCommand() { // runVaultContainerWithInitCommand { ctx := context.Background() - vaultContainer, err := vault.RunContainer(ctx, vault.WithToken("MyToKeN"), vault.WithInitCommand( + vaultContainer, err := vault.Run(ctx, "hashicorp/vault:1.13.0", vault.WithToken("MyToKeN"), vault.WithInitCommand( "auth enable approle", // Enable the approle auth method "secrets disable secret", // Disable the default secret engine "secrets enable -version=1 -path=secret kv", // Enable the kv secret engine at version 1 "write --force auth/approle/role/myrole", // Create a role "write secret/testing top_secret=password123", // Create a secret )) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/vault/go.mod b/modules/vault/go.mod index acdebbe88b..3109854195 100644 --- a/modules/vault/go.mod +++ b/modules/vault/go.mod @@ -1,26 +1,26 @@ module github.com/testcontainers/testcontainers-go/modules/vault -go 1.21 +go 1.22 require ( - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/hashicorp/vault-client-go v0.4.3 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/testcontainers/testcontainers-go v0.34.0 github.com/tidwall/gjson v1.17.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // 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/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -28,17 +28,17 @@ require ( 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/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -62,14 +62,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/vault/go.sum b/modules/vault/go.sum index 41e7256f86..09d227fcfe 100644 --- a/modules/vault/go.sum +++ b/modules/vault/go.sum @@ -4,32 +4,34 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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= @@ -41,8 +43,6 @@ 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -53,10 +53,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= @@ -65,8 +65,8 @@ github.com/hashicorp/vault-client-go v0.4.3 h1:zG7STGVgn/VK6rnZc0k8PGbfv2x/sJExR github.com/hashicorp/vault-client-go v0.4.3/go.mod h1:4tDw7Uhq5XOxS1fO+oMtotHL7j4sB9cp0T7U6m4FzDY= 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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= @@ -75,8 +75,14 @@ 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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -113,7 +119,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -153,23 +160,19 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -180,33 +183,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/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/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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 v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -215,5 +215,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/vault/vault.go b/modules/vault/vault.go index 5c6f47aa6e..b679d45c21 100644 --- a/modules/vault/vault.go +++ b/modules/vault/vault.go @@ -12,8 +12,7 @@ import ( ) const ( - defaultPort = "8200" - defaultImageName = "hashicorp/vault:1.13.0" + defaultPort = "8200" ) // VaultContainer represents the vault container type used in the module @@ -21,10 +20,16 @@ type VaultContainer struct { testcontainers.Container } -// RunContainer creates an instance of the vault container type +// Deprecated: use Run instead +// RunContainer creates an instance of the Vault container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*VaultContainer, error) { + return Run(ctx, "hashicorp/vault:1.13.0", opts...) +} + +// Run creates an instance of the Vault container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*VaultContainer, error) { req := testcontainers.ContainerRequest{ - Image: defaultImageName, + Image: img, ExposedPorts: []string{defaultPort + "/tcp"}, HostConfigModifier: func(hc *container.HostConfig) { hc.CapAdd = []string{"IPC_LOCK"} @@ -47,11 +52,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *VaultContainer + if container != nil { + c = &VaultContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &VaultContainer{container}, nil + return c, nil } // WithToken is a container option function that sets the root token for the Vault diff --git a/modules/vault/vault_test.go b/modules/vault/vault_test.go index d068827fdc..22b87930bb 100644 --- a/modules/vault/vault_test.go +++ b/modules/vault/vault_test.go @@ -3,7 +3,6 @@ package vault_test import ( "context" "io" - "log" "net/http" "testing" "time" @@ -25,9 +24,6 @@ const ( func TestVault(t *testing.T) { ctx := context.Background() opts := []testcontainers.ContainerCustomizer{ - // WithImageName { - testcontainers.WithImage("hashicorp/vault:1.13.0"), - // } // WithToken { testcontainervault.WithToken(token), // } @@ -37,7 +33,8 @@ func TestVault(t *testing.T) { // } } - vaultContainer, err := testcontainervault.RunContainer(ctx, opts...) + vaultContainer, err := testcontainervault.Run(ctx, "hashicorp/vault:1.13.0", opts...) + testcontainers.CleanupContainer(t, vaultContainer) require.NoError(t, err) // httpHostAddress { @@ -53,7 +50,7 @@ func TestVault(t *testing.T) { exec, reader, err := vaultContainer.Exec(ctx, []string{"vault", "kv", "get", "-format=json", "secret/test1"}) // } require.NoError(t, err) - assert.Equal(t, 0, exec) + require.Zero(t, exec) bytes, err := io.ReadAll(reader) require.NoError(t, err) @@ -121,11 +118,4 @@ func TestVault(t *testing.T) { assert.Equal(t, "bar", s.Data.Data["foo"]) }) }) - - t.Cleanup(func() { - // Clean up the vault after the test is complete - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate vault: %s", err) - } - }) } diff --git a/modules/vearch/Makefile b/modules/vearch/Makefile new file mode 100644 index 0000000000..4d420ffba5 --- /dev/null +++ b/modules/vearch/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-vearch diff --git a/modules/vearch/config.toml b/modules/vearch/config.toml new file mode 100644 index 0000000000..95890f2df0 --- /dev/null +++ b/modules/vearch/config.toml @@ -0,0 +1,85 @@ +[global] + # the name will validate join cluster by same name + name = "vearch" + # specify which resources to use to create space + resource_name = "default" + # you data save to disk path ,If you are in a production environment, You'd better set absolute paths + data = ["datas/","datas1/"] + # log path , If you are in a production environment, You'd better set absolute paths + log = "logs/" + # default log type for any model + level = "debug" + # master <-> ps <-> router will use this key to send or receive data + signkey = "secret" + # skip auth for master and router + skip_auth = true + # tell Vearch whether it should manage it's own instance of etcd or not + self_manage_etcd = false + # automatically remove the failed node and recover when new nodes join + auto_recover_ps = false + # support access etcd basic auth,depend on self_manage_etcd = true + support_etcd_auth = false + # ensure leader-follow raft data synchronization is consistent + raft_consistent = false + +# self_manage_etcd = true,means manage etcd by yourself,need provide additional configuration +[etcd] + # etcd server ip or domain + address = ["127.0.0.1"] + # advertise_client_urls AND listen_client_urls + etcd_client_port = 2379 + # provider username and password,if you turn on auth + user_name = "root" + password = "" + +# if you are master you'd better set all config for router and ps and router and ps use default config it so cool +[[masters]] + # name machine name for cluster + name = "m1" + # ip or domain + address = "127.0.0.1" + # api port for http server + api_port = 8817 + # port for etcd server + etcd_port = 2378 + # listen_peer_urls List of comma separated URLs to listen on for peer traffic. + # advertise_peer_urls List of this member's peer URLs to advertise to the rest of the cluster. The URLs needed to be a comma-separated list. + etcd_peer_port = 2390 + # List of this member's client URLs to advertise to the public. + # The URLs needed to be a comma-separated list. + # advertise_client_urls AND listen_client_urls + etcd_client_port = 2370 + # init cluster state + cluster_state = "new" + pprof_port = 6062 + # monitor + monitor_port = 8818 + +[router] + # port for server + port = 9001 + # rpc_port = 9002 + pprof_port = 6061 + plugin_path = "plugin" + allow_origins = ["http://google.com"] + +[ps] + # port for server + rpc_port = 8081 + ps_heartbeat_timeout = 5 # seconds + # raft config begin + raft_heartbeat_port = 8898 + raft_replicate_port = 8899 + heartbeat-interval = 200 # ms + raft_retain_logs = 20000000 + raft_replica_concurrency = 1 + raft_snap_concurrency = 1 + raft_truncate_count = 500000 + # when behind leader this value, will stop the server for search + raft_diff_count = 10000 + pprof_port = 6060 + # if set true, this ps only use in db meta config + private = false + # seconds + flush_time_interval = 600 + flush_count_threshold = 200000 diff --git a/modules/vearch/examples_test.go b/modules/vearch/examples_test.go new file mode 100644 index 0000000000..97ef8d8fe4 --- /dev/null +++ b/modules/vearch/examples_test.go @@ -0,0 +1,38 @@ +package vearch_test + +import ( + "context" + "fmt" + "log" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/vearch" +) + +func ExampleRun() { + // runVearchContainer { + ctx := context.Background() + + vearchContainer, err := vearch.Run(ctx, "vearch/vearch:3.5.1") + defer func() { + if err := testcontainers.TerminateContainer(vearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + state, err := vearchContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} diff --git a/modules/vearch/go.mod b/modules/vearch/go.mod new file mode 100644 index 0000000000..596be1a7f8 --- /dev/null +++ b/modules/vearch/go.mod @@ -0,0 +1,63 @@ +module github.com/testcontainers/testcontainers-go/modules/vearch + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 +) + +require ( + dario.cat/mergo v1.0.0 // 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/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/vearch/go.sum b/modules/vearch/go.sum new file mode 100644 index 0000000000..447eec4038 --- /dev/null +++ b/modules/vearch/go.sum @@ -0,0 +1,190 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/vearch/vearch.go b/modules/vearch/vearch.go new file mode 100644 index 0000000000..a7bcb69083 --- /dev/null +++ b/modules/vearch/vearch.go @@ -0,0 +1,81 @@ +package vearch + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +// VearchContainer represents the Vearch container type used in the module +type VearchContainer struct { + testcontainers.Container +} + +// Deprecated: use Run instead +// RunContainer creates an instance of the Vearch container type +func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*VearchContainer, error) { + return Run(ctx, "vearch/vearch:3.5.1", opts...) +} + +// Run creates an instance of the Vearch container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*VearchContainer, error) { + req := testcontainers.ContainerRequest{ + Image: img, + ExposedPorts: []string{"8817/tcp", "9001/tcp"}, + Cmd: []string{"-conf=/vearch/config.toml", "all"}, + Privileged: true, + Files: []testcontainers.ContainerFile{ + { + HostFilePath: "config.toml", + ContainerFilePath: "/vearch/config.toml", + FileMode: 0o666, + }, + }, + WaitingFor: wait.ForAll( + wait.ForListeningPort("8817/tcp").WithStartupTimeout(5*time.Second), + wait.ForListeningPort("9001/tcp").WithStartupTimeout(5*time.Second), + ), + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, err + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *VearchContainer + if container != nil { + c = &VearchContainer{Container: container} + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// RESTEndpoint returns the REST endpoint of the Vearch container +func (c *VearchContainer) RESTEndpoint(ctx context.Context) (string, error) { + containerPort, err := c.MappedPort(ctx, "8817/tcp") + if err != nil { + return "", fmt.Errorf("failed to get container port: %w", err) + } + + host, err := c.Host(ctx) + if err != nil { + return "", errors.New("failed to get container host") + } + + return fmt.Sprintf("http://%s:%s", host, containerPort.Port()), nil +} diff --git a/modules/vearch/vearch_test.go b/modules/vearch/vearch_test.go new file mode 100644 index 0000000000..43c0f7e1f5 --- /dev/null +++ b/modules/vearch/vearch_test.go @@ -0,0 +1,34 @@ +package vearch_test + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/vearch" +) + +func TestVearch(t *testing.T) { + ctx := context.Background() + + ctr, err := vearch.Run(ctx, "vearch/vearch:3.5.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + t.Run("REST Endpoint", func(tt *testing.T) { + // restEndpoint { + restEndpoint, err := ctr.RESTEndpoint(ctx) + // } + require.NoError(t, err) + + cli := &http.Client{} + resp, err := cli.Get(restEndpoint) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + }) +} diff --git a/modules/weaviate/examples_test.go b/modules/weaviate/examples_test.go index c44e0dc851..443f782413 100644 --- a/modules/weaviate/examples_test.go +++ b/modules/weaviate/examples_test.go @@ -15,26 +15,26 @@ import ( tcweaviate "github.com/testcontainers/testcontainers-go/modules/weaviate" ) -func ExampleRunContainer() { +func ExampleRun() { // runWeaviateContainer { ctx := context.Background() - weaviateContainer, err := tcweaviate.RunContainer(ctx, testcontainers.WithImage("semitechnologies/weaviate:1.24.5")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.24.5") defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := weaviateContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -43,35 +43,37 @@ func ExampleRunContainer() { // true } -func ExampleRunContainer_connectWithClient() { +func ExampleRun_connectWithClient() { // createClientNoModules { ctx := context.Background() - weaviateContainer, err := tcweaviate.RunContainer(ctx, testcontainers.WithImage("semitechnologies/weaviate:1.23.9")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - + weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.23.9") defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } scheme, host, err := weaviateContainer.HttpHostAddress(ctx) if err != nil { - log.Fatalf("failed to get http schema and host: %s", err) // nolint:gocritic + log.Printf("failed to get http schema and host: %s", err) + return } grpcHost, err := weaviateContainer.GrpcHostAddress(ctx) if err != nil { - log.Fatalf("failed to get gRPC host: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC host: %s", err) + return } connectionClient := &http.Client{} headers := map[string]string{ // put here the custom API key, e.g. for OpenAPI - "Authorization": fmt.Sprintf("Bearer %s", "custom-api-key"), + "Authorization": "Bearer custom-api-key", } cli := weaviate.New(weaviate.Config{ @@ -94,7 +96,7 @@ func ExampleRunContainer_connectWithClient() { // } -func ExampleRunContainer_connectWithClientWithModules() { +func ExampleRun_connectWithClientWithModules() { // createClientAndModules { ctx := context.Background() @@ -111,35 +113,36 @@ func ExampleRunContainer_connectWithClientWithModules() { } opts := []testcontainers.ContainerCustomizer{ - testcontainers.WithImage("semitechnologies/weaviate:1.24.5"), testcontainers.WithEnv(envs), } - weaviateContainer, err := tcweaviate.RunContainer(ctx, opts...) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - + weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.25.5", opts...) defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } scheme, host, err := weaviateContainer.HttpHostAddress(ctx) if err != nil { - log.Fatalf("failed to get http schema and host: %s", err) // nolint:gocritic + log.Printf("failed to get http schema and host: %s", err) + return } grpcHost, err := weaviateContainer.GrpcHostAddress(ctx) if err != nil { - log.Fatalf("failed to get gRPC host: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC host: %s", err) + return } connectionClient := &http.Client{} headers := map[string]string{ // put here the custom API key, e.g. for OpenAPI - "Authorization": fmt.Sprintf("Bearer %s", "custom-api-key"), + "Authorization": "Bearer custom-api-key", } cli := weaviate.New(weaviate.Config{ diff --git a/modules/weaviate/go.mod b/modules/weaviate/go.mod index d571f6c66c..e99308f1f5 100644 --- a/modules/weaviate/go.mod +++ b/modules/weaviate/go.mod @@ -1,27 +1,29 @@ module github.com/testcontainers/testcontainers-go/modules/weaviate -go 1.21 +go 1.22 require ( - github.com/testcontainers/testcontainers-go v0.30.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 github.com/weaviate/weaviate-go-client/v4 v4.13.1 - google.golang.org/grpc v1.62.1 + google.golang.org/grpc v1.64.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -41,11 +43,12 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect 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 @@ -55,6 +58,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // 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/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -68,15 +72,13 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/weaviate/go.sum b/modules/weaviate/go.sum index 1536aaeae7..da152d0a52 100644 --- a/modules/weaviate/go.sum +++ b/modules/weaviate/go.sum @@ -5,10 +5,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -18,22 +16,24 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 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/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -126,8 +126,8 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh 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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -152,6 +152,8 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -201,6 +203,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -260,13 +264,11 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= @@ -276,10 +278,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -287,8 +289,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -310,22 +310,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -335,21 +335,19 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn 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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= @@ -370,5 +368,5 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/weaviate/weaviate.go b/modules/weaviate/weaviate.go index 3782e148de..e773174d57 100644 --- a/modules/weaviate/weaviate.go +++ b/modules/weaviate/weaviate.go @@ -2,6 +2,7 @@ package weaviate import ( "context" + "errors" "fmt" "time" @@ -10,7 +11,6 @@ import ( ) const ( - image = "semitechnologies/weaviate:1.24.6" httpPort = "8080/tcp" grpcPort = "50051/tcp" ) @@ -20,10 +20,16 @@ type WeaviateContainer struct { testcontainers.Container } +// Deprecated: use Run instead // RunContainer creates an instance of the Weaviate container type func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*WeaviateContainer, error) { + return Run(ctx, "semitechnologies/weaviate:1.25.5", opts...) +} + +// Run creates an instance of the Weaviate container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*WeaviateContainer, error) { req := testcontainers.ContainerRequest{ - Image: image, + Image: img, Cmd: []string{"--host", "0.0.0.0", "--scheme", "http", "--port", "8080"}, ExposedPorts: []string{httpPort, grpcPort}, Env: map[string]string{ @@ -33,6 +39,7 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize WaitingFor: wait.ForAll( wait.ForListeningPort(httpPort).WithStartupTimeout(5*time.Second), wait.ForListeningPort(grpcPort).WithStartupTimeout(5*time.Second), + wait.ForHTTP("/v1/.well-known/ready").WithPort(httpPort), ), } @@ -48,11 +55,16 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *WeaviateContainer + if container != nil { + c = &WeaviateContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &WeaviateContainer{Container: container}, nil + return c, nil } // HttpHostAddress returns the schema and host of the Weaviate container. @@ -65,7 +77,7 @@ func (c *WeaviateContainer) HttpHostAddress(ctx context.Context) (string, string host, err := c.Host(ctx) if err != nil { - return "", "", fmt.Errorf("failed to get container host") + return "", "", errors.New("failed to get container host") } return "http", fmt.Sprintf("%s:%s", host, port.Port()), nil @@ -81,7 +93,7 @@ func (c *WeaviateContainer) GrpcHostAddress(ctx context.Context) (string, error) host, err := c.Host(ctx) if err != nil { - return "", fmt.Errorf("failed to get container host") + return "", errors.New("failed to get container host") } return fmt.Sprintf("%s:%s", host, port.Port()), nil diff --git a/modules/weaviate/weaviate_test.go b/modules/weaviate/weaviate_test.go index dc12f43528..c85a25726c 100644 --- a/modules/weaviate/weaviate_test.go +++ b/modules/weaviate/weaviate_test.go @@ -6,97 +6,67 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + wvt "github.com/weaviate/weaviate-go-client/v4/weaviate" + wvtgrpc "github.com/weaviate/weaviate-go-client/v4/weaviate/grpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/health/grpc_health_v1" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/weaviate" - wvt "github.com/weaviate/weaviate-go-client/v4/weaviate" - wvtgrpc "github.com/weaviate/weaviate-go-client/v4/weaviate/grpc" ) func TestWeaviate(t *testing.T) { ctx := context.Background() - container, err := weaviate.RunContainer(ctx, testcontainers.WithImage("semitechnologies/weaviate:1.24.5")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := weaviate.Run(ctx, "semitechnologies/weaviate:1.25.5") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("HttpHostAddress", func(tt *testing.T) { // httpHostAddress { - schema, host, err := container.HttpHostAddress(ctx) + schema, host, err := ctr.HttpHostAddress(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(fmt.Sprintf("%s://%s", schema, host)) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("GrpcHostAddress", func(tt *testing.T) { // gRPCHostAddress { - host, err := container.GrpcHostAddress(ctx) + host, err := ctr.GrpcHostAddress(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var opts []grpc.DialOption - opts = append(opts, grpc.WithBlock()) + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - conn, err := grpc.Dial(host, opts...) - if err != nil { - tt.Fatalf("failed to dial connection: %v", err) - } + conn, err := grpc.NewClient(host, opts...) + require.NoErrorf(t, err, "failed to dial connection") client := grpc_health_v1.NewHealthClient(conn) check, err := client.Check(context.TODO(), &grpc_health_v1.HealthCheckRequest{}) - if err != nil { - tt.Fatalf("failed to get a health check: %v", err) - } - if grpc_health_v1.HealthCheckResponse_SERVING.Enum().Number() != check.Status.Number() { - tt.Fatalf("unexpected status code: %d", check.Status.Number()) - } + require.NoErrorf(t, err, "failed to get a health check") + require.Equalf(t, grpc_health_v1.HealthCheckResponse_SERVING.Enum().Number(), check.Status.Number(), "unexpected status code: %d", check.Status.Number()) }) t.Run("Weaviate client", func(tt *testing.T) { - httpScheme, httpHost, err := container.HttpHostAddress(ctx) - if err != nil { - tt.Fatal(err) - } - grpcHost, err := container.GrpcHostAddress(ctx) - if err != nil { - tt.Fatal(err) - } + httpScheme, httpHost, err := ctr.HttpHostAddress(ctx) + require.NoError(tt, err) + grpcHost, err := ctr.GrpcHostAddress(ctx) + require.NoError(tt, err) config := wvt.Config{Scheme: httpScheme, Host: httpHost, GrpcConfig: &wvtgrpc.Config{Host: grpcHost}} client, err := wvt.NewClient(config) - if err != nil { - tt.Fatal(err) - } + require.NoError(tt, err) meta, err := client.Misc().MetaGetter().Do(ctx) - if err != nil { - tt.Fatal(err) - } + require.NoError(tt, err) - if meta == nil || meta.Version == "" { - tt.Fatal("failed to get /v1/meta response") - } + require.NotNilf(tt, meta, "failed to get /v1/meta response") + require.NotEmptyf(tt, meta.Version, "failed to get /v1/meta response") }) } diff --git a/modules/yugabytedb/Makefile b/modules/yugabytedb/Makefile new file mode 100644 index 0000000000..a56dee99f2 --- /dev/null +++ b/modules/yugabytedb/Makefile @@ -0,0 +1,5 @@ +include ../../commons-test.mk + +.PHONY: test +test: + $(MAKE) test-yugabytedb diff --git a/modules/yugabytedb/examples_test.go b/modules/yugabytedb/examples_test.go new file mode 100644 index 0000000000..641fc5a53f --- /dev/null +++ b/modules/yugabytedb/examples_test.go @@ -0,0 +1,155 @@ +package yugabytedb_test + +import ( + "context" + "database/sql" + "fmt" + "log" + "net" + + _ "github.com/lib/pq" + "github.com/yugabyte/gocql" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/yugabytedb" +) + +func ExampleRun() { + // runyugabyteDBContainer { + ctx := context.Background() + + yugabytedbContainer, err := yugabytedb.Run( + ctx, + "yugabytedb/yugabyte:2024.1.3.0-b105", + yugabytedb.WithKeyspace("custom-keyspace"), + yugabytedb.WithUser("custom-user"), + yugabytedb.WithDatabaseName("custom-db"), + yugabytedb.WithDatabaseUser("custom-user"), + yugabytedb.WithDatabasePassword("custom-password"), + ) + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + defer func() { + if err := testcontainers.TerminateContainer(yugabytedbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + // } + + state, err := yugabytedbContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: true +} + +func ExampleContainer_YSQLConnectionString() { + ctx := context.Background() + + yugabytedbContainer, err := yugabytedb.Run( + ctx, + "yugabytedb/yugabyte:2024.1.3.0-b105", + ) + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + defer func() { + if err := testcontainers.TerminateContainer(yugabytedbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + + connStr, err := yugabytedbContainer.YSQLConnectionString(ctx, "sslmode=disable") + if err != nil { + log.Printf("failed to get connection string: %s", err) + return + } + + db, err := sql.Open("postgres", connStr) + if err != nil { + log.Printf("failed to open connection: %s", err) + return + } + + defer db.Close() + + var i int + row := db.QueryRowContext(ctx, "SELECT 1") + if err := row.Scan(&i); err != nil { + log.Printf("failed to scan row: %s", err) + return + } + + fmt.Println(i) + + // Output: 1 +} + +func ExampleContainer_newCluster() { + ctx := context.Background() + + yugabytedbContainer, err := yugabytedb.Run( + ctx, + "yugabytedb/yugabyte:2024.1.3.0-b105", + ) + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + defer func() { + if err := testcontainers.TerminateContainer(yugabytedbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + + yugabytedbContainerHost, err := yugabytedbContainer.Host(ctx) + if err != nil { + log.Printf("failed to get container host: %s", err) + return + } + + yugabyteContainerPort, err := yugabytedbContainer.MappedPort(ctx, "9042/tcp") + if err != nil { + log.Printf("failed to get container port: %s", err) + return + } + + cluster := gocql.NewCluster(net.JoinHostPort(yugabytedbContainerHost, yugabyteContainerPort.Port())) + cluster.Keyspace = "yugabyte" + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: "yugabyte", + Password: "yugabyte", + } + + session, err := cluster.CreateSession() + if err != nil { + log.Printf("failed to create session: %s", err) + return + } + + defer session.Close() + + var i int + if err := session.Query(` + SELECT COUNT(*) + FROM system_schema.keyspaces + WHERE keyspace_name = 'yugabyte' + `).Scan(&i); err != nil { + log.Printf("failed to scan row: %s", err) + return + } + + fmt.Println(i) + + // Output: 1 +} diff --git a/modules/yugabytedb/go.mod b/modules/yugabytedb/go.mod new file mode 100644 index 0000000000..57d7f8b64b --- /dev/null +++ b/modules/yugabytedb/go.mod @@ -0,0 +1,65 @@ +module github.com/testcontainers/testcontainers-go/modules/yugabytedb + +go 1.22 + +require ( + github.com/lib/pq v1.10.9 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.34.0 + github.com/yugabyte/gocql v1.6.0-yb-1 +) + +require ( + dario.cat/mergo v1.0.0 // 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/containerd/containerd v1.7.18 // 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/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // 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/golang/snappy v0.0.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect + github.com/klauspost/compress v1.17.4 // indirect + 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/moby/docker-image-spec v1.3.1 // indirect + 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 + 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.0 // 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/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.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.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/yugabytedb/go.sum b/modules/yugabytedb/go.sum new file mode 100644 index 0000000000..4fa4e1cea6 --- /dev/null +++ b/modules/yugabytedb/go.sum @@ -0,0 +1,209 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +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/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +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/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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/yugabyte/gocql v1.6.0-yb-1 h1:3anNiHsJwKQ8Dn7RdmkTEuIzV1l7e9QJZ8wkOZ87ELg= +github.com/yugabyte/gocql v1.6.0-yb-1/go.mod h1:LAokR6+vevDCrTxk52U7p6ki+4qELu4XU7JUGYa2O2M= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +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/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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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 v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/modules/yugabytedb/options.go b/modules/yugabytedb/options.go new file mode 100644 index 0000000000..485b979468 --- /dev/null +++ b/modules/yugabytedb/options.go @@ -0,0 +1,53 @@ +package yugabytedb + +import ( + "github.com/testcontainers/testcontainers-go" +) + +// WithDatabaseName sets the initial database name for the yugabyteDB container. +func WithDatabaseName(dbName string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ysqlDatabaseNameEnv] = dbName + return nil + } +} + +// WithDatabaseUser sets the initial database user for the yugabyteDB container. +func WithDatabaseUser(dbUser string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ysqlDatabaseUserEnv] = dbUser + return nil + } +} + +// WithDatabasePassword sets the initial database password for the yugabyteDB container. +func WithDatabasePassword(dbPassword string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ysqlDatabasePasswordEnv] = dbPassword + return nil + } +} + +// WithKeyspace sets the initial keyspace for the yugabyteDB container. +func WithKeyspace(keyspace string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ycqlKeyspaceEnv] = keyspace + return nil + } +} + +// WithUser sets the initial user for the yugabyteDB container. +func WithUser(user string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ycqlUserNameEnv] = user + return nil + } +} + +// WithPassword sets the initial password for the yugabyteDB container. +func WithPassword(password string) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + req.Env[ycqlPasswordEnv] = password + return nil + } +} diff --git a/modules/yugabytedb/yugabytedb.go b/modules/yugabytedb/yugabytedb.go new file mode 100644 index 0000000000..13d6e9ccb0 --- /dev/null +++ b/modules/yugabytedb/yugabytedb.go @@ -0,0 +1,126 @@ +package yugabytedb + +import ( + "context" + "fmt" + "net" + "strings" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + ycqlPort = "9042/tcp" + + ycqlKeyspaceEnv = "YCQL_KEYSPACE" + ycqlUserNameEnv = "YCQL_USER" + ycqlPasswordEnv = "YCQL_PASSWORD" + + ycqlKeyspace = "yugabyte" + ycqlUserName = "yugabyte" + ycqlPassword = "yugabyte" +) + +const ( + ysqlPort = "5433/tcp" + + ysqlDatabaseNameEnv = "YSQL_DB" + ysqlDatabaseUserEnv = "YSQL_USER" + ysqlDatabasePasswordEnv = "YSQL_PASSWORD" + + ysqlDatabaseName = "yugabyte" + ysqlDatabaseUser = "yugabyte" + ysqlDatabasePassword = "yugabyte" +) + +// Container represents the yugabyteDB container type used in the module +type Container struct { + testcontainers.Container + + ysqlDatabaseName string + ysqlDatabaseUser string + ysqlDatabasePassword string +} + +// Run creates an instance of the yugabyteDB container type and automatically starts it. +// A default configuration is used for the container, but it can be customized using the +// provided options. +// When using default configuration values it is recommended to use the provided +// [*Container.YSQLConnectionString] and [*Container.YCQLConfigureClusterConfig] +// methods to use the container in their respective clients. +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { + req := testcontainers.ContainerRequest{ + Image: img, + Cmd: []string{"bin/yugabyted", "start", "--background=false"}, + WaitingFor: wait.ForAll( + wait.ForLog("YugabyteDB Started").WithOccurrence(1), + wait.ForLog("Data placement constraint successfully verified").WithOccurrence(1), + wait.ForListeningPort(ysqlPort), + wait.ForListeningPort(ycqlPort), + ), + ExposedPorts: []string{ycqlPort, ysqlPort}, + Env: map[string]string{ + ycqlKeyspaceEnv: ycqlKeyspace, + ycqlUserNameEnv: ycqlUserName, + ycqlPasswordEnv: ycqlPassword, + ysqlDatabaseNameEnv: ysqlDatabaseName, + ysqlDatabaseUserEnv: ysqlDatabaseUser, + ysqlDatabasePasswordEnv: ysqlDatabasePassword, + }, + } + + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + + for _, opt := range opts { + if err := opt.Customize(&genericContainerReq); err != nil { + return nil, fmt.Errorf("customize: %w", err) + } + } + + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Container + if container != nil { + c = &Container{ + Container: container, + ysqlDatabaseName: req.Env[ysqlDatabaseNameEnv], + ysqlDatabaseUser: req.Env[ysqlDatabaseUserEnv], + ysqlDatabasePassword: req.Env[ysqlDatabasePasswordEnv], + } + } + + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// YSQLConnectionString returns a connection string for the yugabyteDB container +// using the configured database name, user, password, port, host and additional +// arguments. +// Additional arguments are appended to the connection string as query parameters +// in the form of key=value pairs separated by "&". +func (y *Container) YSQLConnectionString(ctx context.Context, args ...string) (string, error) { + host, err := y.Host(ctx) + if err != nil { + return "", fmt.Errorf("host: %w", err) + } + + mappedPort, err := y.MappedPort(ctx, ysqlPort) + if err != nil { + return "", fmt.Errorf("mapped port: %w", err) + } + + return fmt.Sprintf( + "postgres://%s:%s@%s/%s?%s", + y.ysqlDatabaseUser, + y.ysqlDatabasePassword, + net.JoinHostPort(host, mappedPort.Port()), + y.ysqlDatabaseName, + strings.Join(args, "&"), + ), nil +} diff --git a/modules/yugabytedb/yugabytedb_test.go b/modules/yugabytedb/yugabytedb_test.go new file mode 100644 index 0000000000..38a93f0c89 --- /dev/null +++ b/modules/yugabytedb/yugabytedb_test.go @@ -0,0 +1,129 @@ +package yugabytedb_test + +import ( + "context" + "database/sql" + "fmt" + "net" + "testing" + + _ "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yugabyte/gocql" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/yugabytedb" +) + +func TestYugabyteDB_YSQL(t *testing.T) { + t.Run("Run", func(t *testing.T) { + ctx := context.Background() + + ctr, err := yugabytedb.Run(ctx, "yugabytedb/yugabyte:2024.1.3.0-b105") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + ctrHost, err := ctr.Host(ctx) + require.NoError(t, err) + + ctrPort, err := ctr.MappedPort(ctx, "5433/tcp") + require.NoError(t, err) + + ysqlConnStr, err := ctr.YSQLConnectionString(ctx, "sslmode=disable") + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("postgres://yugabyte:yugabyte@%s:%s/yugabyte?sslmode=disable", ctrHost, ctrPort.Port()), ysqlConnStr) + + db, err := sql.Open("postgres", ysqlConnStr) + require.NoError(t, err) + require.NotNil(t, db) + + err = db.Ping() + require.NoError(t, err) + }) + + t.Run("custom-options", func(t *testing.T) { + ctx := context.Background() + ctr, err := yugabytedb.Run(ctx, "yugabytedb/yugabyte:2024.1.3.0-b105", + yugabytedb.WithDatabaseName("custom-db"), + yugabytedb.WithDatabaseUser("custom-user"), + yugabytedb.WithDatabasePassword("custom-password"), + ) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + ctrHost, err := ctr.Host(ctx) + require.NoError(t, err) + + ctrPort, err := ctr.MappedPort(ctx, "5433/tcp") + require.NoError(t, err) + + ysqlConnStr, err := ctr.YSQLConnectionString(ctx, "sslmode=disable") + require.NoError(t, err) + assert.Equal(t, fmt.Sprintf("postgres://custom-user:custom-password@%s:%s/custom-db?sslmode=disable", ctrHost, ctrPort.Port()), ysqlConnStr) + + db, err := sql.Open("postgres", ysqlConnStr) + require.NoError(t, err) + require.NotNil(t, db) + + err = db.Ping() + require.NoError(t, err) + }) +} + +func TestYugabyteDB_YCQL(t *testing.T) { + t.Run("Run", func(t *testing.T) { + ctx := context.Background() + + ctr, err := yugabytedb.Run(ctx, "yugabytedb/yugabyte:2024.1.3.0-b105") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + ctrHost, err := ctr.Host(ctx) + require.NoError(t, err) + + ctrPort, err := ctr.MappedPort(ctx, "9042/tcp") + require.NoError(t, err) + + cluster := gocql.NewCluster(net.JoinHostPort(ctrHost, ctrPort.Port())) + cluster.Keyspace = "yugabyte" + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: "yugabyte", + Password: "yugabyte", + } + + session, err := cluster.CreateSession() + require.NoError(t, err) + session.Close() + }) + + t.Run("custom-options", func(t *testing.T) { + ctx := context.Background() + + ctr, err := yugabytedb.Run(ctx, "yugabytedb/yugabyte:2024.1.3.0-b105", + yugabytedb.WithKeyspace("custom-keyspace"), + yugabytedb.WithUser("custom-user"), + yugabytedb.WithPassword("custom-password"), + ) + + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + ctrHost, err := ctr.Host(ctx) + require.NoError(t, err) + + ctrPort, err := ctr.MappedPort(ctx, "9042/tcp") + require.NoError(t, err) + + cluster := gocql.NewCluster(net.JoinHostPort(ctrHost, ctrPort.Port())) + cluster.Keyspace = "custom-keyspace" + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: "custom-user", + Password: "custom-password", + } + + session, err := cluster.CreateSession() + require.NoError(t, err) + session.Close() + }) +} diff --git a/mounts_test.go b/mounts_test.go index ecf91943dd..b1ac51d305 100644 --- a/mounts_test.go +++ b/mounts_test.go @@ -171,13 +171,14 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { } func TestCreateContainerWithVolume(t *testing.T) { + volumeName := "test-volume" // volumeMounts { req := testcontainers.ContainerRequest{ Image: "alpine", Mounts: testcontainers.ContainerMounts{ { Source: testcontainers.GenericVolumeMountSource{ - Name: "test-volume", + Name: volumeName, }, Target: "/data", }, @@ -190,8 +191,8 @@ func TestCreateContainerWithVolume(t *testing.T) { ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, c, testcontainers.RemoveVolumes(volumeName)) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) // Check if volume is created client, err := testcontainers.NewDockerClientWithOpts(ctx) @@ -204,12 +205,13 @@ func TestCreateContainerWithVolume(t *testing.T) { } func TestMountsReceiveRyukLabels(t *testing.T) { + volumeName := "app-data" req := testcontainers.ContainerRequest{ Image: "alpine", Mounts: testcontainers.ContainerMounts{ { Source: testcontainers.GenericVolumeMountSource{ - Name: "app-data", + Name: volumeName, }, Target: "/data", }, @@ -217,19 +219,24 @@ func TestMountsReceiveRyukLabels(t *testing.T) { } ctx := context.Background() + client, err := testcontainers.NewDockerClientWithOpts(ctx) + require.NoError(t, err) + defer client.Close() + + // Ensure the volume is removed before creating the container + // otherwise the volume will be reused and the labels won't be set. + err = client.VolumeRemove(ctx, volumeName, true) + require.NoError(t, err) + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, c, testcontainers.RemoveVolumes(volumeName)) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) - - // Check if volume is created with the expected labels - client, err := testcontainers.NewDockerClientWithOpts(ctx) - require.NoError(t, err) - defer client.Close() - volume, err := client.VolumeInspect(ctx, "app-data") + // Check if volume is created with the expected labels. + volume, err := client.VolumeInspect(ctx, volumeName) require.NoError(t, err) - assert.Equal(t, testcontainers.GenericLabels(), volume.Labels) + require.Equal(t, testcontainers.GenericLabels(), volume.Labels) } diff --git a/network.go b/network.go index 7ff0bf5ab3..e0cc83f510 100644 --- a/network.go +++ b/network.go @@ -3,14 +3,15 @@ package testcontainers import ( "context" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" + + "github.com/testcontainers/testcontainers-go/internal/core" ) // NetworkProvider allows the creation of networks on an arbitrary system type NetworkProvider interface { - CreateNetwork(context.Context, NetworkRequest) (Network, error) // create a network - GetNetwork(context.Context, NetworkRequest) (types.NetworkResource, error) // get a network + CreateNetwork(context.Context, NetworkRequest) (Network, error) // create a network + GetNetwork(context.Context, NetworkRequest) (network.Inspect, error) // get a network } // Deprecated: will be removed in the future @@ -24,21 +25,21 @@ type DefaultNetwork string // Deprecated: will be removed in the future. func (n DefaultNetwork) ApplyGenericTo(opts *GenericProviderOptions) { - opts.DefaultNetwork = string(n) + opts.defaultNetwork = string(n) } // Deprecated: will be removed in the future. func (n DefaultNetwork) ApplyDockerTo(opts *DockerProviderOptions) { - opts.DefaultNetwork = string(n) + opts.defaultNetwork = string(n) } // Deprecated: will be removed in the future // NetworkRequest represents the parameters used to get a network type NetworkRequest struct { Driver string - CheckDuplicate bool + CheckDuplicate bool // Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client package to older daemons. Internal bool - EnableIPv6 bool + EnableIPv6 *bool Name string Labels map[string]string Attachable bool @@ -48,3 +49,12 @@ type NetworkRequest struct { ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper registry ReaperOptions []ContainerOption // Deprecated: the reaper is configured at the properties level, for an entire test session } + +// sessionID returns the session ID for the network request. +func (r NetworkRequest) sessionID() string { + if sessionID := r.Labels[core.LabelSessionID]; sessionID != "" { + return sessionID + } + + return core.SessionID() +} diff --git a/network/examples_test.go b/network/examples_test.go new file mode 100644 index 0000000000..a6b6bec495 --- /dev/null +++ b/network/examples_test.go @@ -0,0 +1,76 @@ +package network_test + +import ( + "context" + "fmt" + "log" + + dockernetwork "github.com/docker/docker/api/types/network" + + "github.com/testcontainers/testcontainers-go/network" +) + +func ExampleNew() { + // createNetwork { + ctx := context.Background() + + net, err := network.New(ctx) + if err != nil { + fmt.Println(err) + return + } + defer func() { + if err := net.Remove(ctx); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + // } + + fmt.Println(net.ID != "") + fmt.Println(net.Driver) + + // Output: + // true + // bridge +} + +func ExampleNew_withOptions() { + // newNetworkWithOptions { + ctx := context.Background() + + // dockernetwork is the alias used for github.com/docker/docker/api/types/network + ipamConfig := dockernetwork.IPAM{ + Driver: "default", + Config: []dockernetwork.IPAMConfig{ + { + Subnet: "10.1.1.0/24", + Gateway: "10.1.1.254", + }, + }, + Options: map[string]string{ + "driver": "host-local", + }, + } + net, err := network.New(ctx, + network.WithIPAM(&ipamConfig), + network.WithAttachable(), + network.WithDriver("bridge"), + ) + if err != nil { + fmt.Println(err) + return + } + defer func() { + if err := net.Remove(ctx); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + // } + + fmt.Println(net.ID != "") + fmt.Println(net.Driver) + + // Output: + // true + // bridge +} diff --git a/network/network.go b/network/network.go index 8a057d4110..f185ffa189 100644 --- a/network/network.go +++ b/network/network.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" "github.com/google/uuid" @@ -19,7 +18,7 @@ import ( // - Labels: the Testcontainers for Go generic labels, to be managed by Ryuk. Please see the GenericLabels() function // And those options can be modified by the user, using the CreateModifier function field. func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.DockerNetwork, error) { - nc := types.NetworkCreate{ + nc := network.CreateOptions{ Driver: "bridge", Labels: testcontainers.GenericLabels(), } @@ -32,14 +31,13 @@ func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.Docker //nolint:staticcheck netReq := testcontainers.NetworkRequest{ - Driver: nc.Driver, - CheckDuplicate: nc.CheckDuplicate, - Internal: nc.Internal, - EnableIPv6: nc.EnableIPv6, - Name: uuid.NewString(), - Labels: nc.Labels, - Attachable: nc.Attachable, - IPAM: nc.IPAM, + Driver: nc.Driver, + Internal: nc.Internal, + EnableIPv6: nc.EnableIPv6, + Name: uuid.NewString(), + Labels: nc.Labels, + Attachable: nc.Attachable, + IPAM: nc.IPAM, } //nolint:staticcheck @@ -57,21 +55,21 @@ func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.Docker // NetworkCustomizer is an interface that can be used to configure the network create request. type NetworkCustomizer interface { - Customize(req *types.NetworkCreate) error + Customize(req *network.CreateOptions) error } // CustomizeNetworkOption is a type that can be used to configure the network create request. -type CustomizeNetworkOption func(req *types.NetworkCreate) error +type CustomizeNetworkOption func(req *network.CreateOptions) error // Customize implements the NetworkCustomizer interface, // applying the option to the network create request. -func (opt CustomizeNetworkOption) Customize(req *types.NetworkCreate) error { +func (opt CustomizeNetworkOption) Customize(req *network.CreateOptions) error { return opt(req) } // WithAttachable allows to set the network as attachable. func WithAttachable() CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { + return func(original *network.CreateOptions) error { original.Attachable = true return nil @@ -79,18 +77,17 @@ func WithAttachable() CustomizeNetworkOption { } // WithCheckDuplicate allows to check if a network with the same name already exists. +// +// Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client package to older daemons. func WithCheckDuplicate() CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { - //nolint:staticcheck - original.CheckDuplicate = true - + return func(original *network.CreateOptions) error { return nil } } // WithDriver allows to override the default network driver, which is "bridge". func WithDriver(driver string) CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { + return func(original *network.CreateOptions) error { original.Driver = driver return nil @@ -100,16 +97,16 @@ func WithDriver(driver string) CustomizeNetworkOption { // WithEnableIPv6 allows to set the network as IPv6 enabled. // Please use this option if and only if IPv6 is enabled on the Docker daemon. func WithEnableIPv6() CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { - original.EnableIPv6 = true - + return func(original *network.CreateOptions) error { + enableIPv6 := true + original.EnableIPv6 = &enableIPv6 return nil } } // WithInternal allows to set the network as internal. func WithInternal() CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { + return func(original *network.CreateOptions) error { original.Internal = true return nil @@ -119,7 +116,7 @@ func WithInternal() CustomizeNetworkOption { // WithLabels allows to set the network labels, adding the new ones // to the default Testcontainers for Go labels. func WithLabels(labels map[string]string) CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { + return func(original *network.CreateOptions) error { for k, v := range labels { original.Labels[k] = v } @@ -130,7 +127,7 @@ func WithLabels(labels map[string]string) CustomizeNetworkOption { // WithIPAM allows to change the default IPAM configuration. func WithIPAM(ipam *network.IPAM) CustomizeNetworkOption { - return func(original *types.NetworkCreate) error { + return func(original *network.CreateOptions) error { original.IPAM = ipam return nil diff --git a/network/network_test.go b/network/network_test.go index 4d081638cd..bbe5d45c7c 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -2,15 +2,11 @@ package network_test import ( "context" - "fmt" - "log" "testing" "time" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" dockernetwork "github.com/docker/docker/api/types/network" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -20,39 +16,28 @@ import ( ) const ( - nginxAlpineImage = "docker.io/nginx:alpine" + nginxAlpineImage = "nginx:alpine" nginxDefaultPort = "80/tcp" ) -// Create a network. -func ExampleNew() { - // createNetwork { +func TestNew(t *testing.T) { ctx := context.Background() net, err := network.New(ctx, - network.WithCheckDuplicate(), network.WithAttachable(), - // Makes the network internal only, meaning the host machine cannot access it. - // Remove or use `network.WithDriver("bridge")` to change the network's mode. - network.WithInternal(), + network.WithDriver("bridge"), network.WithLabels(map[string]string{"this-is-a-test": "value"}), ) - if err != nil { - fmt.Println(err) - return - } + require.NoError(t, err) defer func() { - if err := net.Remove(ctx); err != nil { - log.Fatalf("failed to remove network: %s", err) - } + require.NoError(t, net.Remove(ctx)) }() networkName := net.Name - // } - nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "nginx:alpine", + Image: nginxAlpineImage, ExposedPorts: []string{ "80/tcp", }, @@ -62,67 +47,35 @@ func ExampleNew() { }, Started: true, }) - defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) - } - }() + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) client, err := testcontainers.NewDockerClientWithOpts(context.Background()) - if err != nil { - fmt.Println(err) - return - } - - args := filters.NewArgs() - args.Add("name", networkName) + require.NoError(t, err) - resources, err := client.NetworkList(context.Background(), types.NetworkListOptions{ - Filters: args, + resources, err := client.NetworkList(context.Background(), dockernetwork.ListOptions{ + Filters: filters.NewArgs(filters.Arg("name", networkName)), }) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(len(resources)) + require.NoError(t, err) + require.Len(t, resources, 1) newNetwork := resources[0] - expectedLabels := testcontainers.GenericLabels() expectedLabels["this-is-a-test"] = "true" - fmt.Println(newNetwork.Attachable) - fmt.Println(newNetwork.Internal) - fmt.Println(newNetwork.Labels["this-is-a-test"]) - - state, err := nginxC.State(ctx) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(state.Running) - - // Output: - // 1 - // true - // true - // value - // true + require.True(t, newNetwork.Attachable) + require.False(t, newNetwork.Internal) + require.Equal(t, "value", newNetwork.Labels["this-is-a-test"]) + require.NoError(t, testcontainers.TerminateContainer(nginxC)) } // testNetworkAliases { func TestContainerAttachedToNewNetwork(t *testing.T) { ctx := context.Background() - newNetwork, err := network.New(ctx, network.WithCheckDuplicate()) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - require.NoError(t, newNetwork.Remove(ctx)) - }) + newNetwork, err := network.New(ctx) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name @@ -145,33 +98,21 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { } nginx, err := testcontainers.GenericContainer(ctx, gcr) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() networks, err := nginx.Networks(ctx) - if err != nil { - t.Fatal(err) - } - if len(networks) != 1 { - t.Errorf("Expected networks 1. Got '%d'.", len(networks)) - } - network := networks[0] - if network != networkName { - t.Errorf("Expected network name '%s'. Got '%s'.", networkName, network) - } + require.NoError(t, err) + require.Len(t, networks, 1) + + nw := networks[0] + require.Equal(t, networkName, nw) networkAliases, err := nginx.NetworkAliases(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkAliases) != 1 { - t.Errorf("Expected network aliases for 1 network. Got '%d'.", len(networkAliases)) - } + require.NoError(t, err) + require.Len(t, networkAliases, 1) networkAlias := networkAliases[networkName] - require.NotEmpty(t, networkAlias) for _, alias := range aliases { @@ -179,12 +120,8 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { } networkIP, err := nginx.ContainerIP(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkIP) == 0 { - t.Errorf("Expected an IP address, got %v", networkIP) - } + require.NoError(t, err) + require.NotEmpty(t, networkIP) } // } @@ -192,13 +129,9 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { func TestContainerIPs(t *testing.T) { ctx := context.Background() - newNetwork, err := network.New(ctx, network.WithCheckDuplicate()) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - require.NoError(t, newNetwork.Remove(ctx)) - }) + newNetwork, err := network.New(ctx) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name @@ -216,19 +149,12 @@ func TestContainerIPs(t *testing.T) { }, Started: true, }) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() ips, err := nginx.ContainerIPs(ctx) - if err != nil { - t.Fatal(err) - } - - if len(ips) != 2 { - t.Errorf("Expected two IP addresses, got %v", len(ips)) - } + require.NoError(t, err) + require.Len(t, ips, 2) } func TestContainerWithReaperNetwork(t *testing.T) { @@ -244,33 +170,25 @@ func TestContainerWithReaperNetwork(t *testing.T) { for i := 0; i < maxNetworksCount; i++ { n, err := network.New(ctx) require.NoError(t, err) - // use t.Cleanup to run after terminateContainerOnEnd - t.Cleanup(func() { - require.NoError(t, n.Remove(ctx)) - }) + testcontainers.CleanupNetwork(t, n) networks = append(networks, n.Name) } - req := testcontainers.ContainerRequest{ - Image: nginxAlpineImage, - ExposedPorts: []string{nginxDefaultPort}, - WaitingFor: wait.ForAll( - wait.ForListeningPort(nginxDefaultPort), - wait.ForLog("Configuration complete; ready for start up"), - ), - Networks: networks, - } - nginx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, + ContainerRequest: testcontainers.ContainerRequest{ + Image: nginxAlpineImage, + ExposedPorts: []string{nginxDefaultPort}, + WaitingFor: wait.ForAll( + wait.ForListeningPort(nginxDefaultPort), + wait.ForLog("Configuration complete; ready for start up"), + ), + Networks: networks, + }, + Started: true, }) - + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() containerId := nginx.GetContainerID() @@ -280,55 +198,39 @@ func TestContainerWithReaperNetwork(t *testing.T) { cnt, err := cli.ContainerInspect(ctx, containerId) require.NoError(t, err) - assert.Len(t, cnt.NetworkSettings.Networks, maxNetworksCount) - assert.NotNil(t, cnt.NetworkSettings.Networks[networks[0]]) - assert.NotNil(t, cnt.NetworkSettings.Networks[networks[1]]) + require.Len(t, cnt.NetworkSettings.Networks, maxNetworksCount) + require.NotNil(t, cnt.NetworkSettings.Networks[networks[0]]) + require.NotNil(t, cnt.NetworkSettings.Networks[networks[1]]) } func TestMultipleContainersInTheNewNetwork(t *testing.T) { ctx := context.Background() - net, err := network.New(ctx, network.WithCheckDuplicate(), network.WithDriver("bridge")) - if err != nil { - t.Fatal("cannot create network") - } - defer func() { - require.NoError(t, net.Remove(ctx)) - }() + net, err := network.New(ctx, network.WithDriver("bridge")) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, net) networkName := net.Name - req1 := testcontainers.ContainerRequest{ - Image: nginxAlpineImage, - Networks: []string{networkName}, - } - c1, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req1, - Started: true, + ContainerRequest: testcontainers.ContainerRequest{ + Image: nginxAlpineImage, + Networks: []string{networkName}, + }, + Started: true, }) - if err != nil { - t.Fatal(err) - } - defer func() { - require.NoError(t, c1.Terminate(ctx)) - }() + testcontainers.CleanupContainer(t, c1) + require.NoError(t, err) - req2 := testcontainers.ContainerRequest{ - Image: nginxAlpineImage, - Networks: []string{networkName}, - } c2, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req2, - Started: true, + ContainerRequest: testcontainers.ContainerRequest{ + Image: nginxAlpineImage, + Networks: []string{networkName}, + }, + Started: true, }) - if err != nil { - t.Fatal(err) - return - } - defer func() { - require.NoError(t, c2.Terminate(ctx)) - }() + testcontainers.CleanupContainer(t, c2) + require.NoError(t, err) pNets, err := c1.Networks(ctx) require.NoError(t, err) @@ -336,11 +238,11 @@ func TestMultipleContainersInTheNewNetwork(t *testing.T) { rNets, err := c2.Networks(ctx) require.NoError(t, err) - assert.Len(t, pNets, 1) - assert.Len(t, rNets, 1) + require.Len(t, pNets, 1) + require.Len(t, rNets, 1) - assert.Equal(t, networkName, pNets[0]) - assert.Equal(t, networkName, rNets[0]) + require.Equal(t, networkName, pNets[0]) + require.Equal(t, networkName, rNets[0]) } func TestNew_withOptions(t *testing.T) { @@ -361,24 +263,19 @@ func TestNew_withOptions(t *testing.T) { }, } net, err := network.New(ctx, - network.WithCheckDuplicate(), network.WithIPAM(&ipamConfig), network.WithAttachable(), network.WithDriver("bridge"), ) // } - if err != nil { - t.Fatal("cannot create network: ", err) - } - defer func() { - require.NoError(t, net.Remove(ctx)) - }() + require.NoError(t, err) + testcontainers.CleanupNetwork(t, net) networkName := net.Name nginx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "nginx:alpine", + Image: nginxAlpineImage, ExposedPorts: []string{ "80/tcp", }, @@ -387,32 +284,24 @@ func TestNew_withOptions(t *testing.T) { }, }, }) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() provider, err := testcontainers.ProviderDocker.GetProvider() - if err != nil { - t.Fatal("Cannot get Provider") - } + require.NoError(t, err) defer provider.Close() //nolint:staticcheck foundNetwork, err := provider.GetNetwork(ctx, testcontainers.NetworkRequest{Name: networkName}) - if err != nil { - t.Fatal("Cannot get created network by name") - } - assert.Equal(t, ipamConfig, foundNetwork.IPAM) + require.NoError(t, err) + require.Equal(t, ipamConfig, foundNetwork.IPAM) } func TestWithNetwork(t *testing.T) { // first create the network to be reused - nw, err := network.New(context.Background(), network.WithCheckDuplicate(), network.WithLabels(map[string]string{"network-type": "unique"})) + nw, err := network.New(context.Background(), network.WithLabels(map[string]string{"network-type": "unique"})) require.NoError(t, err) - defer func() { - require.NoError(t, nw.Remove(context.Background())) - }() + testcontainers.CleanupNetwork(t, nw) networkName := nw.Name @@ -425,35 +314,32 @@ func TestWithNetwork(t *testing.T) { err := network.WithNetwork([]string{"alias"}, nw)(&req) require.NoError(t, err) - assert.Len(t, req.Networks, 1) - assert.Equal(t, networkName, req.Networks[0]) + require.Len(t, req.Networks, 1) + require.Equal(t, networkName, req.Networks[0]) - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) } // verify that the network is created only once client, err := testcontainers.NewDockerClientWithOpts(context.Background()) require.NoError(t, err) - args := filters.NewArgs() - args.Add("name", networkName) - - resources, err := client.NetworkList(context.Background(), types.NetworkListOptions{ - Filters: args, + resources, err := client.NetworkList(context.Background(), dockernetwork.ListOptions{ + Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Len(t, resources, 1) + require.Len(t, resources, 1) newNetwork := resources[0] expectedLabels := testcontainers.GenericLabels() expectedLabels["network-type"] = "unique" - assert.Equal(t, networkName, newNetwork.Name) - assert.False(t, newNetwork.Attachable) - assert.False(t, newNetwork.Internal) - assert.Equal(t, expectedLabels, newNetwork.Labels) + require.Equal(t, networkName, newNetwork.Name) + require.False(t, newNetwork.Attachable) + require.False(t, newNetwork.Internal) + require.Equal(t, expectedLabels, newNetwork.Labels) } func TestWithSyntheticNetwork(t *testing.T) { @@ -472,31 +358,26 @@ func TestWithSyntheticNetwork(t *testing.T) { err := network.WithNetwork([]string{"alias"}, nw)(&req) require.NoError(t, err) - assert.Len(t, req.Networks, 1) - assert.Equal(t, networkName, req.Networks[0]) + require.Len(t, req.Networks, 1) + require.Equal(t, networkName, req.Networks[0]) - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) // verify that the network is created only once client, err := testcontainers.NewDockerClientWithOpts(context.Background()) require.NoError(t, err) - args := filters.NewArgs() - args.Add("name", networkName) - - resources, err := client.NetworkList(context.Background(), types.NetworkListOptions{ - Filters: args, + resources, err := client.NetworkList(context.Background(), dockernetwork.ListOptions{ + Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Empty(t, resources) // no Docker network was created + require.Empty(t, resources) // no Docker network was created c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - assert.NotNil(t, c) - defer func() { - require.NoError(t, c.Terminate(context.Background())) - }() + require.NotNil(t, c) } func TestWithNewNetwork(t *testing.T) { @@ -510,25 +391,21 @@ func TestWithNewNetwork(t *testing.T) { network.WithLabels(map[string]string{"this-is-a-test": "value"}), )(&req) require.NoError(t, err) - - assert.Len(t, req.Networks, 1) + require.Len(t, req.Networks, 1) networkName := req.Networks[0] - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) client, err := testcontainers.NewDockerClientWithOpts(context.Background()) require.NoError(t, err) - args := filters.NewArgs() - args.Add("name", networkName) - - resources, err := client.NetworkList(context.Background(), types.NetworkListOptions{ - Filters: args, + resources, err := client.NetworkList(context.Background(), dockernetwork.ListOptions{ + Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Len(t, resources, 1) + require.Len(t, resources, 1) newNetwork := resources[0] defer func() { @@ -538,10 +415,10 @@ func TestWithNewNetwork(t *testing.T) { expectedLabels := testcontainers.GenericLabels() expectedLabels["this-is-a-test"] = "value" - assert.Equal(t, networkName, newNetwork.Name) - assert.True(t, newNetwork.Attachable) - assert.True(t, newNetwork.Internal) - assert.Equal(t, expectedLabels, newNetwork.Labels) + require.Equal(t, networkName, newNetwork.Name) + require.True(t, newNetwork.Attachable) + require.True(t, newNetwork.Internal) + require.Equal(t, expectedLabels, newNetwork.Labels) } func TestWithNewNetworkContextTimeout(t *testing.T) { @@ -560,6 +437,6 @@ func TestWithNewNetworkContextTimeout(t *testing.T) { require.Error(t, err) // we do not want to fail, just skip the network creation - assert.Empty(t, req.Networks) - assert.Empty(t, req.NetworkAliases) + require.Empty(t, req.Networks) + require.Empty(t, req.NetworkAliases) } diff --git a/options.go b/options.go index 95b6d9394f..4eb39058c8 100644 --- a/options.go +++ b/options.go @@ -3,6 +3,7 @@ package testcontainers import ( "context" "fmt" + "net/url" "time" "dario.cat/mergo" @@ -95,6 +96,7 @@ func WithHostPortAccess(ports ...int) CustomizeRequestOption { } } +// Deprecated: the modules API forces passing the image as part of the signature of the Run function. // WithImage sets the image for a container func WithImage(image string) CustomizeRequestOption { return func(req *GenericContainerRequest) error { @@ -116,6 +118,52 @@ type ImageSubstitutor interface { // } +// CustomHubSubstitutor represents a way to substitute the hub of an image with a custom one, +// using provided value with respect to the HubImageNamePrefix configuration value. +type CustomHubSubstitutor struct { + hub string +} + +// NewCustomHubSubstitutor creates a new CustomHubSubstitutor +func NewCustomHubSubstitutor(hub string) CustomHubSubstitutor { + return CustomHubSubstitutor{ + hub: hub, + } +} + +// Description returns the name of the type and a short description of how it modifies the image. +func (c CustomHubSubstitutor) Description() string { + return fmt.Sprintf("CustomHubSubstitutor (replaces hub with %s)", c.hub) +} + +// Substitute replaces the hub of the image with the provided one, with certain conditions: +// - if the hub is empty, the image is returned as is. +// - if the image already contains a registry, the image is returned as is. +// - if the HubImageNamePrefix configuration value is set, the image is returned as is. +func (c CustomHubSubstitutor) Substitute(image string) (string, error) { + registry := core.ExtractRegistry(image, "") + cfg := ReadConfig() + + exclusions := []func() bool{ + func() bool { return c.hub == "" }, + func() bool { return registry != "" }, + func() bool { return cfg.Config.HubImageNamePrefix != "" }, + } + + for _, exclusion := range exclusions { + if exclusion() { + return image, nil + } + } + + result, err := url.JoinPath(c.hub, image) + if err != nil { + return "", err + } + + return result, nil +} + // prependHubRegistry represents a way to prepend a custom Hub registry to the image name, // using the HubImageNamePrefix configuration value type prependHubRegistry struct { @@ -138,7 +186,7 @@ func (p prependHubRegistry) Description() string { // - if the prefix is empty, the image is returned as is. // - if the image is a non-hub image (e.g. where another registry is set), the image is returned as is. // - if the image is a Docker Hub image where the hub registry is explicitly part of the name -// (i.e. anything with a docker.io or registry.hub.docker.com host part), the image is returned as is. +// (i.e. anything with a registry.hub.docker.com host part), the image is returned as is. func (p prependHubRegistry) Substitute(image string) (string, error) { registry := core.ExtractRegistry(image, "") @@ -156,7 +204,12 @@ func (p prependHubRegistry) Substitute(image string) (string, error) { } } - return fmt.Sprintf("%s/%s", p.prefix, image), nil + result, err := url.JoinPath(p.prefix, image) + if err != nil { + return "", err + } + + return result, nil } // WithImageSubstitutors sets the image substitutors for a container diff --git a/options_test.go b/options_test.go index 94d01adc59..a1d5af0fa5 100644 --- a/options_test.go +++ b/options_test.go @@ -54,7 +54,7 @@ func TestOverrideContainerRequest(t *testing.T) { // toBeMergedRequest should not be changed assert.Equal(t, "", toBeMergedRequest.Env["BAR"]) - assert.Len(t, toBeMergedRequest.ExposedPorts, 1) + require.Len(t, toBeMergedRequest.ExposedPorts, 1) assert.Equal(t, "67890/tcp", toBeMergedRequest.ExposedPorts[0]) // req should be merged with toBeMergedRequest @@ -91,16 +91,13 @@ func TestWithLogConsumers(t *testing.T) { err := testcontainers.WithLogConsumers(lc)(&req) require.NoError(t, err) - c, err := testcontainers.GenericContainer(context.Background(), req) + ctx := context.Background() + c, err := testcontainers.GenericContainer(ctx, req) + testcontainers.CleanupContainer(t, c) // we expect an error because the MySQL environment variables are not set // but this is expected because we just want to test the log consumer - require.Error(t, err) - defer func() { - err = c.Terminate(context.Background()) - require.NoError(t, err) - }() - - assert.NotEmpty(t, lc.msgs) + require.ErrorContains(t, err, "container exited with code 1") + require.NotEmpty(t, lc.msgs) } func TestWithStartupCommand(t *testing.T) { @@ -117,15 +114,12 @@ func TestWithStartupCommand(t *testing.T) { err := testcontainers.WithStartupCommand(testExec)(&req) require.NoError(t, err) - assert.Len(t, req.LifecycleHooks, 1) - assert.Len(t, req.LifecycleHooks[0].PostStarts, 1) + require.Len(t, req.LifecycleHooks, 1) + require.Len(t, req.LifecycleHooks[0].PostStarts, 1) c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err = c.Terminate(context.Background()) - require.NoError(t, err) - }() _, reader, err := c.Exec(context.Background(), []string{"ls", "/tmp/.testcontainers"}, exec.Multiplexed()) require.NoError(t, err) @@ -149,15 +143,12 @@ func TestWithAfterReadyCommand(t *testing.T) { err := testcontainers.WithAfterReadyCommand(testExec)(&req) require.NoError(t, err) - assert.Len(t, req.LifecycleHooks, 1) - assert.Len(t, req.LifecycleHooks[0].PostReadies, 1) + require.Len(t, req.LifecycleHooks, 1) + require.Len(t, req.LifecycleHooks[0].PostReadies, 1) c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err = c.Terminate(context.Background()) - require.NoError(t, err) - }() _, reader, err := c.Exec(context.Background(), []string{"ls", "/tmp/.testcontainers"}, exec.Multiplexed()) require.NoError(t, err) diff --git a/parallel.go b/parallel.go index 34740eeaf4..0349023ba2 100644 --- a/parallel.go +++ b/parallel.go @@ -31,25 +31,28 @@ func (gpe ParallelContainersError) Error() string { return fmt.Sprintf("%v", gpe.Errors) } +// parallelContainersResult represents result. +type parallelContainersResult struct { + ParallelContainersRequestError + Container Container +} + func parallelContainersRunner( ctx context.Context, requests <-chan GenericContainerRequest, - errors chan<- ParallelContainersRequestError, - containers chan<- Container, + results chan<- parallelContainersResult, wg *sync.WaitGroup, ) { + defer wg.Done() for req := range requests { c, err := GenericContainer(ctx, req) + res := parallelContainersResult{Container: c} if err != nil { - errors <- ParallelContainersRequestError{ - Request: req, - Error: err, - } - continue + res.Request = req + res.Error = err } - containers <- c + results <- res } - wg.Done() } // ParallelContainers creates a generic containers with parameters and run it in parallel mode @@ -64,41 +67,26 @@ func ParallelContainers(ctx context.Context, reqs ParallelContainerRequest, opt } tasksChan := make(chan GenericContainerRequest, tasksChanSize) - errsChan := make(chan ParallelContainersRequestError) - resChan := make(chan Container) - waitRes := make(chan struct{}) - - containers := make([]Container, 0) - errors := make([]ParallelContainersRequestError, 0) + resultsChan := make(chan parallelContainersResult, tasksChanSize) + done := make(chan struct{}) - wg := sync.WaitGroup{} + var wg sync.WaitGroup wg.Add(tasksChanSize) // run workers for i := 0; i < tasksChanSize; i++ { - go parallelContainersRunner(ctx, tasksChan, errsChan, resChan, &wg) + go parallelContainersRunner(ctx, tasksChan, resultsChan, &wg) } + var errs []ParallelContainersRequestError + containers := make([]Container, 0, len(reqs)) go func() { - for { - select { - case c, ok := <-resChan: - if !ok { - resChan = nil - } else { - containers = append(containers, c) - } - case e, ok := <-errsChan: - if !ok { - errsChan = nil - } else { - errors = append(errors, e) - } - } - - if resChan == nil && errsChan == nil { - waitRes <- struct{}{} - break + defer close(done) + for res := range resultsChan { + if res.Error != nil { + errs = append(errs, res.ParallelContainersRequestError) + } else { + containers = append(containers, res.Container) } } }() @@ -107,14 +95,15 @@ func ParallelContainers(ctx context.Context, reqs ParallelContainerRequest, opt tasksChan <- req } close(tasksChan) + wg.Wait() - close(resChan) - close(errsChan) - <-waitRes + close(resultsChan) + + <-done - if len(errors) != 0 { - return containers, ParallelContainersError{Errors: errors} + if len(errs) != 0 { + return containers, ParallelContainersError{Errors: errs} } return containers, nil diff --git a/parallel_test.go b/parallel_test.go index 122f59a4f7..25f919e99d 100644 --- a/parallel_test.go +++ b/parallel_test.go @@ -2,7 +2,6 @@ package testcontainers import ( "context" - "errors" "fmt" "testing" "time" @@ -99,23 +98,18 @@ func TestParallelContainers(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { res, err := ParallelContainers(context.Background(), tc.reqs, ParallelContainersOptions{}) - if err != nil { - require.NotZero(t, tc.expErrors) - var e ParallelContainersError - errors.As(err, &e) - if len(e.Errors) != tc.expErrors { - t.Fatalf("expected errors: %d, got: %d\n", tc.expErrors, len(e.Errors)) - } - } - for _, c := range res { - c := c - terminateContainerOnEnd(t, context.Background(), c) + CleanupContainer(t, c) } - if len(res) != tc.resLen { - t.Fatalf("expected containers: %d, got: %d\n", tc.resLen, len(res)) + if tc.expErrors != 0 { + require.Error(t, err) + var errs ParallelContainersError + require.ErrorAs(t, err, &errs) + require.Len(t, errs.Errors, tc.expErrors) } + + require.Len(t, res, tc.resLen) }) } } @@ -157,11 +151,8 @@ func TestParallelContainersWithReuse(t *testing.T) { ctx := context.Background() res, err := ParallelContainers(ctx, parallelRequest, ParallelContainersOptions{}) - if err != nil { - var e ParallelContainersError - errors.As(err, &e) - t.Fatalf("expected errors: %d, got: %d\n", 0, len(e.Errors)) + for _, c := range res { + CleanupContainer(t, c) } - // Container is reused, only terminate first container - terminateContainerOnEnd(t, ctx, res[0]) + require.NoError(t, err) } diff --git a/port_forwarding.go b/port_forwarding.go index fc1e952d5e..bb6bae2393 100644 --- a/port_forwarding.go +++ b/port_forwarding.go @@ -2,9 +2,11 @@ package testcontainers import ( "context" + "errors" "fmt" "io" "net" + "sync" "time" "github.com/docker/docker/api/types/container" @@ -12,17 +14,19 @@ import ( "golang.org/x/crypto/ssh" "github.com/testcontainers/testcontainers-go/internal/core/network" + "github.com/testcontainers/testcontainers-go/wait" ) const ( // hubSshdImage { - image string = "testcontainers/sshd:1.2.0" + sshdImage string = "testcontainers/sshd:1.2.0" // } + // HostInternal is the internal hostname used to reach the host from the container, // using the SSHD container as a bridge. HostInternal string = "host.testcontainers.internal" user string = "root" - sshPort = "22" + sshPort = "22/tcp" ) // sshPassword is a random password generated for the SSHD container. @@ -35,11 +39,9 @@ var sshPassword = uuid.NewString() // 1. Create a new SSHD container. // 2. Expose the host ports to the container after the container is ready. // 3. Close the SSH sessions before killing the container. -func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (ContainerLifecycleHooks, error) { - var sshdConnectHook ContainerLifecycleHooks - - if len(p) == 0 { - return sshdConnectHook, fmt.Errorf("no ports to expose") +func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) (sshdConnectHook ContainerLifecycleHooks, err error) { + if len(ports) == 0 { + return sshdConnectHook, errors.New("no ports to expose") } // Use the first network of the container to connect to the SSHD container. @@ -55,9 +57,9 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (Cont opts := []ContainerCustomizer{} if len(req.Networks) > 0 { // get the first network of the container to connect the SSHD container to it. - nw, err := network.Get(ctx, sshdFirstNetwork) + nw, err := network.GetByName(ctx, sshdFirstNetwork) if err != nil { - return sshdConnectHook, fmt.Errorf("failed to get the network: %w", err) + return sshdConnectHook, fmt.Errorf("get network %q: %w", sshdFirstNetwork, err) } dockerNw := DockerNetwork{ @@ -88,14 +90,36 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (Cont // start the SSHD container with the provided options sshdContainer, err := newSshdContainer(ctx, opts...) + // Ensure the SSHD container is stopped and removed in case of error. + defer func() { + if err != nil { + err = errors.Join(err, TerminateContainer(sshdContainer)) + } + }() if err != nil { - return sshdConnectHook, fmt.Errorf("failed to create the SSH server: %w", err) + return sshdConnectHook, fmt.Errorf("new sshd container: %w", err) } - // IP in the first network of the container - sshdIP, err := sshdContainer.ContainerIP(context.Background()) + // IP in the first network of the container. + inspect, err := sshdContainer.Inspect(ctx) if err != nil { - return sshdConnectHook, fmt.Errorf("failed to get IP for the SSHD container: %w", err) + return sshdConnectHook, fmt.Errorf("inspect sshd container: %w", err) + } + + // TODO: remove once we have docker context support via #2810 + sshdIP := inspect.NetworkSettings.IPAddress + if sshdIP == "" { + single := len(inspect.NetworkSettings.Networks) == 1 + for name, network := range inspect.NetworkSettings.Networks { + if name == sshdFirstNetwork || single { + sshdIP = network.IPAddress + break + } + } + } + + if sshdIP == "" { + return sshdConnectHook, errors.New("sshd container IP not found") } if req.HostConfigModifier == nil { @@ -126,6 +150,20 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (Cont originalHCM(hostConfig) } + stopHooks := []ContainerHook{ + func(ctx context.Context, _ Container) error { + if ctx.Err() != nil { + // Context already canceled, need to create a new one to ensure + // the SSH session is closed. + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + } + + return TerminateContainer(sshdContainer, StopContext(ctx)) + }, + } + // after the container is ready, create the SSH tunnel // for each exposed port from the host. sshdConnectHook = ContainerLifecycleHooks{ @@ -134,12 +172,8 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (Cont return sshdContainer.exposeHostPort(ctx, req.HostAccessPorts...) }, }, - PreTerminates: []ContainerHook{ - func(ctx context.Context, _ Container) error { - // before killing the container, close the SSH sessions - return sshdContainer.Terminate(ctx) - }, - }, + PreStops: stopHooks, + PreTerminates: stopHooks, } return sshdConnectHook, nil @@ -149,10 +183,10 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, p ...int) (Cont func newSshdContainer(ctx context.Context, opts ...ContainerCustomizer) (*sshdContainer, error) { req := GenericContainerRequest{ ContainerRequest: ContainerRequest{ - Image: image, - HostAccessPorts: []int{}, // empty list because it does not need any port - ExposedPorts: []string{sshPort}, - Env: map[string]string{"PASSWORD": sshPassword}, + Image: sshdImage, + ExposedPorts: []string{sshPort}, + Env: map[string]string{"PASSWORD": sshPassword}, + WaitingFor: wait.ForListeningPort(sshPort), }, Started: true, } @@ -164,177 +198,230 @@ func newSshdContainer(ctx context.Context, opts ...ContainerCustomizer) (*sshdCo } c, err := GenericContainer(ctx, req) - if err != nil { - return nil, err + var sshd *sshdContainer + if c != nil { + sshd = &sshdContainer{Container: c} } - // force a type assertion to return a concrete type, - // because GenericContainer returns a Container interface. - dc := c.(*DockerContainer) - - sshd := &sshdContainer{ - DockerContainer: dc, - portForwarders: []PortForwarder{}, + if err != nil { + return sshd, fmt.Errorf("generic container: %w", err) } - sshClientConfig, err := configureSSHConfig(ctx, sshd) - if err != nil { - // return the container and the error to the caller to handle it + if err = sshd.clientConfig(ctx); err != nil { + // Return the container and the error to the caller to handle it. return sshd, err } - sshd.sshConfig = sshClientConfig - return sshd, nil } // sshdContainer represents the SSHD container type used for the port forwarding container. -// It's an internal type that extends the DockerContainer type, to add the SSH tunneling capabilities. +// It's an internal type that extends the DockerContainer type, to add the SSH tunnelling capabilities. type sshdContainer struct { - *DockerContainer + Container port string sshConfig *ssh.ClientConfig - portForwarders []PortForwarder + portForwarders []*portForwarder } // Terminate stops the container and closes the SSH session func (sshdC *sshdContainer) Terminate(ctx context.Context) error { + return errors.Join( + sshdC.closePorts(), + sshdC.Container.Terminate(ctx), + ) +} + +// Stop stops the container and closes the SSH session +func (sshdC *sshdContainer) Stop(ctx context.Context, timeout *time.Duration) error { + return errors.Join( + sshdC.closePorts(), + sshdC.Container.Stop(ctx, timeout), + ) +} + +// closePorts closes all port forwarders. +func (sshdC *sshdContainer) closePorts() error { + var errs []error for _, pfw := range sshdC.portForwarders { - pfw.Close(ctx) + if err := pfw.Close(); err != nil { + errs = append(errs, err) + } } - - return sshdC.DockerContainer.Terminate(ctx) + sshdC.portForwarders = nil // Ensure the port forwarders are not used after closing. + return errors.Join(errs...) } -func configureSSHConfig(ctx context.Context, sshdC *sshdContainer) (*ssh.ClientConfig, error) { +// clientConfig sets up the the SSHD client configuration. +func (sshdC *sshdContainer) clientConfig(ctx context.Context) error { mappedPort, err := sshdC.MappedPort(ctx, sshPort) if err != nil { - return nil, err + return fmt.Errorf("mapped port: %w", err) } - sshdC.port = mappedPort.Port() - sshConfig := ssh.ClientConfig{ + sshdC.port = mappedPort.Port() + sshdC.sshConfig = &ssh.ClientConfig{ User: user, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Auth: []ssh.AuthMethod{ssh.Password(sshPassword)}, - Timeout: 30 * time.Second, } - return &sshConfig, nil + return nil } -func (sshdC *sshdContainer) exposeHostPort(ctx context.Context, ports ...int) error { +// exposeHostPort exposes the host ports to the container. +func (sshdC *sshdContainer) exposeHostPort(ctx context.Context, ports ...int) (err error) { + defer func() { + if err != nil { + err = errors.Join(err, sshdC.closePorts()) + } + }() for _, port := range ports { - pw := NewPortForwarder(fmt.Sprintf("localhost:%s", sshdC.port), sshdC.sshConfig, port, port) - sshdC.portForwarders = append(sshdC.portForwarders, *pw) + pf, err := newPortForwarder(ctx, "localhost:"+sshdC.port, sshdC.sshConfig, port) + if err != nil { + return fmt.Errorf("new port forwarder: %w", err) + } - go pw.Forward(ctx) - } - - // continue when all port forwarders have created the connection - for _, pfw := range sshdC.portForwarders { - <-pfw.connectionCreated + sshdC.portForwarders = append(sshdC.portForwarders, pf) } return nil } -type PortForwarder struct { - sshDAddr string - sshConfig *ssh.ClientConfig - remotePort int - localPort int - connectionCreated chan struct{} // used to signal that the connection has been created, so the caller can proceed - terminateChan chan struct{} // used to signal that the connection has been terminated +// portForwarder forwards a port from the container to the host. +type portForwarder struct { + client *ssh.Client + listener net.Listener + dialTimeout time.Duration + localAddr string + ctx context.Context + cancel context.CancelFunc + + // closeMtx protects the close operation + closeMtx sync.Mutex + closeErr error } -func NewPortForwarder(sshDAddr string, sshConfig *ssh.ClientConfig, remotePort, localPort int) *PortForwarder { - return &PortForwarder{ - sshDAddr: sshDAddr, - sshConfig: sshConfig, - remotePort: remotePort, - localPort: localPort, - connectionCreated: make(chan struct{}), - terminateChan: make(chan struct{}), +// newPortForwarder creates a new running portForwarder for the given port. +// The context is only used for the initial SSH connection. +func newPortForwarder(ctx context.Context, sshDAddr string, sshConfig *ssh.ClientConfig, port int) (pf *portForwarder, err error) { + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", sshDAddr) + if err != nil { + return nil, fmt.Errorf("ssh dial: %w", err) } -} -func (pf *PortForwarder) Close(ctx context.Context) { - close(pf.terminateChan) - close(pf.connectionCreated) -} + // Ensure the connection is closed in case of error. + defer func() { + if err != nil { + err = errors.Join(err, conn.Close()) + } + }() -func (pf *PortForwarder) Forward(ctx context.Context) error { - client, err := ssh.Dial("tcp", pf.sshDAddr, pf.sshConfig) + c, chans, reqs, err := ssh.NewClientConn(conn, sshDAddr, sshConfig) if err != nil { - return fmt.Errorf("error dialing ssh server: %w", err) + return nil, fmt.Errorf("ssh new client conn: %w", err) } - defer client.Close() - listener, err := client.Listen("tcp", fmt.Sprintf("localhost:%d", pf.remotePort)) + client := ssh.NewClient(c, chans, reqs) + + listener, err := client.Listen("tcp", fmt.Sprintf("localhost:%d", port)) if err != nil { - return fmt.Errorf("error listening on remote port: %w", err) + return nil, fmt.Errorf("listening on remote port %d: %w", port, err) } - defer listener.Close() - // signal that the connection has been created - pf.connectionCreated <- struct{}{} + ctx, cancel := context.WithCancel(context.Background()) + + pf = &portForwarder{ + client: client, + listener: listener, + localAddr: fmt.Sprintf("localhost:%d", port), + ctx: ctx, + cancel: cancel, + dialTimeout: time.Second * 2, + } + + go pf.run() + + return pf, nil +} + +// Close closes the port forwarder. +func (pf *portForwarder) Close() error { + pf.closeMtx.Lock() + defer pf.closeMtx.Unlock() - // check if the context or the terminateChan has been closed select { - case <-ctx.Done(): - if err := listener.Close(); err != nil { - return fmt.Errorf("error closing listener: %w", err) - } - if err := client.Close(); err != nil { - return fmt.Errorf("error closing client: %w", err) - } - return nil - case <-pf.terminateChan: - if err := listener.Close(); err != nil { - return fmt.Errorf("error closing listener: %w", err) - } - if err := client.Close(); err != nil { - return fmt.Errorf("error closing client: %w", err) - } - return nil + case <-pf.ctx.Done(): + // Already closed. + return pf.closeErr default: } + var errs []error + if err := pf.listener.Close(); err != nil { + errs = append(errs, fmt.Errorf("close listener: %w", err)) + } + if err := pf.client.Close(); err != nil { + errs = append(errs, fmt.Errorf("close client: %w", err)) + } + + pf.closeErr = errors.Join(errs...) + pf.cancel() + + return pf.closeErr +} + +// run forwards the port from the remote connection to the local connection. +func (pf *portForwarder) run() { for { - remote, err := listener.Accept() + remote, err := pf.listener.Accept() if err != nil { - return fmt.Errorf("error accepting connection: %w", err) + if errors.Is(err, io.EOF) { + // The listener has been closed. + return + } + + // Ignore errors as they are transient and we want requests to + // continue to be accepted. + continue } - go pf.runTunnel(ctx, remote) + go pf.tunnel(remote) } } -// runTunnel runs a tunnel between two connections; as soon as one connection -// reaches EOF or reports an error, both connections are closed and this -// function returns. -func (pf *PortForwarder) runTunnel(ctx context.Context, remote net.Conn) { +// tunnel runs a tunnel between two connections; as soon as the forwarder +// context is cancelled or one connection copies returns, irrespective of +// the error, both connections are closed. +func (pf *portForwarder) tunnel(remote net.Conn) { + defer remote.Close() + + ctx, cancel := context.WithTimeout(pf.ctx, pf.dialTimeout) + defer cancel() + var dialer net.Dialer - local, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("localhost:%d", pf.localPort)) + local, err := dialer.DialContext(ctx, "tcp", pf.localAddr) if err != nil { - remote.Close() + // Nothing we can do with the error. return } defer local.Close() - defer remote.Close() - done := make(chan struct{}, 2) + ctx, cancel = context.WithCancel(pf.ctx) go func() { - io.Copy(local, remote) //nolint:errcheck // Nothing we can usefully do with the error - done <- struct{}{} + defer cancel() + io.Copy(local, remote) //nolint:errcheck // Nothing useful we can do with the error. }() go func() { - io.Copy(remote, local) //nolint:errcheck // Nothing we can usefully do with the error - done <- struct{}{} + defer cancel() + io.Copy(remote, local) //nolint:errcheck // Nothing useful we can do with the error. }() - <-done + // Wait for the context to be done before returning which triggers + // both connections to close. This is done to to prevent the copies + // blocking forever on unused connections. + <-ctx.Done() } diff --git a/port_forwarding_test.go b/port_forwarding_test.go index aa08159afd..d6395b20a7 100644 --- a/port_forwarding_test.go +++ b/port_forwarding_test.go @@ -6,9 +6,12 @@ import ( "io" "net" "net/http" + "net/http/httptest" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/network" @@ -19,183 +22,135 @@ const ( ) func TestExposeHostPorts(t *testing.T) { - tests := []struct { - name string - numberOfPorts int - hasNetwork bool - hasHostAccess bool - }{ - { - name: "single port", - numberOfPorts: 1, - hasHostAccess: true, - }, - { - name: "single port using a network", - numberOfPorts: 1, - hasNetwork: true, - hasHostAccess: true, - }, - { - name: "multiple ports", - numberOfPorts: 3, - hasHostAccess: true, - }, - { - name: "single port with cancellation", - numberOfPorts: 1, - hasHostAccess: false, + hostPorts := make([]int, 3) + for i := range hostPorts { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, expectedResponse) + })) + hostPorts[i] = server.Listener.Addr().(*net.TCPAddr).Port + t.Cleanup(server.Close) + } + + singlePort := hostPorts[0:1] + + t.Run("single-port", func(t *testing.T) { + testExposeHostPorts(t, singlePort, false, false) + }) + + t.Run("single-port-network", func(t *testing.T) { + testExposeHostPorts(t, singlePort, true, false) + }) + + t.Run("single-port-host-access", func(t *testing.T) { + testExposeHostPorts(t, singlePort, false, true) + }) + + t.Run("single-port-network-host-access", func(t *testing.T) { + testExposeHostPorts(t, singlePort, true, true) + }) + + t.Run("multi-port", func(t *testing.T) { + testExposeHostPorts(t, hostPorts, false, false) + }) + + t.Run("multi-port-network", func(t *testing.T) { + testExposeHostPorts(t, hostPorts, true, false) + }) + + t.Run("multi-port-host-access", func(t *testing.T) { + testExposeHostPorts(t, hostPorts, false, true) + }) + + t.Run("multi-port-network-host-access", func(t *testing.T) { + testExposeHostPorts(t, hostPorts, true, true) + }) +} + +func testExposeHostPorts(t *testing.T, hostPorts []int, hasNetwork, hasHostAccess bool) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + var hostAccessPorts []int + if hasHostAccess { + hostAccessPorts = hostPorts + } + req := testcontainers.GenericContainerRequest{ + // hostAccessPorts { + ContainerRequest: testcontainers.ContainerRequest{ + Image: "alpine:3.17", + HostAccessPorts: hostAccessPorts, + Cmd: []string{"top"}, }, + // } + Started: true, + } + + if hasNetwork { + nw, err := network.New(ctx) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, nw) + + req.Networks = []string{nw.Name} + req.NetworkAliases = map[string][]string{nw.Name: {"myalpine"}} } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - freePorts := make([]int, tt.numberOfPorts) - for i := range freePorts { - freePort, err := getFreePort() - if err != nil { - t.Fatal(err) - } - - freePorts[i] = freePort - - // create an http server for each port - server, err := createHttpServer(freePort) - if err != nil { - t.Fatal(err) - } - go func() { - _ = server.ListenAndServe() - }() - t.Cleanup(func() { - server.Close() - }) - } - - req := testcontainers.GenericContainerRequest{ - // hostAccessPorts { - ContainerRequest: testcontainers.ContainerRequest{ - Image: "alpine:3.17", - HostAccessPorts: freePorts, - Cmd: []string{"top"}, - }, - // } - Started: true, - } - - if tt.hasNetwork { - nw, err := network.New(context.Background()) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err := nw.Remove(context.Background()); err != nil { - t.Fatal(err) - } - }) - - req.Networks = []string{nw.ID} - req.NetworkAliases = map[string][]string{nw.ID: {"myalpine"}} - } - - ctx := context.Background() - if !tt.hasHostAccess { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, 10*time.Second) - defer cancel() - } - - c, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err := c.Terminate(context.Background()); err != nil { - t.Fatal(err) - } - }) - - if tt.hasHostAccess { - // create a container that has host access, which will - // automatically forward the port to the container - assertContainerHasHostAccess(t, c, freePorts...) - } else { - // force cancellation because of timeout - time.Sleep(11 * time.Second) - - assertContainerHasNoHostAccess(t, c, freePorts...) - } - }) + c, err := testcontainers.GenericContainer(ctx, req) + testcontainers.CleanupContainer(t, c) + require.NoError(t, err) + + if hasHostAccess { + // Verify that the container can access the host ports. + containerHasHostAccess(t, c, hostPorts...) + return } + + // Verify that the container cannot access the host ports. + containerHasNoHostAccess(t, c, hostPorts...) } +// httpRequest sends an HTTP request from the container to the host port via +// [testcontainers.HostInternal] address. func httpRequest(t *testing.T, c testcontainers.Container, port int) (int, string) { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + // wgetHostInternal { code, reader, err := c.Exec( - context.Background(), - []string{"wget", "-q", "-O", "-", fmt.Sprintf("http://%s:%d", testcontainers.HostInternal, port)}, + ctx, + []string{"wget", "-q", "-O", "-", "-T", "2", fmt.Sprintf("http://%s:%d", testcontainers.HostInternal, port)}, tcexec.Multiplexed(), ) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // read the response bs, err := io.ReadAll(reader) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return code, string(bs) } -func assertContainerHasHostAccess(t *testing.T, c testcontainers.Container, ports ...int) { +// containerHasHostAccess verifies that the container can access the host ports +// via [testcontainers.HostInternal] address. +func containerHasHostAccess(t *testing.T, c testcontainers.Container, ports ...int) { + t.Helper() for _, port := range ports { code, response := httpRequest(t, c, port) - if code != 0 { - t.Fatalf("expected status code [%d] but got [%d]", 0, code) - } - - if response != expectedResponse { - t.Fatalf("expected [%s] but got [%s]", expectedResponse, response) - } + require.Zero(t, code) + require.Equal(t, expectedResponse, response) } } -func assertContainerHasNoHostAccess(t *testing.T, c testcontainers.Container, ports ...int) { +// containerHasNoHostAccess verifies that the container cannot access the host ports +// via [testcontainers.HostInternal] address. +func containerHasNoHostAccess(t *testing.T, c testcontainers.Container, ports ...int) { + t.Helper() for _, port := range ports { - _, response := httpRequest(t, c, port) - - if response == expectedResponse { - t.Fatalf("expected not to get [%s] but got [%s]", expectedResponse, response) - } - } -} - -func createHttpServer(port int) (*http.Server, error) { - server := &http.Server{ - Addr: fmt.Sprintf(":%d", port), - } - - server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, expectedResponse) - }) - - return server, nil -} - -// getFreePort asks the kernel for a free open port that is ready to use. -func getFreePort() (int, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - return 0, err - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, err + code, response := httpRequest(t, c, port) + require.NotZero(t, code) + require.Contains(t, response, "bad address") } - defer l.Close() - return l.Addr().(*net.TCPAddr).Port, nil } diff --git a/provider.go b/provider.go index 1be1ebe20a..31714c0c14 100644 --- a/provider.go +++ b/provider.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -24,7 +25,7 @@ type ( // GenericProviderOptions defines options applicable to all providers GenericProviderOptions struct { Logger Logging - DefaultNetwork string + defaultNetwork string } // GenericProviderOption defines a common interface to modify GenericProviderOptions @@ -144,16 +145,10 @@ func NewDockerProvider(provOpts ...DockerProviderOption) (*DockerProvider, error return nil, err } - tcConfig := ReadConfig() - - dockerHost := core.ExtractDockerHost(ctx) - - p := &DockerProvider{ + return &DockerProvider{ DockerProviderOptions: o, - host: dockerHost, + host: core.MustExtractDockerHost(ctx), client: c, - config: tcConfig, - } - - return p, nil + config: config.Read(), + }, nil } diff --git a/provider_test.go b/provider_test.go index 2448d84c07..94206e46bf 100644 --- a/provider_test.go +++ b/provider_test.go @@ -4,11 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/core" ) func TestProviderTypeGetProviderAutodetect(t *testing.T) { - dockerHost := core.ExtractDockerHost(context.Background()) + dockerHost := core.MustExtractDockerHost(context.Background()) const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock" tests := []struct { @@ -16,7 +18,6 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { tr ProviderType DockerHost string want string - wantErr bool }{ { name: "default provider without podman.socket", @@ -65,17 +66,10 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { t.Setenv("DOCKER_HOST", tt.DockerHost) got, err := tt.tr.GetProvider() - if (err != nil) != tt.wantErr { - t.Errorf("ProviderType.GetProvider() error = %v, wantErr %v", err, tt.wantErr) - return - } + require.NoErrorf(t, err, "ProviderType.GetProvider()") provider, ok := got.(*DockerProvider) - if !ok { - t.Fatalf("ProviderType.GetProvider() = %T, want %T", got, &DockerProvider{}) - } - if provider.defaultBridgeNetworkName != tt.want { - t.Errorf("ProviderType.GetProvider() = %v, want %v", provider.defaultBridgeNetworkName, tt.want) - } + require.Truef(t, ok, "ProviderType.GetProvider() = %T, want %T", got, &DockerProvider{}) + require.Equalf(t, tt.want, provider.defaultBridgeNetworkName, "ProviderType.GetProvider() = %v, want %v", provider.defaultBridgeNetworkName, tt.want) }) } } diff --git a/reaper.go b/reaper.go index 54feb90cbe..1d97a36ffa 100644 --- a/reaper.go +++ b/reaper.go @@ -1,16 +1,20 @@ package testcontainers import ( - "bufio" + "bytes" "context" + "errors" "fmt" - "math/rand" + "io" "net" + "os" "strings" "sync" + "syscall" "time" "github.com/cenkalti/backoff/v4" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/errdefs" @@ -33,9 +37,23 @@ const ( var ( // Deprecated: it has been replaced by an internal value ReaperDefaultImage = config.ReaperDefaultImage - reaperInstance *Reaper // We would like to create reaper only once - reaperMutex sync.Mutex - reaperOnce sync.Once + + // defaultReaperPort is the default port that the reaper listens on if not + // overridden by the RYUK_PORT environment variable. + defaultReaperPort = nat.Port("8080/tcp") + + // errReaperNotFound is returned when no reaper container is found. + errReaperNotFound = errors.New("reaper not found") + + // errReaperDisabled is returned if a reaper is requested but the + // config has it disabled. + errReaperDisabled = errors.New("reaper disabled") + + // spawner is the singleton instance of reaperSpawner. + spawner = &reaperSpawner{} + + // reaperAck is the expected response from the reaper container. + reaperAck = []byte("ACK\n") ) // ReaperProvider represents a provider for the reaper to run itself with @@ -46,10 +64,18 @@ type ReaperProvider interface { } // NewReaper creates a Reaper with a sessionID to identify containers and a provider to use -// Deprecated: it's not possible to create a reaper anymore. Compose module uses this method +// Deprecated: it's not possible to create a reaper any more. Compose module uses this method // to create a reaper for the compose stack. +// +// The caller must call Connect at least once on the returned Reaper and use the returned +// result otherwise the reaper will be kept open until the process exits. func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error) { - return reuseOrCreateReaper(ctx, sessionID, provider) + reaper, err := spawner.reaper(ctx, sessionID, provider) + if err != nil { + return nil, fmt.Errorf("reaper: %w", err) + } + + return reaper, nil } // reaperContainerNameFromSessionID returns the container name that uniquely @@ -57,169 +83,306 @@ func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, r func reaperContainerNameFromSessionID(sessionID string) string { // The session id is 64 characters, so we will not hit the limit of 128 // characters for container names. - return fmt.Sprintf("reaper_%s", sessionID) + return "reaper_" + sessionID +} + +// reaperSpawner is a singleton that manages the reaper container. +type reaperSpawner struct { + instance *Reaper + mtx sync.Mutex +} + +// port returns the port that a new reaper should listens on. +func (r *reaperSpawner) port() nat.Port { + if port := os.Getenv("RYUK_PORT"); port != "" { + natPort, err := nat.NewPort("tcp", port) + if err != nil { + panic(fmt.Sprintf("invalid RYUK_PORT value %q: %s", port, err)) + } + return natPort + } + + return defaultReaperPort +} + +// backoff returns a backoff policy for the reaper spawner. +// It will take at most 20 seconds, doing each attempt every 100ms - 250ms. +func (r *reaperSpawner) backoff() *backoff.ExponentialBackOff { + // We want random intervals between 100ms and 250ms for concurrent executions + // to not be synchronized: it could be the case that multiple executions of this + // function happen at the same time (specifically when called from a different test + // process execution), and we want to avoid that they all try to find the reaper + // container at the same time. + b := &backoff.ExponentialBackOff{ + InitialInterval: time.Millisecond * 100, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + // Adjust MaxInterval to compensate for randomization factor which can be added to + // returned interval so we have a maximum of 250ms. + MaxInterval: time.Duration(float64(time.Millisecond*250) * backoff.DefaultRandomizationFactor), + MaxElapsedTime: time.Second * 20, + Stop: backoff.Stop, + Clock: backoff.SystemClock, + } + b.Reset() + + return b } -// lookUpReaperContainer returns a DockerContainer type with the reaper container in the case +// cleanup terminates the reaper container if set. +func (r *reaperSpawner) cleanup() error { + r.mtx.Lock() + defer r.mtx.Unlock() + + return r.cleanupLocked() +} + +// cleanupLocked terminates the reaper container if set. +// It must be called with the lock held. +func (r *reaperSpawner) cleanupLocked() error { + if r.instance == nil { + return nil + } + + err := TerminateContainer(r.instance.container) + r.instance = nil + + return err +} + +// lookupContainer returns a DockerContainer type with the reaper container in the case // it's found in the running state, and including the labels for sessionID, reaper, and ryuk. // It will perform a retry with exponential backoff to allow for the container to be started and // avoid potential false negatives. -func lookUpReaperContainer(ctx context.Context, sessionID string) (*DockerContainer, error) { +func (r *reaperSpawner) lookupContainer(ctx context.Context, sessionID string) (*DockerContainer, error) { dockerClient, err := NewDockerClientWithOpts(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("new client: %w", err) } defer dockerClient.Close() - // the backoff will take at most 5 seconds to find the reaper container - // doing each attempt every 100ms - exp := backoff.NewExponentialBackOff() + provider, err := NewDockerProvider() + if err != nil { + return nil, fmt.Errorf("new provider: %w", err) + } - // we want random intervals between 100ms and 500ms for concurrent executions - // to not be synchronized: it could be the case that multiple executions of this - // function happen at the same time (specifically when called from a different test - // process execution), and we want to avoid that they all try to find the reaper - // container at the same time. - exp.InitialInterval = time.Duration(rand.Intn(5)*100) * time.Millisecond - exp.RandomizationFactor = rand.Float64() * 0.5 - exp.Multiplier = rand.Float64() * 2.0 - exp.MaxInterval = 5.0 * time.Second // max interval between attempts - exp.MaxElapsedTime = 1 * time.Minute // max time to keep trying - - var reaperContainer *DockerContainer - err = backoff.Retry(func() error { - args := []filters.KeyValuePair{ + provider.SetClient(dockerClient) + + opts := container.ListOptions{ + All: true, + Filters: filters.NewArgs( filters.Arg("label", fmt.Sprintf("%s=%s", core.LabelSessionID, sessionID)), filters.Arg("label", fmt.Sprintf("%s=%t", core.LabelReaper, true)), filters.Arg("label", fmt.Sprintf("%s=%t", core.LabelRyuk, true)), filters.Arg("name", reaperContainerNameFromSessionID(sessionID)), - } + ), + } - resp, err := dockerClient.ContainerList(ctx, container.ListOptions{ - All: true, - Filters: filters.NewArgs(args...), - }) - if err != nil { - return err - } + return backoff.RetryWithData( + func() (*DockerContainer, error) { + resp, err := dockerClient.ContainerList(ctx, opts) + if err != nil { + return nil, fmt.Errorf("container list: %w", err) + } - if len(resp) == 0 { - // reaper container not found in the running state: do not look for it again - return nil - } + if len(resp) == 0 { + // No reaper container not found. + return nil, backoff.Permanent(errReaperNotFound) + } - if len(resp) > 1 { - return fmt.Errorf("not possible to have multiple reaper containers found for session ID %s", sessionID) - } + if len(resp) > 1 { + return nil, fmt.Errorf("found %d reaper containers for session ID %q", len(resp), sessionID) + } - r, err := containerFromDockerResponse(ctx, resp[0]) - if err != nil { - return err - } + r, err := provider.ContainerFromType(ctx, resp[0]) + if err != nil { + return nil, fmt.Errorf("from docker: %w", err) + } - reaperContainer = r + switch { + case r.healthStatus == types.Healthy, + r.healthStatus == types.NoHealthcheck: + return r, nil + case r.healthStatus != "": + return nil, fmt.Errorf("container not healthy: %s", r.healthStatus) + } - return nil - }, backoff.WithContext(exp, ctx)) + return r, nil + }, + backoff.WithContext(r.backoff(), ctx), + ) +} + +// isRunning returns an error if the container is not running. +func (r *reaperSpawner) isRunning(ctx context.Context, ctr Container) error { + state, err := ctr.State(ctx) if err != nil { - return nil, err + return fmt.Errorf("container state: %w", err) } - return reaperContainer, nil + if !state.Running { + // Use NotFound error to indicate the container is not running + // and should be recreated. + return errdefs.NotFound(fmt.Errorf("container state: %s", state.Status)) + } + + return nil } -// reuseOrCreateReaper returns an existing Reaper instance if it exists and is running. Otherwise, a new Reaper instance -// will be created with a sessionID to identify containers in the same test session/program. -func reuseOrCreateReaper(ctx context.Context, sessionID string, provider ReaperProvider) (*Reaper, error) { - reaperMutex.Lock() - defer reaperMutex.Unlock() - - // 1. if the reaper instance has been already created, return it - if reaperInstance != nil { - // Verify this instance is still running by checking state. - // Can't use Container.IsRunning because the bool is not updated when Reaper is terminated - state, err := reaperInstance.container.State(ctx) - if err != nil { - if !errdefs.IsNotFound(err) { - return nil, err +// retryError returns a permanent error if the error is not considered retryable. +func (r *reaperSpawner) retryError(err error) error { + var timeout interface { + Timeout() bool + } + switch { + case isCleanupSafe(err), + createContainerFailDueToNameConflictRegex.MatchString(err.Error()), + errors.Is(err, syscall.ECONNREFUSED), + errors.Is(err, syscall.ECONNRESET), + errors.Is(err, syscall.ECONNABORTED), + errors.Is(err, syscall.ETIMEDOUT), + errors.Is(err, os.ErrDeadlineExceeded), + errors.As(err, &timeout) && timeout.Timeout(), + errors.Is(err, context.DeadlineExceeded), + errors.Is(err, context.Canceled): + // Retryable error. + return err + default: + return backoff.Permanent(err) + } +} + +// reaper returns an existing Reaper instance if it exists and is running, otherwise +// a new Reaper instance will be created with a sessionID to identify containers in +// the same test session/program. If connect is true, the reaper will be connected +// to the reaper container. +// Returns an error if config.RyukDisabled is true. +// +// Safe for concurrent calls. +func (r *reaperSpawner) reaper(ctx context.Context, sessionID string, provider ReaperProvider) (*Reaper, error) { + if config.Read().RyukDisabled { + return nil, errReaperDisabled + } + + r.mtx.Lock() + defer r.mtx.Unlock() + + return backoff.RetryWithData( + r.retryLocked(ctx, sessionID, provider), + backoff.WithContext(r.backoff(), ctx), + ) +} + +// retryLocked returns a function that can be used to create or reuse a reaper container. +// If connect is true, the reaper will be connected to the reaper container. +// It must be called with the lock held. +func (r *reaperSpawner) retryLocked(ctx context.Context, sessionID string, provider ReaperProvider) func() (*Reaper, error) { + return func() (reaper *Reaper, err error) { + reaper, err = r.reuseOrCreate(ctx, sessionID, provider) + // Ensure that the reaper is terminated if an error occurred. + defer func() { + if err != nil { + if reaper != nil { + err = errors.Join(err, TerminateContainer(reaper.container)) + } + err = r.retryError(errors.Join(err, r.cleanupLocked())) } - } else if state.Running { - return reaperInstance, nil - } - // else: the reaper instance has been terminated, so we need to create a new one - reaperOnce = sync.Once{} - } - - // 2. because the reaper instance has not been created yet, look for it in the Docker daemon, which - // will happen if the reaper container has been created in the same test session but in a different - // test process execution (e.g. when running tests in parallel), not having initialized the reaper - // instance yet. - reaperContainer, err := lookUpReaperContainer(context.Background(), sessionID) - if err == nil && reaperContainer != nil { - // The reaper container exists as a Docker container: re-use it - Logger.Printf("đŸ”Ĩ Reaper obtained from Docker for this test session %s", reaperContainer.ID) - reaperInstance, err = reuseReaperContainer(ctx, sessionID, provider, reaperContainer) + }() if err != nil { return nil, err } - return reaperInstance, nil + + if err = r.isRunning(ctx, reaper.container); err != nil { + return nil, err + } + + // Check we can still connect. + termSignal, err := reaper.connect(ctx) + if err != nil { + return nil, fmt.Errorf("connect: %w", err) + } + + reaper.setOrSignal(termSignal) + + r.instance = reaper + + return reaper, nil } +} + +// reuseOrCreate returns an existing Reaper instance if it exists, otherwise a new Reaper instance. +func (r *reaperSpawner) reuseOrCreate(ctx context.Context, sessionID string, provider ReaperProvider) (*Reaper, error) { + if r.instance != nil { + // We already have an associated reaper. + return r.instance, nil + } + + // Look for an existing reaper created in the same test session but in a + // different test process execution e.g. when running tests in parallel. + container, err := r.lookupContainer(context.Background(), sessionID) + if err != nil { + if !errors.Is(err, errReaperNotFound) { + return nil, fmt.Errorf("look up container: %w", err) + } - // 3. the reaper container does not exist in the Docker daemon: create it, and do it using the - // synchronization primitive to avoid multiple executions of this function to create the reaper - var reaperErr error - reaperOnce.Do(func() { - r, err := newReaper(ctx, sessionID, provider) + // The reaper container was not found, continue to create a new one. + reaper, err := r.newReaper(ctx, sessionID, provider) if err != nil { - reaperErr = err - return + return nil, fmt.Errorf("new reaper: %w", err) } - reaperInstance, reaperErr = r, nil - }) - if reaperErr != nil { - reaperOnce = sync.Once{} - return nil, reaperErr + return reaper, nil } - return reaperInstance, nil + // A reaper container exists re-use it. + reaper, err := r.fromContainer(ctx, sessionID, provider, container) + if err != nil { + return nil, fmt.Errorf("from container %q: %w", container.ID[:8], err) + } + + return reaper, nil } -// reuseReaperContainer constructs a Reaper from an already running reaper -// DockerContainer. -func reuseReaperContainer(ctx context.Context, sessionID string, provider ReaperProvider, reaperContainer *DockerContainer) (*Reaper, error) { - endpoint, err := reaperContainer.PortEndpoint(ctx, "8080", "") +// fromContainer constructs a Reaper from an already running reaper DockerContainer. +func (r *reaperSpawner) fromContainer(ctx context.Context, sessionID string, provider ReaperProvider, dockerContainer *DockerContainer) (*Reaper, error) { + Logger.Printf("âŗ Waiting for Reaper %q to be ready", dockerContainer.ID[:8]) + + // Reusing an existing container so we determine the port from the container's exposed ports. + if err := wait.ForExposedPort(). + WithPollInterval(100*time.Millisecond). + SkipInternalCheck(). + WaitUntilReady(ctx, dockerContainer); err != nil { + return nil, fmt.Errorf("wait for reaper %s: %w", dockerContainer.ID[:8], err) + } + + endpoint, err := dockerContainer.Endpoint(ctx, "") if err != nil { - return nil, err + return nil, fmt.Errorf("port endpoint: %w", err) } + + Logger.Printf("đŸ”Ĩ Reaper obtained from Docker for this test session %s", dockerContainer.ID[:8]) + return &Reaper{ Provider: provider, SessionID: sessionID, Endpoint: endpoint, - container: reaperContainer, + container: dockerContainer, }, nil } -// newReaper creates a Reaper with a sessionID to identify containers and a -// provider to use. Do not call this directly, use reuseOrCreateReaper instead. -func newReaper(ctx context.Context, sessionID string, provider ReaperProvider) (*Reaper, error) { - dockerHostMount := core.ExtractDockerSocket(ctx) - - reaper := &Reaper{ - Provider: provider, - SessionID: sessionID, - } - - listeningPort := nat.Port("8080/tcp") +// newReaper creates a connected Reaper with a sessionID to identify containers +// and a provider to use. +func (r *reaperSpawner) newReaper(ctx context.Context, sessionID string, provider ReaperProvider) (reaper *Reaper, err error) { + dockerHostMount := core.MustExtractDockerSocket(ctx) + port := r.port() tcConfig := provider.Config().Config - req := ContainerRequest{ Image: config.ReaperDefaultImage, - ExposedPorts: []string{string(listeningPort)}, + ExposedPorts: []string{string(port)}, Labels: core.DefaultLabels(sessionID), Privileged: tcConfig.RyukPrivileged, - WaitingFor: wait.ForListeningPort(listeningPort), + WaitingFor: wait.ForListeningPort(port), Name: reaperContainerNameFromSessionID(sessionID), HostConfigModifier: func(hc *container.HostConfig) { hc.AutoRemove = true @@ -238,133 +401,179 @@ func newReaper(ctx context.Context, sessionID string, provider ReaperProvider) ( req.Env["RYUK_VERBOSE"] = "true" } - // include reaper-specific labels to the reaper container + // Setup reaper-specific labels for the reaper container. req.Labels[core.LabelReaper] = "true" req.Labels[core.LabelRyuk] = "true" + delete(req.Labels, core.LabelReap) // Attach reaper container to a requested network if it is specified if p, ok := provider.(*DockerProvider); ok { - req.Networks = append(req.Networks, p.DefaultNetwork) + defaultNetwork, err := p.ensureDefaultNetwork(ctx) + if err != nil { + return nil, fmt.Errorf("ensure default network: %w", err) + } + + req.Networks = append(req.Networks, defaultNetwork) } c, err := provider.RunContainer(ctx, req) - if err != nil { - // We need to check whether the error is caused by a container with the same name - // already existing due to race conditions. We manually match the error message - // as we do not have any error types to check against. - if createContainerFailDueToNameConflictRegex.MatchString(err.Error()) { - // Manually retrieve the already running reaper container. However, we need to - // use retries here as there are two possible race conditions that might lead to - // errors: In most cases, there is a small delay between container creation and - // actually being visible in list-requests. This means that creation might fail - // due to name conflicts, but when we list containers with this name, we do not - // get any results. In another case, the container might have simply died in the - // meantime and therefore cannot be found. - const timeout = 5 * time.Second - const cooldown = 100 * time.Millisecond - start := time.Now() - var reaperContainer *DockerContainer - for time.Since(start) < timeout { - reaperContainer, err = lookUpReaperContainer(ctx, sessionID) - if err == nil && reaperContainer != nil { - break - } - select { - case <-ctx.Done(): - case <-time.After(cooldown): - } - } - if err != nil { - return nil, fmt.Errorf("look up reaper container due to name conflict failed: %w", err) - } - // If the reaper container was not found, it is most likely to have died in - // between as we can exclude any client errors because of the previous error - // check. Because the reaper should only die if it performed clean-ups, we can - // fail here as the reaper timeout needs to be increased, anyway. - if reaperContainer == nil { - return nil, fmt.Errorf("look up reaper container returned nil although creation failed due to name conflict") - } - Logger.Printf("đŸ”Ĩ Reaper obtained from Docker for this test session %s", reaperContainer.ID) - reaper, err := reuseReaperContainer(ctx, sessionID, provider, reaperContainer) - if err != nil { - return nil, err - } - return reaper, nil + defer func() { + if err != nil { + err = errors.Join(err, TerminateContainer(c)) } - return nil, err + }() + if err != nil { + return nil, fmt.Errorf("run container: %w", err) } - reaper.container = c - endpoint, err := c.PortEndpoint(ctx, "8080", "") + endpoint, err := c.PortEndpoint(ctx, port, "") if err != nil { - return nil, err + return nil, fmt.Errorf("port endpoint: %w", err) } - reaper.Endpoint = endpoint - return reaper, nil + return &Reaper{ + Provider: provider, + SessionID: sessionID, + Endpoint: endpoint, + container: c, + }, nil } // Reaper is used to start a sidecar container that cleans up resources type Reaper struct { - Provider ReaperProvider - SessionID string - Endpoint string - container Container + Provider ReaperProvider + SessionID string + Endpoint string + container Container + mtx sync.Mutex // Protects termSignal. + termSignal chan bool } -// Connect runs a goroutine which can be terminated by sending true into the returned channel +// Connect connects to the reaper container and sends the labels to it +// so that it can clean up the containers with the same labels. +// +// It returns a channel that can be closed to terminate the connection. +// Returns an error if config.RyukDisabled is true. func (r *Reaper) Connect() (chan bool, error) { - conn, err := net.DialTimeout("tcp", r.Endpoint, 10*time.Second) - if err != nil { - return nil, fmt.Errorf("%w: Connecting to Ryuk on %s failed", err, r.Endpoint) + if config.Read().RyukDisabled { + return nil, errReaperDisabled } - terminationSignal := make(chan bool) - go func(conn net.Conn) { - sock := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - defer conn.Close() + if termSignal := r.useTermSignal(); termSignal != nil { + return termSignal, nil + } - labelFilters := []string{} - for l, v := range core.DefaultLabels(r.SessionID) { - labelFilters = append(labelFilters, fmt.Sprintf("label=%s=%s", l, v)) - } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() - retryLimit := 3 - for retryLimit > 0 { - retryLimit-- + return r.connect(ctx) +} - if _, err := sock.WriteString(strings.Join(labelFilters, "&")); err != nil { - continue - } +// close signals the connection to close if needed. +// Safe for concurrent calls. +func (r *Reaper) close() { + r.mtx.Lock() + defer r.mtx.Unlock() - if _, err := sock.WriteString("\n"); err != nil { - continue - } + if r.termSignal != nil { + r.termSignal <- true + r.termSignal = nil + } +} - if err := sock.Flush(); err != nil { - continue - } +// setOrSignal sets the reapers termSignal field if nil +// otherwise consumes by sending true to it. +// Safe for concurrent calls. +func (r *Reaper) setOrSignal(termSignal chan bool) { + r.mtx.Lock() + defer r.mtx.Unlock() + + if r.termSignal != nil { + // Already have an existing connection, close the new one. + termSignal <- true + return + } - resp, err := sock.ReadString('\n') - if err != nil { - continue - } + // First or new unused termSignal, assign for caller to reuse. + r.termSignal = termSignal +} - if resp == "ACK\n" { - break - } - } +// useTermSignal if termSignal is not nil returns it +// and sets it to nil, otherwise returns nil. +// +// Safe for concurrent calls. +func (r *Reaper) useTermSignal() chan bool { + r.mtx.Lock() + defer r.mtx.Unlock() + + if r.termSignal == nil { + return nil + } + + // Use existing connection. + term := r.termSignal + r.termSignal = nil + + return term +} + +// connect connects to the reaper container and sends the labels to it +// so that it can clean up the containers with the same labels. +// +// It returns a channel that can be sent true to terminate the connection. +// Returns an error if config.RyukDisabled is true. +func (r *Reaper) connect(ctx context.Context) (chan bool, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", r.Endpoint) + if err != nil { + return nil, fmt.Errorf("dial reaper %s: %w", r.Endpoint, err) + } + terminationSignal := make(chan bool) + go func() { + defer conn.Close() + if err := r.handshake(conn); err != nil { + Logger.Printf("Reaper handshake failed: %s", err) + } <-terminationSignal - }(conn) + }() return terminationSignal, nil } +// handshake sends the labels to the reaper container and reads the ACK. +func (r *Reaper) handshake(conn net.Conn) error { + labels := core.DefaultLabels(r.SessionID) + labelFilters := make([]string, 0, len(labels)) + for l, v := range labels { + labelFilters = append(labelFilters, fmt.Sprintf("label=%s=%s", l, v)) + } + + filters := []byte(strings.Join(labelFilters, "&") + "\n") + buf := make([]byte, 4) + if _, err := conn.Write(filters); err != nil { + return fmt.Errorf("writing filters: %w", err) + } + + n, err := io.ReadFull(conn, buf) + if err != nil { + return fmt.Errorf("read ack: %w", err) + } + + if !bytes.Equal(reaperAck, buf[:n]) { + // We have received the ACK so all done. + return fmt.Errorf("unexpected reaper response: %s", buf[:n]) + } + + return nil +} + // Labels returns the container labels to use so that this Reaper cleans them up // Deprecated: internally replaced by core.DefaultLabels(sessionID) func (r *Reaper) Labels() map[string]string { - return map[string]string{ - core.LabelLang: "go", - core.LabelSessionID: r.SessionID, - } + return GenericLabels() +} + +// isReaperImage returns true if the image name is the reaper image. +func isReaperImage(name string) bool { + return strings.HasSuffix(name, config.ReaperDefaultImage) } diff --git a/reaper_test.go b/reaper_test.go index d0b6f4fc7c..e9bc5ccb9f 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -4,14 +4,15 @@ import ( "context" "errors" "os" + "strconv" "sync" "testing" "time" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/errdefs" "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/internal/config" @@ -23,48 +24,29 @@ import ( const testSessionID = "this-is-a-different-session-id" type mockReaperProvider struct { - req ContainerRequest - hostConfig *container.HostConfig - enpointSettings map[string]*network.EndpointSettings - config TestcontainersConfig - initialReaper *Reaper - initialReaperOnce sync.Once - t *testing.T + req ContainerRequest + hostConfig *container.HostConfig + endpointSettings map[string]*network.EndpointSettings + config TestcontainersConfig } -func newMockReaperProvider(t *testing.T) *mockReaperProvider { +func newMockReaperProvider(cfg config.Config) *mockReaperProvider { m := &mockReaperProvider{ config: TestcontainersConfig{ - Config: config.Config{}, + Config: cfg, }, - t: t, - initialReaper: reaperInstance, - //nolint:govet - initialReaperOnce: reaperOnce, } - // explicitly reset the reaperInstance to nil to start from a fresh state - reaperInstance = nil - reaperOnce = sync.Once{} - return m } var errExpected = errors.New("expected") -func (m *mockReaperProvider) RestoreReaperState() { - m.t.Cleanup(func() { - reaperInstance = m.initialReaper - //nolint:govet - reaperOnce = m.initialReaperOnce - }) -} - func (m *mockReaperProvider) RunContainer(ctx context.Context, req ContainerRequest) (Container, error) { m.req = req m.hostConfig = &container.HostConfig{} - m.enpointSettings = map[string]*network.EndpointSettings{} + m.endpointSettings = map[string]*network.EndpointSettings{} if req.HostConfigModifier == nil { req.HostConfigModifier = defaultHostConfigModifier(req) @@ -72,7 +54,7 @@ func (m *mockReaperProvider) RunContainer(ctx context.Context, req ContainerRequ req.HostConfigModifier(m.hostConfig) if req.EnpointSettingsModifier != nil { - req.EnpointSettingsModifier(m.enpointSettings) + req.EnpointSettingsModifier(m.endpointSettings) } // we're only interested in the request, so instead of mocking the Docker client @@ -84,14 +66,14 @@ func (m *mockReaperProvider) Config() TestcontainersConfig { return m.config } -// createContainerRequest creates the expected request and allows for customization -func createContainerRequest(customize func(ContainerRequest) ContainerRequest) ContainerRequest { +// expectedReaperRequest creates the expected reaper container request with the given customizations. +func expectedReaperRequest(customize ...func(*ContainerRequest)) ContainerRequest { req := ContainerRequest{ Image: config.ReaperDefaultImage, ExposedPorts: []string{"8080/tcp"}, Labels: core.DefaultLabels(testSessionID), HostConfigModifier: func(hostConfig *container.HostConfig) { - hostConfig.Binds = []string{core.ExtractDockerSocket(context.Background()) + ":/var/run/docker.sock"} + hostConfig.Binds = []string{core.MustExtractDockerSocket(context.Background()) + ":/var/run/docker.sock"} }, WaitingFor: wait.ForListeningPort(nat.Port("8080/tcp")), Env: map[string]string{ @@ -102,24 +84,29 @@ func createContainerRequest(customize func(ContainerRequest) ContainerRequest) C req.Labels[core.LabelReaper] = "true" req.Labels[core.LabelRyuk] = "true" + delete(req.Labels, core.LabelReap) - if customize == nil { - return req + for _, customize := range customize { + customize(&req) } - return customize(req) + return req } -func TestContainerStartsWithoutTheReaper(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if !tcConfig.RyukDisabled { - t.Skip("Ryuk is enabled, skipping test") - } +// reaperDisable disables / enables the reaper for the duration of the test. +func reaperDisable(t *testing.T, disabled bool) { + t.Helper() + + config.Reset() + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", strconv.FormatBool(disabled)) + t.Cleanup(config.Reset) +} +func testContainerStart(t *testing.T) { + t.Helper() ctx := context.Background() - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ Image: nginxAlpineImage, @@ -129,62 +116,57 @@ func TestContainerStartsWithoutTheReaper(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) +} - sessionID := core.SessionID() +// testReaperRunning validates that a reaper is running. +func testReaperRunning(t *testing.T) { + t.Helper() - reaperContainer, err := lookUpReaperContainer(ctx, sessionID) - if err != nil { - t.Fatal(err, "expected reaper container not found.") - } - if reaperContainer != nil { - t.Fatal("expected zero reaper running.") - } + ctx := context.Background() + sessionID := core.SessionID() + reaperContainer, err := spawner.lookupContainer(ctx, sessionID) + require.NoError(t, err) + require.NotNil(t, reaperContainer) } -func TestContainerStartsWithTheReaper(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } +func TestContainer(t *testing.T) { + reaperDisable(t, false) - ctx := context.Background() + t.Run("start/reaper-enabled", func(t *testing.T) { + testContainerStart(t) + testReaperRunning(t) + }) - c, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, - ContainerRequest: ContainerRequest{ - Image: nginxAlpineImage, - ExposedPorts: []string{ - nginxDefaultPort, - }, - }, - Started: true, + t.Run("stop/reaper-enabled", func(t *testing.T) { + testContainerStop(t) + testReaperRunning(t) }) - if err != nil { - t.Fatal(err) - } - terminateContainerOnEnd(t, ctx, c) - sessionID := core.SessionID() + t.Run("terminate/reaper-enabled", func(t *testing.T) { + testContainerTerminate(t) + testReaperRunning(t) + }) - reaperContainer, err := lookUpReaperContainer(ctx, sessionID) - if err != nil { - t.Fatal(err, "expected reaper container running.") - } - if reaperContainer == nil { - t.Fatal("expected one reaper to be running.") - } + reaperDisable(t, true) + + t.Run("start/reaper-disabled", func(t *testing.T) { + testContainerStart(t) + }) + + t.Run("stop/reaper-disabled", func(t *testing.T) { + testContainerStop(t) + }) + + t.Run("terminate/reaper-disabled", func(t *testing.T) { + testContainerTerminate(t) + }) } -func TestContainerStopWithReaper(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } +// testContainerStop tests stopping a container. +func testContainerStop(t *testing.T) { + t.Helper() ctx := context.Background() @@ -198,42 +180,26 @@ func TestContainerStopWithReaper(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) state, err := nginxA.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.Running != true { - t.Fatal("The container shoud be in running state") - } + require.NoError(t, err) + require.True(t, state.Running) + stopTimeout := 10 * time.Second err = nginxA.Stop(ctx, &stopTimeout) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err = nginxA.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.Running != false { - t.Fatal("The container shoud not be running") - } - if state.Status != "exited" { - t.Fatal("The container shoud be in exited state") - } + require.NoError(t, err) + require.False(t, state.Running) + require.Equal(t, "exited", state.Status) } -func TestContainerTerminationWithReaper(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } - +// testContainerTerminate tests terminating a container. +func testContainerTerminate(t *testing.T) { + t.Helper() ctx := context.Background() nginxA, err := GenericContainer(ctx, GenericContainerRequest{ @@ -246,338 +212,288 @@ func TestContainerTerminationWithReaper(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginxA) + require.NoError(t, err) state, err := nginxA.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.Running != true { - t.Fatal("The container shoud be in running state") - } - err = nginxA.Terminate(ctx) - if err != nil { - t.Fatal(err) - } - _, err = nginxA.State(ctx) - if err == nil { - t.Fatal("expected error from container inspect.") - } -} - -func TestContainerTerminationWithoutReaper(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if !tcConfig.RyukDisabled { - t.Skip("Ryuk is enabled, skipping test") - } - - ctx := context.Background() - - nginxA, err := GenericContainer(ctx, GenericContainerRequest{ - ProviderType: providerType, - ContainerRequest: ContainerRequest{ - Image: nginxAlpineImage, - ExposedPorts: []string{ - nginxDefaultPort, - }, - }, - Started: true, - }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + require.True(t, state.Running) - state, err := nginxA.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.Running != true { - t.Fatal("The container shoud be in running state") - } err = nginxA.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = nginxA.State(ctx) - if err == nil { - t.Fatal("expected error from container inspect.") - } + require.Error(t, err) } func Test_NewReaper(t *testing.T) { - type cases struct { - name string - req ContainerRequest - config TestcontainersConfig - ctx context.Context - env map[string]string - } + reaperDisable(t, false) - tests := []cases{ - { - name: "non-privileged", - req: createContainerRequest(nil), - config: TestcontainersConfig{Config: config.Config{ + ctx := context.Background() + + t.Run("non-privileged", func(t *testing.T) { + testNewReaper(ctx, t, + config.Config{ RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, - }}, - }, - { - name: "privileged", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { - req.Privileged = true - return req - }), - config: TestcontainersConfig{Config: config.Config{ + }, + expectedReaperRequest(), + ) + }) + + t.Run("privileged", func(t *testing.T) { + testNewReaper(ctx, t, + config.Config{ RyukPrivileged: true, RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, - }}, - }, - { - name: "configured non-default timeouts", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { - req.Env = map[string]string{ - "RYUK_CONNECTION_TIMEOUT": "1m0s", - "RYUK_RECONNECTION_TIMEOUT": "10m0s", - } - return req - }), - config: TestcontainersConfig{Config: config.Config{ + }, + expectedReaperRequest(), + ) + }) + + t.Run("custom-timeouts", func(t *testing.T) { + testNewReaper(ctx, t, + config.Config{ RyukPrivileged: true, - RyukConnectionTimeout: time.Minute, - RyukReconnectionTimeout: 10 * time.Minute, - }}, - }, - { - name: "configured verbose mode", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { + RyukConnectionTimeout: 2 * time.Minute, + RyukReconnectionTimeout: 20 * time.Second, + }, + expectedReaperRequest(func(req *ContainerRequest) { req.Env = map[string]string{ - "RYUK_VERBOSE": "true", + "RYUK_CONNECTION_TIMEOUT": "2m0s", + "RYUK_RECONNECTION_TIMEOUT": "20s", } - return req }), - config: TestcontainersConfig{Config: config.Config{ + ) + }) + + t.Run("verbose", func(t *testing.T) { + testNewReaper(ctx, t, + config.Config{ RyukPrivileged: true, RyukVerbose: true, - }}, - }, - { - name: "docker-host in context", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { - req.HostConfigModifier = func(hostConfig *container.HostConfig) { - hostConfig.Binds = []string{core.ExtractDockerSocket(context.Background()) + ":/var/run/docker.sock"} + }, + expectedReaperRequest(func(req *ContainerRequest) { + req.Env = map[string]string{ + "RYUK_VERBOSE": "true", } - return req }), - config: TestcontainersConfig{Config: config.Config{ + ) + }) + + t.Run("docker-host", func(t *testing.T) { + testNewReaper(context.WithValue(ctx, core.DockerHostContextKey, core.DockerSocketPathWithSchema), t, + config.Config{ RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, - }}, - ctx: context.WithValue(context.TODO(), core.DockerHostContextKey, core.DockerSocketPathWithSchema), - }, - { - name: "Reaper including custom Hub prefix", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { - req.Image = config.ReaperDefaultImage - req.Privileged = true - return req + }, + expectedReaperRequest(func(req *ContainerRequest) { + req.HostConfigModifier = func(hostConfig *container.HostConfig) { + hostConfig.Binds = []string{core.MustExtractDockerSocket(ctx) + ":/var/run/docker.sock"} + } }), - config: TestcontainersConfig{Config: config.Config{ + ) + }) + + t.Run("hub-prefix", func(t *testing.T) { + testNewReaper(context.WithValue(ctx, core.DockerHostContextKey, core.DockerSocketPathWithSchema), t, + config.Config{ HubImageNamePrefix: "registry.mycompany.com/mirror", RyukPrivileged: true, RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, - }}, - }, - { - name: "Reaper including custom Hub prefix as env var", - req: createContainerRequest(func(req ContainerRequest) ContainerRequest { + }, + expectedReaperRequest(func(req *ContainerRequest) { req.Image = config.ReaperDefaultImage req.Privileged = true - return req }), - config: TestcontainersConfig{Config: config.Config{ + ) + }) + + t.Run("hub-prefix-env", func(t *testing.T) { + config.Reset() + t.Cleanup(config.Reset) + + t.Setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "registry.mycompany.com/mirror") + testNewReaper(context.WithValue(ctx, core.DockerHostContextKey, core.DockerSocketPathWithSchema), t, + config.Config{ RyukPrivileged: true, RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, - }}, - env: map[string]string{ - "TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX": "registry.mycompany.com/mirror", }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if test.env != nil { - config.Reset() // reset the config using the internal method to avoid the sync.Once - for k, v := range test.env { - t.Setenv(k, v) - } - } + expectedReaperRequest(func(req *ContainerRequest) { + req.Image = config.ReaperDefaultImage + req.Privileged = true + }), + ) + }) +} - if prefix := os.Getenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX"); prefix != "" { - test.config.Config.HubImageNamePrefix = prefix - } +func testNewReaper(ctx context.Context, t *testing.T, cfg config.Config, expected ContainerRequest) { + t.Helper() - provider := newMockReaperProvider(t) - provider.config = test.config - t.Cleanup(provider.RestoreReaperState) + if prefix := os.Getenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX"); prefix != "" { + cfg.HubImageNamePrefix = prefix + } - if test.ctx == nil { - test.ctx = context.TODO() - } + provider := newMockReaperProvider(cfg) - _, err := reuseOrCreateReaper(test.ctx, testSessionID, provider) - // we should have errored out see mockReaperProvider.RunContainer - require.EqualError(t, err, "expected") + // We need a new reaperSpawner for each test case to avoid reusing + // an existing reaper instance. + spawner := &reaperSpawner{} + reaper, err := spawner.reaper(ctx, testSessionID, provider) + cleanupReaper(t, reaper, spawner) + // We should have errored out see mockReaperProvider.RunContainer. + require.ErrorIs(t, err, errExpected) - assert.Equal(t, test.req.Image, provider.req.Image, "expected image doesn't match the submitted request") - assert.Equal(t, test.req.ExposedPorts, provider.req.ExposedPorts, "expected exposed ports don't match the submitted request") - assert.Equal(t, test.req.Labels, provider.req.Labels, "expected labels don't match the submitted request") - assert.Equal(t, test.req.Mounts, provider.req.Mounts, "expected mounts don't match the submitted request") - assert.Equal(t, test.req.WaitingFor, provider.req.WaitingFor, "expected waitingFor don't match the submitted request") - assert.Equal(t, test.req.Env, provider.req.Env, "expected env doesn't match the submitted request") + require.Equal(t, expected.Image, provider.req.Image, "expected image doesn't match the submitted request") + require.Equal(t, expected.ExposedPorts, provider.req.ExposedPorts, "expected exposed ports don't match the submitted request") + require.Equal(t, expected.Labels, provider.req.Labels, "expected labels don't match the submitted request") + require.Equal(t, expected.Mounts, provider.req.Mounts, "expected mounts don't match the submitted request") + require.Equal(t, expected.WaitingFor, provider.req.WaitingFor, "expected waitingFor don't match the submitted request") + require.Equal(t, expected.Env, provider.req.Env, "expected env doesn't match the submitted request") - // checks for reaper's preCreationCallback fields - assert.Equal(t, container.NetworkMode(Bridge), provider.hostConfig.NetworkMode, "expected networkMode doesn't match the submitted request") - assert.True(t, provider.hostConfig.AutoRemove, "expected networkMode doesn't match the submitted request") - }) - } + // checks for reaper's preCreationCallback fields + require.Equal(t, container.NetworkMode(Bridge), provider.hostConfig.NetworkMode, "expected networkMode doesn't match the submitted request") + require.True(t, provider.hostConfig.AutoRemove, "expected networkMode doesn't match the submitted request") } func Test_ReaperReusedIfHealthy(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } - - testProvider := newMockReaperProvider(t) - t.Cleanup(testProvider.RestoreReaperState) + reaperDisable(t, false) - SkipIfProviderIsNotHealthy(&testing.T{}) + SkipIfProviderIsNotHealthy(t) ctx := context.Background() // As other integration tests run with the (shared) Reaper as well, re-use the instance to not interrupt other tests - wasReaperRunning := reaperInstance != nil + if spawner.instance != nil { + t.Cleanup(func() { + require.NoError(t, spawner.cleanup()) + }) + } + + provider, err := ProviderDocker.GetProvider() + require.NoError(t, err) - provider, _ := ProviderDocker.GetProvider() - reaper, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, reaper, spawner) require.NoError(t, err, "creating the Reaper should not error") - reaperReused, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + reaperReused, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, reaper, spawner) require.NoError(t, err, "reusing the Reaper should not error") - // assert that the internal state of both reaper instances is the same - assert.Equal(t, reaper.SessionID, reaperReused.SessionID, "expecting the same SessionID") - assert.Equal(t, reaper.Endpoint, reaperReused.Endpoint, "expecting the same reaper endpoint") - assert.Equal(t, reaper.Provider, reaperReused.Provider, "expecting the same container provider") - assert.Equal(t, reaper.container.GetContainerID(), reaperReused.container.GetContainerID(), "expecting the same container ID") - assert.Equal(t, reaper.container.SessionID(), reaperReused.container.SessionID(), "expecting the same session ID") - - terminate, err := reaper.Connect() - defer func(term chan bool) { - term <- true - }(terminate) - require.NoError(t, err, "connecting to Reaper should be successful") - if !wasReaperRunning { - terminateContainerOnEnd(t, ctx, reaper.container) - } + // Ensure the internal state of both reaper instances is the same + require.Equal(t, reaper.SessionID, reaperReused.SessionID, "expecting the same SessionID") + require.Equal(t, reaper.Endpoint, reaperReused.Endpoint, "expecting the same reaper endpoint") + require.Equal(t, reaper.Provider, reaperReused.Provider, "expecting the same container provider") + require.Equal(t, reaper.container.GetContainerID(), reaperReused.container.GetContainerID(), "expecting the same container ID") + require.Equal(t, reaper.container.SessionID(), reaperReused.container.SessionID(), "expecting the same session ID") + + termSignal, err := reaper.Connect() + cleanupTermSignal(t, termSignal) + require.NoError(t, err, "connecting to Reaper should be successful") } func Test_RecreateReaperIfTerminated(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } + reaperDisable(t, false) - mockProvider := newMockReaperProvider(t) - t.Cleanup(mockProvider.RestoreReaperState) + SkipIfProviderIsNotHealthy(t) - SkipIfProviderIsNotHealthy(&testing.T{}) + provider, err := ProviderDocker.GetProvider() + require.NoError(t, err) - provider, _ := ProviderDocker.GetProvider() ctx := context.Background() - reaper, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, reaper, spawner) require.NoError(t, err, "creating the Reaper should not error") - terminate, err := reaper.Connect() - require.NoError(t, err, "connecting to Reaper should be successful") - terminate <- true + termSignal, err := reaper.Connect() + if termSignal != nil { + termSignal <- true + } + require.NoError(t, err) + + // Wait for up to ryuk's default reconnect timeout + 1s to allow for a graceful shutdown/cleanup of the container. + timeout := time.NewTimer(time.Second * 11) + t.Cleanup(func() { + timeout.Stop() + }) + for { + state, err := reaper.container.State(ctx) + if err != nil { + if errdefs.IsNotFound(err) { + break + } + require.NoError(t, err) + } + + if !state.Running { + break + } + + select { + case <-timeout.C: + t.Fatal("reaper container should have been terminated") + default: + } - // Wait for ryuk's default timeout (10s) + 1s to allow for a graceful shutdown/cleanup of the container. - time.Sleep(11 * time.Second) + time.Sleep(time.Millisecond * 100) + } - recreatedReaper, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + recreatedReaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, recreatedReaper, spawner) require.NoError(t, err, "creating the Reaper should not error") - assert.NotEqual(t, reaper.container.GetContainerID(), recreatedReaper.container.GetContainerID(), "expected different container ID") + require.NotEqual(t, reaper.container.GetContainerID(), recreatedReaper.container.GetContainerID(), "expected different container ID") - terminate, err = recreatedReaper.Connect() - defer func(term chan bool) { - term <- true - }(terminate) + recreatedTermSignal, err := recreatedReaper.Connect() + cleanupTermSignal(t, recreatedTermSignal) require.NoError(t, err, "connecting to Reaper should be successful") - terminateContainerOnEnd(t, ctx, recreatedReaper.container) } func TestReaper_reuseItFromOtherTestProgramUsingDocker(t *testing.T) { - config.Reset() // reset the config using the internal method to avoid the sync.Once - tcConfig := config.Read() - if tcConfig.RyukDisabled { - t.Skip("Ryuk is disabled, skipping test") - } - - mockProvider := &mockReaperProvider{ - initialReaper: reaperInstance, - //nolint:govet - initialReaperOnce: reaperOnce, - t: t, - } - t.Cleanup(mockProvider.RestoreReaperState) + reaperDisable(t, false) - // explicitly set the reaperInstance to nil to simulate another test program in the same session accessing the same reaper - reaperInstance = nil - reaperOnce = sync.Once{} + // Explicitly set the reaper instance to nil to simulate another test + // program in the same session accessing the same reaper. + spawner.instance = nil - SkipIfProviderIsNotHealthy(&testing.T{}) + SkipIfProviderIsNotHealthy(t) ctx := context.Background() - // As other integration tests run with the (shared) Reaper as well, re-use the instance to not interrupt other tests - wasReaperRunning := reaperInstance != nil + // As other integration tests run with the (shared) Reaper as well, + // re-use the instance to not interrupt other tests. + if spawner.instance != nil { + t.Cleanup(func() { + require.NoError(t, spawner.cleanup()) + }) + } + + provider, err := ProviderDocker.GetProvider() + require.NoError(t, err) - provider, _ := ProviderDocker.GetProvider() - reaper, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + reaper, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, reaper, spawner) require.NoError(t, err, "creating the Reaper should not error") - // explicitly reset the reaperInstance to nil to simulate another test program in the same session accessing the same reaper - reaperInstance = nil - reaperOnce = sync.Once{} + // Explicitly reset the reaper instance to nil to simulate another test + // program in the same session accessing the same reaper. + spawner.instance = nil - reaperReused, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + reaperReused, err := spawner.reaper(context.WithValue(ctx, core.DockerHostContextKey, provider.(*DockerProvider).host), testSessionID, provider) + cleanupReaper(t, reaper, spawner) require.NoError(t, err, "reusing the Reaper should not error") - // assert that the internal state of both reaper instances is the same - assert.Equal(t, reaper.SessionID, reaperReused.SessionID, "expecting the same SessionID") - assert.Equal(t, reaper.Endpoint, reaperReused.Endpoint, "expecting the same reaper endpoint") - assert.Equal(t, reaper.Provider, reaperReused.Provider, "expecting the same container provider") - assert.Equal(t, reaper.container.GetContainerID(), reaperReused.container.GetContainerID(), "expecting the same container ID") - assert.Equal(t, reaper.container.SessionID(), reaperReused.container.SessionID(), "expecting the same session ID") - - terminate, err := reaper.Connect() - defer func(term chan bool) { - term <- true - }(terminate) - require.NoError(t, err, "connecting to Reaper should be successful") - if !wasReaperRunning { - terminateContainerOnEnd(t, ctx, reaper.container) - } + // Ensure that the internal state of both reaper instances is the same. + require.Equal(t, reaper.SessionID, reaperReused.SessionID, "expecting the same SessionID") + require.Equal(t, reaper.Endpoint, reaperReused.Endpoint, "expecting the same reaper endpoint") + require.Equal(t, reaper.Provider, reaperReused.Provider, "expecting the same container provider") + require.Equal(t, reaper.container.GetContainerID(), reaperReused.container.GetContainerID(), "expecting the same container ID") + require.Equal(t, reaper.container.SessionID(), reaperReused.container.SessionID(), "expecting the same session ID") + + termSignal, err := reaper.Connect() + cleanupTermSignal(t, termSignal) + require.NoError(t, err, "connecting to Reaper should be successful") } // TestReaper_ReuseRunning tests whether reusing the reaper if using @@ -588,9 +504,11 @@ func TestReaper_reuseItFromOtherTestProgramUsingDocker(t *testing.T) { // already running for the same session id by returning its container instance // instead. func TestReaper_ReuseRunning(t *testing.T) { + reaperDisable(t, false) + const concurrency = 64 - timeout, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + timeout, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() sessionID := SessionID() @@ -601,27 +519,54 @@ func TestReaper_ReuseRunning(t *testing.T) { obtainedReaperContainerIDs := make([]string, concurrency) var wg sync.WaitGroup for i := 0; i < concurrency; i++ { - i := i wg.Add(1) - go func() { + go func(i int) { defer wg.Done() - reaperContainer, err := lookUpReaperContainer(timeout, sessionID) - if err == nil && reaperContainer != nil { - // Found. - obtainedReaperContainerIDs[i] = reaperContainer.GetContainerID() - return - } - // Not found -> create. - createdReaper, err := newReaper(timeout, sessionID, dockerProvider) - require.NoError(t, err, "new reaper should not fail") - obtainedReaperContainerIDs[i] = createdReaper.container.GetContainerID() - }() + spawner := &reaperSpawner{} + reaper, err := spawner.reaper(timeout, sessionID, dockerProvider) + cleanupReaper(t, reaper, spawner) + require.NoError(t, err) + + obtainedReaperContainerIDs[i] = reaper.container.GetContainerID() + }(i) } wg.Wait() // Assure that all calls returned the same container. firstContainerID := obtainedReaperContainerIDs[0] for i, containerID := range obtainedReaperContainerIDs { - assert.Equal(t, firstContainerID, containerID, "call %d should have returned same container id", i) + require.Equal(t, firstContainerID, containerID, "call %d should have returned same container id", i) } } + +func TestSpawnerBackoff(t *testing.T) { + b := spawner.backoff() + for i := 0; i < 100; i++ { + require.LessOrEqual(t, b.NextBackOff(), time.Millisecond*250, "backoff should not exceed max interval") + } +} + +// cleanupReaper schedules reaper for cleanup if it's not nil. +func cleanupReaper(t *testing.T, reaper *Reaper, spawner *reaperSpawner) { + t.Helper() + + if reaper == nil { + return + } + + t.Cleanup(func() { + reaper.close() + require.NoError(t, spawner.cleanup()) + }) +} + +// cleanupTermSignal ensures that termSignal +func cleanupTermSignal(t *testing.T, termSignal chan bool) { + t.Helper() + + t.Cleanup(func() { + if termSignal != nil { + termSignal <- true + } + }) +} diff --git a/requirements.txt b/requirements.txt index 8959e8fbcd..e4db8827e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -mkdocs==1.5.2 +mkdocs==1.5.3 mkdocs-codeinclude-plugin==0.2.1 -mkdocs-include-markdown-plugin==6.0.4 -mkdocs-material==8.2.7 -mkdocs-markdownextradata-plugin==0.2.5 +mkdocs-include-markdown-plugin==6.2.2 +mkdocs-material==9.5.18 +mkdocs-markdownextradata-plugin==0.2.6 diff --git a/scripts/bump-go.sh b/scripts/bump-go.sh index 7b949ee89b..5ff33a5e6e 100755 --- a/scripts/bump-go.sh +++ b/scripts/bump-go.sh @@ -21,7 +21,7 @@ readonly CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly DRY_RUN="${DRY_RUN:-true}" readonly ROOT_DIR="$(dirname "$CURRENT_DIR")" readonly GO_MOD_FILE="${ROOT_DIR}/go.mod" -readonly DEVCONTAINER_IMAGE_PREFIX="go:0-" +readonly DEVCONTAINER_IMAGE_PREFIX="go:" function main() { echo "Updating Go version:" diff --git a/scripts/bump-reaper.sh b/scripts/bump-reaper.sh index 4e1211a1b3..86af6b31c0 100755 --- a/scripts/bump-reaper.sh +++ b/scripts/bump-reaper.sh @@ -5,11 +5,11 @@ # dry-run mode, which will print the commands that would be executed, without actually # executing them. # -# Usage: ./scripts/bump-reaper.sh "docker.io/testcontainers/ryuk:1.2.3" +# Usage: ./scripts/bump-reaper.sh "testcontainers/ryuk:1.2.3" # # It's possible to run the script without dry-run mode actually executing the commands. # -# Usage: DRY_RUN="false" ./scripts/bump-reaper.sh "docker.io/testcontainers/ryuk:1.2.3" +# Usage: DRY_RUN="false" ./scripts/bump-reaper.sh "testcontainers/ryuk:1.2.3" readonly CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly DRY_RUN="${DRY_RUN:-true}" @@ -39,7 +39,7 @@ function main() { # This function reads the reaper.go file and extracts the current version. function extractCurrentVersion() { - cat "${REAPER_FILE}" | grep 'ReaperDefaultImage = ' | cut -d '=' -f 2 | cut -d '"' -f 1 + cat "${REAPER_FILE}" | grep 'ReaperDefaultImage = ' | cut -d '=' -f 2 } main "$@" diff --git a/scripts/check_environment.sh b/scripts/check_environment.sh index 6dc4c869ba..2a04402b41 100755 --- a/scripts/check_environment.sh +++ b/scripts/check_environment.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash i=0 -while [ $i -ne 10 ] +while [ $i -ne 30 ] do containers=$(docker ps --format "{{.ID}}" | wc -l) if [ "$containers" -eq "0" ]; then diff --git a/scripts/release.sh b/scripts/release.sh index 6709c5c358..b675cb51e2 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -13,7 +13,7 @@ # Usage: DRY_RUN="false" ./scripts/release.sh readonly BUMP_TYPE="${BUMP_TYPE:-minor}" -readonly DOCKER_IMAGE_SEMVER="docker.io/mdelapenya/semver-tool:3.4.0" +readonly DOCKER_IMAGE_SEMVER="mdelapenya/semver-tool:3.4.0" readonly DRY_RUN="${DRY_RUN:-true}" readonly CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" readonly ROOT_DIR="$(dirname "$CURRENT_DIR")" diff --git a/sonar-project.properties b/sonar-project.properties index dcef78cc69..67ef15fcd5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,7 +7,7 @@ sonar.projectKey=testcontainers_testcontainers-go sonar.projectName=testcontainers-go -sonar.projectVersion=v0.30.0 +sonar.projectVersion=v0.34.0 sonar.sources=. @@ -18,4 +18,4 @@ sonar.test.inclusions=**/*_test.go sonar.test.exclusions=**/vendor/** sonar.go.coverage.reportPaths=**/coverage.out -sonar.go.tests.reportPaths=TEST-unit.xml,examples/nginx/TEST-unit.xml,examples/toxiproxy/TEST-unit.xml,modulegen/TEST-unit.xml,modules/artemis/TEST-unit.xml,modules/cassandra/TEST-unit.xml,modules/chroma/TEST-unit.xml,modules/clickhouse/TEST-unit.xml,modules/cockroachdb/TEST-unit.xml,modules/compose/TEST-unit.xml,modules/consul/TEST-unit.xml,modules/couchbase/TEST-unit.xml,modules/dolt/TEST-unit.xml,modules/elasticsearch/TEST-unit.xml,modules/gcloud/TEST-unit.xml,modules/inbucket/TEST-unit.xml,modules/influxdb/TEST-unit.xml,modules/k3s/TEST-unit.xml,modules/k6/TEST-unit.xml,modules/kafka/TEST-unit.xml,modules/localstack/TEST-unit.xml,modules/mariadb/TEST-unit.xml,modules/milvus/TEST-unit.xml,modules/minio/TEST-unit.xml,modules/mockserver/TEST-unit.xml,modules/mongodb/TEST-unit.xml,modules/mssql/TEST-unit.xml,modules/mysql/TEST-unit.xml,modules/nats/TEST-unit.xml,modules/neo4j/TEST-unit.xml,modules/ollama/TEST-unit.xml,modules/openfga/TEST-unit.xml,modules/openldap/TEST-unit.xml,modules/opensearch/TEST-unit.xml,modules/postgres/TEST-unit.xml,modules/pulsar/TEST-unit.xml,modules/qdrant/TEST-unit.xml,modules/rabbitmq/TEST-unit.xml,modules/redis/TEST-unit.xml,modules/redpanda/TEST-unit.xml,modules/registry/TEST-unit.xml,modules/surrealdb/TEST-unit.xml,modules/vault/TEST-unit.xml,modules/weaviate/TEST-unit.xml +sonar.go.tests.reportPaths=TEST-unit.xml,examples/nginx/TEST-unit.xml,examples/toxiproxy/TEST-unit.xml,modulegen/TEST-unit.xml,modules/artemis/TEST-unit.xml,modules/azurite/TEST-unit.xml,modules/cassandra/TEST-unit.xml,modules/chroma/TEST-unit.xml,modules/clickhouse/TEST-unit.xml,modules/cockroachdb/TEST-unit.xml,modules/compose/TEST-unit.xml,modules/consul/TEST-unit.xml,modules/couchbase/TEST-unit.xml,modules/databend/TEST-unit.xml,modules/dolt/TEST-unit.xml,modules/dynamodb/TEST-unit.xml,modules/elasticsearch/TEST-unit.xml,modules/etcd/TEST-unit.xml,modules/gcloud/TEST-unit.xml,modules/grafana-lgtm/TEST-unit.xml,modules/inbucket/TEST-unit.xml,modules/influxdb/TEST-unit.xml,modules/k3s/TEST-unit.xml,modules/k6/TEST-unit.xml,modules/kafka/TEST-unit.xml,modules/localstack/TEST-unit.xml,modules/mariadb/TEST-unit.xml,modules/meilisearch/TEST-unit.xml,modules/milvus/TEST-unit.xml,modules/minio/TEST-unit.xml,modules/mockserver/TEST-unit.xml,modules/mongodb/TEST-unit.xml,modules/mssql/TEST-unit.xml,modules/mysql/TEST-unit.xml,modules/nats/TEST-unit.xml,modules/neo4j/TEST-unit.xml,modules/ollama/TEST-unit.xml,modules/openfga/TEST-unit.xml,modules/openldap/TEST-unit.xml,modules/opensearch/TEST-unit.xml,modules/postgres/TEST-unit.xml,modules/pulsar/TEST-unit.xml,modules/qdrant/TEST-unit.xml,modules/rabbitmq/TEST-unit.xml,modules/redis/TEST-unit.xml,modules/redpanda/TEST-unit.xml,modules/registry/TEST-unit.xml,modules/surrealdb/TEST-unit.xml,modules/valkey/TEST-unit.xml,modules/vault/TEST-unit.xml,modules/vearch/TEST-unit.xml,modules/weaviate/TEST-unit.xml,modules/yugabytedb/TEST-unit.xml diff --git a/testcontainers.go b/testcontainers.go index 5b52e09a22..7ae4a40c14 100644 --- a/testcontainers.go +++ b/testcontainers.go @@ -6,7 +6,12 @@ import ( "github.com/testcontainers/testcontainers-go/internal/core" ) -// ExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema. +// Deprecated: use MustExtractDockerHost instead. +func ExtractDockerSocket() string { + return MustExtractDockerSocket(context.Background()) +} + +// MustExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema. // Use this function to get the docker socket path, not the host (e.g. mounting the socket in a container). // This function does not consider Windows containers at the moment. // The possible alternatives are: @@ -14,13 +19,13 @@ import ( // 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file. // 2. The TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable. // 3. Using a Docker client, check if the Info().OperativeSystem is "Docker Desktop" and return the default docker socket path for rootless docker. -// 4. Else, Get the current Docker Host from the existing strategies: see ExtractDockerHost. +// 4. Else, Get the current Docker Host from the existing strategies: see MustExtractDockerHost. // 5. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock) // 6. Else, the default location of the docker socket is used (/var/run/docker.sock) // -// In any case, if the docker socket schema is "tcp://", the default docker socket path will be returned. -func ExtractDockerSocket() string { - return core.ExtractDockerSocket(context.Background()) +// It panics if a Docker client cannot be created, or the Docker host cannot be discovered. +func MustExtractDockerSocket(ctx context.Context) string { + return core.MustExtractDockerSocket(ctx) } // SessionID returns a unique session ID for the current test session. Because each Go package diff --git a/testcontainers_test.go b/testcontainers_test.go index fe5af71e89..6c06d87483 100644 --- a/testcontainers_test.go +++ b/testcontainers_test.go @@ -1,27 +1,24 @@ package testcontainers import ( - "fmt" "os" "os/exec" "regexp" "testing" + + "github.com/stretchr/testify/require" ) func TestSessionID(t *testing.T) { t.Run("SessionID() returns a non-empty string", func(t *testing.T) { sessionID := SessionID() - if sessionID == "" { - t.Error("SessionID() returned an empty string") - } + require.NotEmptyf(t, sessionID, "SessionID() returned an empty string") }) t.Run("Multiple calls to SessionID() return the same value", func(t *testing.T) { sessionID1 := SessionID() sessionID2 := SessionID() - if sessionID1 != sessionID2 { - t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) - } + require.Equalf(t, sessionID1, sessionID2, "SessionID() returned different values: %s != %s", sessionID1, sessionID2) }) t.Run("Multiple calls to SessionID() in multiple goroutines return the same value", func(t *testing.T) { @@ -42,9 +39,7 @@ func TestSessionID(t *testing.T) { <-done <-done - if sessionID1 != sessionID2 { - t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) - } + require.Equalf(t, sessionID1, sessionID2, "SessionID() returned different values: %s != %s", sessionID1, sessionID2) }) t.Run("SessionID() from different child processes returns the same value", func(t *testing.T) { @@ -56,22 +51,16 @@ func TestSessionID(t *testing.T) { cmd1 := exec.Command("go", args...) cmd1.Env = env stdoutStderr1, err := cmd1.CombinedOutput() - if err != nil { - t.Errorf("cmd1.Run() failed with %s", err) - } + require.NoErrorf(t, err, "cmd1.Run() failed with %s", err) sessionID1 := re.FindString(string(stdoutStderr1)) cmd2 := exec.Command("go", args...) cmd2.Env = env stdoutStderr2, err := cmd2.CombinedOutput() - if err != nil { - t.Errorf("cmd2.Run() failed with %s", err) - } + require.NoErrorf(t, err, "cmd2.Run() failed with %s", err) sessionID2 := re.FindString(string(stdoutStderr2)) - if sessionID1 != sessionID2 { - t.Errorf("SessionID() returned different values: %s != %s", sessionID1, sessionID2) - } + require.Equalf(t, sessionID1, sessionID2, "SessionID() returned different values: %s != %s", sessionID1, sessionID2) }) } @@ -81,5 +70,5 @@ func TestSessionIDHelper(t *testing.T) { t.Skip("Not a real test, used as a test helper") } - fmt.Printf(">>>%s<<<\n", SessionID()) + t.Logf(">>>%s<<<\n", SessionID()) } diff --git a/testdata/Dockerfile b/testdata/Dockerfile index 7157611a13..14cfaf1e23 100644 --- a/testdata/Dockerfile +++ b/testdata/Dockerfile @@ -1 +1 @@ -FROM docker.io/redis:5.0-alpine@sha256:1a3c609295332f1ce603948142a132656c92a08149d7096e203058533c415b8c +FROM redis:5.0-alpine@sha256:1a3c609295332f1ce603948142a132656c92a08149d7096e203058533c415b8c diff --git a/testdata/args.Dockerfile b/testdata/args.Dockerfile index 984ef51eee..0260639719 100644 --- a/testdata/args.Dockerfile +++ b/testdata/args.Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.13-alpine +FROM golang:1.13-alpine ARG FOO diff --git a/testdata/auth.Dockerfile b/testdata/auth.Dockerfile index 1f57898cba..5fcebe0baf 100644 --- a/testdata/auth.Dockerfile +++ b/testdata/auth.Dockerfile @@ -1 +1,3 @@ -FROM localhost:5001/redis:5.0-alpine +ARG REGISTRY_HOST=localhost:5001 + +FROM ${REGISTRY_HOST}/redis:5.0-alpine diff --git a/testdata/buildlog.Dockerfile b/testdata/buildlog.Dockerfile index 0a9bc82c98..67fd379018 100644 --- a/testdata/buildlog.Dockerfile +++ b/testdata/buildlog.Dockerfile @@ -1 +1 @@ -FROM docker.io/alpine +FROM alpine diff --git a/testdata/echo.Dockerfile b/testdata/echo.Dockerfile index 10ab9febf4..36951e1aa6 100644 --- a/testdata/echo.Dockerfile +++ b/testdata/echo.Dockerfile @@ -1,3 +1,3 @@ -FROM docker.io/alpine +FROM alpine CMD ["echo", "this is from the echo test Dockerfile"] \ No newline at end of file diff --git a/testdata/echoserver.Dockerfile b/testdata/echoserver.Dockerfile index 546489ffac..aaf835f35a 100644 --- a/testdata/echoserver.Dockerfile +++ b/testdata/echoserver.Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.13-alpine +FROM golang:1.13-alpine WORKDIR /app diff --git a/testdata/echoserver.go b/testdata/echoserver.go index db5798699a..1222b7045f 100644 --- a/testdata/echoserver.go +++ b/testdata/echoserver.go @@ -10,9 +10,8 @@ import ( func envHandler() http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { - _, _ = rw.Write([]byte(os.Getenv("FOO"))) - rw.WriteHeader(http.StatusAccepted) + rw.Write([]byte(os.Getenv("FOO"))) //nolint:errcheck // Nothing we can usefully do with the error here. } } @@ -21,9 +20,7 @@ func echoHandler(destination *os.File) http.HandlerFunc { echo := req.URL.Query()["echo"][0] l := log.New(destination, "echo ", 0) - l.Println(echo) - _ = destination.Sync() rw.WriteHeader(http.StatusAccepted) } @@ -39,10 +36,13 @@ func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { - panic(err) + log.Println(err) + return } fmt.Println("ready") - _ = http.Serve(ln, mux) + if err := http.Serve(ln, mux); err != nil { + log.Fatal(err) + } } diff --git a/testdata/error.Dockerfile b/testdata/error.Dockerfile new file mode 100644 index 0000000000..5d31293182 --- /dev/null +++ b/testdata/error.Dockerfile @@ -0,0 +1,4 @@ +FROM alpine + +RUN exit 1 + diff --git a/testdata/invalid-config/.docker/config.json b/testdata/invalid-config/.docker/config.json new file mode 100644 index 0000000000..f0f444f355 --- /dev/null +++ b/testdata/invalid-config/.docker/config.json @@ -0,0 +1,3 @@ +{ + "auths": [] +} diff --git a/testdata/retry/Dockerfile b/testdata/retry/Dockerfile new file mode 100644 index 0000000000..c35f1b5f5e --- /dev/null +++ b/testdata/retry/Dockerfile @@ -0,0 +1 @@ +FROM scratch diff --git a/testdata/target.Dockerfile b/testdata/target.Dockerfile index 996a83552f..f6a20273c7 100644 --- a/testdata/target.Dockerfile +++ b/testdata/target.Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/alpine AS target0 +FROM alpine AS target0 CMD ["echo", "target0"] FROM target0 AS target1 diff --git a/testhelpers_test.go b/testhelpers_test.go index 47bbcb54c3..8d7587c17c 100644 --- a/testhelpers_test.go +++ b/testhelpers_test.go @@ -1,26 +1,6 @@ package testcontainers_test -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/testcontainers/testcontainers-go" -) - const ( - nginxAlpineImage = "docker.io/nginx:alpine" + nginxAlpineImage = "nginx:alpine" nginxDefaultPort = "80/tcp" ) - -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) { - tb.Helper() - if ctr == nil { - return - } - tb.Cleanup(func() { - tb.Log("terminating container") - require.NoError(tb, ctr.Terminate(ctx)) - }) -} diff --git a/testing.go b/testing.go index eab23cb805..35ce4f0a39 100644 --- a/testing.go +++ b/testing.go @@ -3,14 +3,24 @@ package testcontainers import ( "context" "fmt" + "io" + "regexp" "testing" + + "github.com/docker/docker/errdefs" + "github.com/stretchr/testify/require" ) +// errAlreadyInProgress is a regular expression that matches the error for a container +// removal that is already in progress. +var errAlreadyInProgress = regexp.MustCompile(`removal of container .* is already in progress`) + // SkipIfProviderIsNotHealthy is a utility function capable of skipping tests // if the provider is not healthy, or running at all. // This is a function designed to be used in your test, when Docker is not mandatory for CI/CD. // In this way tests that depend on Testcontainers won't run if the provider is provisioned correctly. func SkipIfProviderIsNotHealthy(t *testing.T) { + t.Helper() ctx := context.Background() provider, err := ProviderDocker.GetProvider() if err != nil { @@ -25,15 +35,12 @@ func SkipIfProviderIsNotHealthy(t *testing.T) { // SkipIfDockerDesktop is a utility function capable of skipping tests // if tests are run using Docker Desktop. func SkipIfDockerDesktop(t *testing.T, ctx context.Context) { + t.Helper() cli, err := NewDockerClientWithOpts(ctx) - if err != nil { - t.Fatalf("failed to create docker client: %s", err) - } + require.NoErrorf(t, err, "failed to create docker client: %s", err) info, err := cli.Info(ctx) - if err != nil { - t.Fatalf("failed to get docker info: %s", err) - } + require.NoErrorf(t, err, "failed to get docker info: %s", err) if info.OperatingSystem == "Docker Desktop" { t.Skip("Skipping test that requires host network access when running in Docker Desktop") @@ -51,3 +58,108 @@ func (lc *StdoutLogConsumer) Accept(l Log) { } // } + +// CleanupContainer is a helper function that schedules the container +// to be stopped / terminated when the test ends. +// +// This should be called as a defer directly after (before any error check) +// of [GenericContainer](...) or a modules Run(...) in a test to ensure the +// container is stopped when the function ends. +// +// before any error check. If container is nil, its a no-op. +func CleanupContainer(tb testing.TB, ctr Container, options ...TerminateOption) { + tb.Helper() + + tb.Cleanup(func() { + noErrorOrIgnored(tb, TerminateContainer(ctr, options...)) + }) +} + +// CleanupNetwork is a helper function that schedules the network to be +// removed when the test ends. +// This should be the first call after NewNetwork(...) in a test before +// any error check. If network is nil, its a no-op. +func CleanupNetwork(tb testing.TB, network Network) { + tb.Helper() + + tb.Cleanup(func() { + noErrorOrIgnored(tb, network.Remove(context.Background())) + }) +} + +// noErrorOrIgnored is a helper function that checks if the error is nil or an error +// we can ignore. +func noErrorOrIgnored(tb testing.TB, err error) { + tb.Helper() + + if isCleanupSafe(err) { + return + } + + require.NoError(tb, err) +} + +// causer is an interface that allows to get the cause of an error. +type causer interface { + Cause() error +} + +// wrapErr is an interface that allows to unwrap an error. +type wrapErr interface { + Unwrap() error +} + +// unwrapErrs is an interface that allows to unwrap multiple errors. +type unwrapErrs interface { + Unwrap() []error +} + +// isCleanupSafe reports whether all errors in err's tree are one of the +// following, so can safely be ignored: +// - nil +// - not found +// - already in progress +func isCleanupSafe(err error) bool { + if err == nil { + return true + } + + switch x := err.(type) { //nolint:errorlint // We need to check for interfaces. + case errdefs.ErrNotFound: + return true + case errdefs.ErrConflict: + // Terminating a container that is already terminating. + if errAlreadyInProgress.MatchString(err.Error()) { + return true + } + return false + case causer: + return isCleanupSafe(x.Cause()) + case wrapErr: + return isCleanupSafe(x.Unwrap()) + case unwrapErrs: + for _, e := range x.Unwrap() { + if !isCleanupSafe(e) { + return false + } + } + return true + default: + return false + } +} + +// RequireContainerExec is a helper function that executes a command in a container +// It insures that there is no error during the execution +// Finally returns the output of its execution +func RequireContainerExec(ctx context.Context, t *testing.T, container Container, cmd []string) string { + t.Helper() + + code, out, err := container.Exec(ctx, cmd) + require.NoError(t, err) + require.Zero(t, code) + + checkBytes, err := io.ReadAll(out) + require.NoError(t, err) + return string(checkBytes) +} diff --git a/testing_test.go b/testing_test.go index 56817d655a..6c50738220 100644 --- a/testing_test.go +++ b/testing_test.go @@ -1,7 +1,86 @@ package testcontainers -import "testing" +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) func ExampleSkipIfProviderIsNotHealthy() { SkipIfProviderIsNotHealthy(&testing.T{}) } + +type notFoundError struct{} + +func (notFoundError) NotFound() {} + +func (notFoundError) Error() string { + return "not found" +} + +func Test_isNotFound(t *testing.T) { + tests := map[string]struct { + err error + want bool + }{ + "nil": { + err: nil, + want: true, + }, + "join-nils": { + err: errors.Join(nil, nil), + want: true, + }, + "join-nil-not-found": { + err: errors.Join(nil, notFoundError{}), + want: true, + }, + "not-found": { + err: notFoundError{}, + want: true, + }, + "other": { + err: errors.New("other"), + want: false, + }, + "join-other": { + err: errors.Join(nil, notFoundError{}, errors.New("other")), + want: false, + }, + "warp": { + err: fmt.Errorf("wrap: %w", notFoundError{}), + want: true, + }, + "multi-warp": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w", notFoundError{})), + want: true, + }, + "multi-warp-other": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w", errors.New("other"))), + want: false, + }, + "multi-warp-other-not-found": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w %w", errors.New("other"), notFoundError{})), + want: false, + }, + "multi-warp-not-found-nil": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w %w", nil, notFoundError{})), + want: true, + }, + "multi-join-not-found-other": { + err: errors.Join(nil, fmt.Errorf("wrap: %w", errors.Join(notFoundError{}, errors.New("other")))), + want: false, + }, + "multi-join-not-found-nil": { + err: errors.Join(nil, fmt.Errorf("wrap: %w", errors.Join(notFoundError{}, nil))), + want: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.want, isCleanupSafe(tc.err)) + }) + } +} diff --git a/wait/all.go b/wait/all.go index fb097fb5ea..fb7eb4e5f3 100644 --- a/wait/all.go +++ b/wait/all.go @@ -2,7 +2,7 @@ package wait import ( "context" - "fmt" + "errors" "time" ) @@ -58,7 +58,7 @@ func (ms *MultiStrategy) WaitUntilReady(ctx context.Context, target StrategyTarg } if len(ms.Strategies) == 0 { - return fmt.Errorf("no wait strategy supplied") + return errors.New("no wait strategy supplied") } for _, strategy := range ms.Strategies { diff --git a/wait/all_test.go b/wait/all_test.go index 770a54f32c..87b00bb5ee 100644 --- a/wait/all_test.go +++ b/wait/all_test.go @@ -7,6 +7,8 @@ import ( "io" "testing" "time" + + "github.com/stretchr/testify/require" ) func TestMultiStrategy_WaitUntilReady(t *testing.T) { @@ -113,8 +115,11 @@ func TestMultiStrategy_WaitUntilReady(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - if err := tt.strategy.WaitUntilReady(tt.args.ctx, tt.args.target); (err != nil) != tt.wantErr { - t.Errorf("ForAll.WaitUntilReady() error = %v, wantErr = %v", err, tt.wantErr) + err := tt.strategy.WaitUntilReady(tt.args.ctx, tt.args.target) + if tt.wantErr { + require.Error(t, err, "ForAll.WaitUntilReady()") + } else { + require.NoErrorf(t, err, "ForAll.WaitUntilReady()") } }) } diff --git a/wait/exec_test.go b/wait/exec_test.go index 132933018f..a431b146da 100644 --- a/wait/exec_test.go +++ b/wait/exec_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" tcexec "github.com/testcontainers/testcontainers-go/exec" @@ -21,27 +22,29 @@ import ( func ExampleExecStrategy() { ctx := context.Background() req := testcontainers.ContainerRequest{ - Image: "localstack/localstack:latest", - WaitingFor: wait.ForExec([]string{"awslocal", "dynamodb", "list-tables"}), + Image: "alpine:latest", + Entrypoint: []string{"tail", "-f", "/dev/null"}, // needed for the container to stay alive + WaitingFor: wait.ForExec([]string{"ls", "/"}).WithStartupTimeout(1 * time.Second), } - localstack, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := localstack.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - state, err := localstack.State(ctx) + state, err := ctr.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -62,6 +65,11 @@ func (st mockExecTarget) Host(_ context.Context) (string, error) { return "", errors.New("not implemented") } +func (st mockExecTarget) Inspect(ctx context.Context) (*types.ContainerJSON, error) { + return nil, errors.New("not implemented") +} + +// Deprecated: use Inspect instead func (st mockExecTarget) Ports(ctx context.Context) (nat.PortMap, error) { return nil, errors.New("not implemented") } @@ -97,23 +105,23 @@ func (st mockExecTarget) State(_ context.Context) (*types.ContainerState, error) return nil, errors.New("not implemented") } +func (st mockExecTarget) CopyFileFromContainer(_ context.Context, _ string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") +} + func TestExecStrategyWaitUntilReady(t *testing.T) { target := mockExecTarget{} wg := wait.NewExecStrategy([]string{"true"}). WithStartupTimeout(30 * time.Second) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestExecStrategyWaitUntilReadyForExec(t *testing.T) { target := mockExecTarget{} wg := wait.ForExec([]string{"true"}) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestExecStrategyWaitUntilReady_MultipleChecks(t *testing.T) { @@ -124,9 +132,7 @@ func TestExecStrategyWaitUntilReady_MultipleChecks(t *testing.T) { wg := wait.NewExecStrategy([]string{"true"}). WithPollInterval(500 * time.Millisecond) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestExecStrategyWaitUntilReady_DeadlineExceeded(t *testing.T) { @@ -138,9 +144,7 @@ func TestExecStrategyWaitUntilReady_DeadlineExceeded(t *testing.T) { } wg := wait.NewExecStrategy([]string{"true"}) err := wg.WaitUntilReady(ctx, target) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatal(err) - } + require.ErrorIs(t, err, context.DeadlineExceeded) } func TestExecStrategyWaitUntilReady_CustomExitCode(t *testing.T) { @@ -151,9 +155,7 @@ func TestExecStrategyWaitUntilReady_CustomExitCode(t *testing.T) { return exitCode == 10 }) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestExecStrategyWaitUntilReady_withExitCode(t *testing.T) { @@ -164,23 +166,19 @@ func TestExecStrategyWaitUntilReady_withExitCode(t *testing.T) { // Default is 60. Let's shorten that wg.WithStartupTimeout(time.Second * 2) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Ensure we aren't spuriously returning on any code wg = wait.NewExecStrategy([]string{"true"}).WithExitCode(0) wg.WithStartupTimeout(time.Second * 2) err = wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatalf("Expected strategy to timeout out") - } + require.Errorf(t, err, "Expected strategy to timeout out") } func TestExecStrategyWaitUntilReady_CustomResponseMatcher(t *testing.T) { // waitForExecExitCodeResponse { dockerReq := testcontainers.ContainerRequest{ - Image: "docker.io/nginx:latest", + Image: "nginx:latest", WaitingFor: wait.ForExec([]string{"echo", "hello world!"}). WithStartupTimeout(time.Second * 10). WithExitCodeMatcher(func(exitCode int) bool { @@ -194,15 +192,8 @@ func TestExecStrategyWaitUntilReady_CustomResponseMatcher(t *testing.T) { // } ctx := context.Background() - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } } diff --git a/wait/exit_test.go b/wait/exit_test.go index df6aec1c4a..5c2ec004db 100644 --- a/wait/exit_test.go +++ b/wait/exit_test.go @@ -2,12 +2,14 @@ package wait import ( "context" + "errors" "io" "testing" "time" "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" tcexec "github.com/testcontainers/testcontainers-go/exec" ) @@ -20,6 +22,11 @@ func (st exitStrategyTarget) Host(ctx context.Context) (string, error) { return "", nil } +func (st exitStrategyTarget) Inspect(ctx context.Context) (*types.ContainerJSON, error) { + return nil, nil +} + +// Deprecated: use Inspect instead func (st exitStrategyTarget) Ports(ctx context.Context) (nat.PortMap, error) { return nil, nil } @@ -40,13 +47,15 @@ func (st exitStrategyTarget) State(ctx context.Context) (*types.ContainerState, return &types.ContainerState{Running: st.isRunning}, nil } +func (st exitStrategyTarget) CopyFileFromContainer(context.Context, string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") +} + func TestWaitForExit(t *testing.T) { target := exitStrategyTarget{ isRunning: false, } wg := NewExitStrategy().WithExitTimeout(100 * time.Millisecond) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } diff --git a/wait/file.go b/wait/file.go new file mode 100644 index 0000000000..d9cab7a6e4 --- /dev/null +++ b/wait/file.go @@ -0,0 +1,112 @@ +package wait + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/docker/docker/errdefs" +) + +var ( + _ Strategy = (*FileStrategy)(nil) + _ StrategyTimeout = (*FileStrategy)(nil) +) + +// FileStrategy waits for a file to exist in the container. +type FileStrategy struct { + timeout *time.Duration + file string + pollInterval time.Duration + matcher func(io.Reader) error +} + +// NewFileStrategy constructs an FileStrategy strategy. +func NewFileStrategy(file string) *FileStrategy { + return &FileStrategy{ + file: file, + pollInterval: defaultPollInterval(), + } +} + +// WithStartupTimeout can be used to change the default startup timeout +func (ws *FileStrategy) WithStartupTimeout(startupTimeout time.Duration) *FileStrategy { + ws.timeout = &startupTimeout + return ws +} + +// WithPollInterval can be used to override the default polling interval of 100 milliseconds +func (ws *FileStrategy) WithPollInterval(pollInterval time.Duration) *FileStrategy { + ws.pollInterval = pollInterval + return ws +} + +// WithMatcher can be used to consume the file content. +// The matcher can return an errdefs.ErrNotFound to indicate that the file is not ready. +// Any other error will be considered a failure. +// Default: nil, will only wait for the file to exist. +func (ws *FileStrategy) WithMatcher(matcher func(io.Reader) error) *FileStrategy { + ws.matcher = matcher + return ws +} + +// ForFile is a convenience method to assign FileStrategy +func ForFile(file string) *FileStrategy { + return NewFileStrategy(file) +} + +// Timeout returns the timeout for the strategy +func (ws *FileStrategy) Timeout() *time.Duration { + return ws.timeout +} + +// WaitUntilReady waits until the file exists in the container and copies it to the target. +func (ws *FileStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error { + timeout := defaultStartupTimeout() + if ws.timeout != nil { + timeout = *ws.timeout + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + timer := time.NewTicker(ws.pollInterval) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + if err := ws.matchFile(ctx, target); err != nil { + if errdefs.IsNotFound(err) { + // Not found, continue polling. + continue + } + + return fmt.Errorf("copy from container: %w", err) + } + return nil + } + } +} + +// matchFile tries to copy the file from the container and match it. +func (ws *FileStrategy) matchFile(ctx context.Context, target StrategyTarget) error { + rc, err := target.CopyFileFromContainer(ctx, ws.file) + if err != nil { + return fmt.Errorf("copy from container: %w", err) + } + defer rc.Close() + + if ws.matcher == nil { + // No matcher, just check if the file exists. + return nil + } + + if err = ws.matcher(rc); err != nil { + return fmt.Errorf("matcher: %w", err) + } + + return nil +} diff --git a/wait/file_test.go b/wait/file_test.go new file mode 100644 index 0000000000..20bcc13a01 --- /dev/null +++ b/wait/file_test.go @@ -0,0 +1,104 @@ +package wait_test + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/errdefs" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const testFilename = "/tmp/file" + +var anyContext = mock.MatchedBy(func(_ context.Context) bool { return true }) + +// newRunningTarget creates a new mockStrategyTarget that is running. +func newRunningTarget() *mockStrategyTarget { + target := &mockStrategyTarget{} + target.EXPECT().State(anyContext). + Return(&types.ContainerState{Running: true}, nil) + + return target +} + +// testForFile creates a new FileStrategy for testing. +func testForFile() *wait.FileStrategy { + return wait.ForFile(testFilename). + WithStartupTimeout(time.Millisecond * 50). + WithPollInterval(time.Millisecond) +} + +func TestForFile(t *testing.T) { + errNotFound := errdefs.NotFound(errors.New("file not found")) + ctx := context.Background() + + t.Run("not-found", func(t *testing.T) { + target := newRunningTarget() + target.EXPECT().CopyFileFromContainer(anyContext, testFilename).Return(nil, errNotFound) + err := testForFile().WaitUntilReady(ctx, target) + require.EqualError(t, err, context.DeadlineExceeded.Error()) + }) + + t.Run("other-error", func(t *testing.T) { + otherErr := errors.New("other error") + target := newRunningTarget() + target.EXPECT().CopyFileFromContainer(anyContext, testFilename).Return(nil, otherErr) + err := testForFile().WaitUntilReady(ctx, target) + require.ErrorIs(t, err, otherErr) + }) + + t.Run("valid", func(t *testing.T) { + data := "my content\nwibble" + file := bytes.NewBufferString(data) + target := newRunningTarget() + target.EXPECT().CopyFileFromContainer(anyContext, testFilename).Once().Return(nil, errNotFound) + target.EXPECT().CopyFileFromContainer(anyContext, testFilename).Return(io.NopCloser(file), nil) + var out bytes.Buffer + err := testForFile().WithMatcher(func(r io.Reader) error { + if _, err := io.Copy(&out, r); err != nil { + return fmt.Errorf("copy: %w", err) + } + return nil + }).WaitUntilReady(ctx, target) + require.NoError(t, err) + require.Equal(t, data, out.String()) + }) +} + +func TestFileStrategyWaitUntilReady_WithMatcher(t *testing.T) { + // waitForFileWithMatcher { + var out bytes.Buffer + dockerReq := testcontainers.ContainerRequest{ + Image: "nginx:latest", + WaitingFor: wait.ForFile("/etc/nginx/nginx.conf"). + WithStartupTimeout(time.Second * 10). + WithPollInterval(time.Second). + WithMatcher(func(r io.Reader) error { + if _, err := io.Copy(&out, r); err != nil { + return fmt.Errorf("copy: %w", err) + } + return nil + }), + } + // } + + ctx := context.Background() + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + if container != nil { + t.Cleanup(func() { + require.NoError(t, container.Terminate(context.Background())) + }) + } + require.NoError(t, err) + require.Contains(t, out.String(), "worker_processes") +} diff --git a/wait/health_test.go b/wait/health_test.go index 9f76fb7492..4141988905 100644 --- a/wait/health_test.go +++ b/wait/health_test.go @@ -2,7 +2,9 @@ package wait import ( "context" + "errors" "io" + "sync" "testing" "time" @@ -15,36 +17,58 @@ import ( type healthStrategyTarget struct { state *types.ContainerState + mtx sync.Mutex } -func (st healthStrategyTarget) Host(ctx context.Context) (string, error) { +func (st *healthStrategyTarget) Host(ctx context.Context) (string, error) { return "", nil } -func (st healthStrategyTarget) Ports(ctx context.Context) (nat.PortMap, error) { +func (st *healthStrategyTarget) Inspect(ctx context.Context) (*types.ContainerJSON, error) { return nil, nil } -func (st healthStrategyTarget) MappedPort(ctx context.Context, n nat.Port) (nat.Port, error) { +// Deprecated: use Inspect instead +func (st *healthStrategyTarget) Ports(ctx context.Context) (nat.PortMap, error) { + return nil, nil +} + +func (st *healthStrategyTarget) MappedPort(ctx context.Context, n nat.Port) (nat.Port, error) { return n, nil } -func (st healthStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) { +func (st *healthStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) { return nil, nil } -func (st healthStrategyTarget) Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { +func (st *healthStrategyTarget) Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { return 0, nil, nil } -func (st healthStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) { - return st.state, nil +func (st *healthStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) { + st.mtx.Lock() + defer st.mtx.Unlock() + + // Return a copy to prevent data race. + state := *st.state + + return &state, nil +} + +func (st *healthStrategyTarget) setState(health *types.Health) { + st.mtx.Lock() + defer st.mtx.Unlock() + st.state.Health = health +} + +func (st *healthStrategyTarget) CopyFileFromContainer(_ context.Context, _ string) (io.ReadCloser, error) { + return nil, errors.New("not implemented") } // TestWaitForHealthTimesOutForUnhealthy confirms that an unhealthy container will eventually // time out. func TestWaitForHealthTimesOutForUnhealthy(t *testing.T) { - target := healthStrategyTarget{ + target := &healthStrategyTarget{ state: &types.ContainerState{ Running: true, Health: &types.Health{Status: types.Unhealthy}, @@ -59,7 +83,7 @@ func TestWaitForHealthTimesOutForUnhealthy(t *testing.T) { // TestWaitForHealthSucceeds ensures that a healthy container always succeeds. func TestWaitForHealthSucceeds(t *testing.T) { - target := healthStrategyTarget{ + target := &healthStrategyTarget{ state: &types.ContainerState{ Running: true, Health: &types.Health{Status: types.Healthy}, @@ -88,7 +112,7 @@ func TestWaitForHealthWithNil(t *testing.T) { // wait a bit to simulate startup time and give check time to at least // try a few times with a nil Health time.Sleep(200 * time.Millisecond) - target.state.Health = &types.Health{Status: types.Healthy} + target.setState(&types.Health{Status: types.Healthy}) }(target) err := wg.WaitUntilReady(context.Background(), target) diff --git a/wait/host_port.go b/wait/host_port.go index c544825831..7d8b9e76ff 100644 --- a/wait/host_port.go +++ b/wait/host_port.go @@ -13,13 +13,21 @@ import ( "github.com/docker/go-connections/nat" ) +const ( + exitEaccess = 126 // container cmd can't be invoked (permission denied) + exitCmdNotFound = 127 // container cmd not found/does not exist or invalid bind-mount +) + // Implement interface var ( _ Strategy = (*HostPortStrategy)(nil) _ StrategyTimeout = (*HostPortStrategy)(nil) ) -var errShellNotExecutable = errors.New("/bin/sh command not executable") +var ( + errShellNotExecutable = errors.New("/bin/sh command not executable") + errShellNotFound = errors.New("/bin/sh command not found") +) type HostPortStrategy struct { // Port is a string containing port number and protocol in the format "80/tcp" @@ -28,9 +36,15 @@ type HostPortStrategy struct { // all WaitStrategies should have a startupTimeout to avoid waiting infinitely timeout *time.Duration PollInterval time.Duration + + // skipInternalCheck is a flag to skip the internal check, which is useful when + // a shell is not available in the container or when the container doesn't bind + // the port internally until additional conditions are met. + skipInternalCheck bool } -// NewHostPortStrategy constructs a default host port strategy +// NewHostPortStrategy constructs a default host port strategy that waits for the given +// port to be exposed. The default startup timeout is 60 seconds. func NewHostPortStrategy(port nat.Port) *HostPortStrategy { return &HostPortStrategy{ Port: port, @@ -42,18 +56,28 @@ func NewHostPortStrategy(port nat.Port) *HostPortStrategy { // since go has neither covariance nor generics, the return type must be the type of the concrete implementation // this is true for all properties, even the "shared" ones like startupTimeout -// ForListeningPort is a helper similar to those in Wait.java -// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java +// ForListeningPort returns a host port strategy that waits for the given port +// to be exposed and bound internally the container. +// Alias for `NewHostPortStrategy(port)`. func ForListeningPort(port nat.Port) *HostPortStrategy { return NewHostPortStrategy(port) } -// ForExposedPort constructs an exposed port strategy. Alias for `NewHostPortStrategy("")`. -// This strategy waits for the first port exposed in the Docker container. +// ForExposedPort returns a host port strategy that waits for the first port +// to be exposed and bound internally the container. func ForExposedPort() *HostPortStrategy { return NewHostPortStrategy("") } +// SkipInternalCheck changes the host port strategy to skip the internal check, +// which is useful when a shell is not available in the container or when the +// container doesn't bind the port internally until additional conditions are met. +func (hp *HostPortStrategy) SkipInternalCheck() *HostPortStrategy { + hp.skipInternalCheck = true + + return hp +} + // WithStartupTimeout can be used to change the default startup timeout func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *HostPortStrategy { hp.timeout = &startupTimeout @@ -89,21 +113,20 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT internalPort := hp.Port if internalPort == "" { - var ports nat.PortMap - ports, err = target.Ports(ctx) + inspect, err := target.Inspect(ctx) if err != nil { return err } - if len(ports) > 0 { - for p := range ports { - internalPort = p - break + + for port := range inspect.NetworkSettings.Ports { + if internalPort == "" || port.Int() < internalPort.Int() { + internalPort = port } } } if internalPort == "" { - return fmt.Errorf("no port to wait for") + return errors.New("no port to wait for") } var port nat.Port @@ -115,27 +138,37 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT select { case <-ctx.Done(): - return fmt.Errorf("%w: %w", ctx.Err(), err) + return fmt.Errorf("mapped port: retries: %d, port: %q, last err: %w, ctx err: %w", i, port, err, ctx.Err()) case <-time.After(waitInterval): if err := checkTarget(ctx, target); err != nil { - return err + return fmt.Errorf("check target: retries: %d, port: %q, last err: %w", i, port, err) } port, err = target.MappedPort(ctx, internalPort) if err != nil { - log.Printf("(%d) [%s] %s\n", i, port, err) + log.Printf("mapped port: retries: %d, port: %q, err: %s\n", i, port, err) } } } if err := externalCheck(ctx, ipAddress, port, target, waitInterval); err != nil { - return err + return fmt.Errorf("external check: %w", err) } - err = internalCheck(ctx, internalPort, target) - if err != nil && errors.Is(errShellNotExecutable, err) { - log.Println("Shell not executable in container, only external port check will be performed") - } else { - return err + if hp.skipInternalCheck { + return nil + } + + if err = internalCheck(ctx, internalPort, target); err != nil { + switch { + case errors.Is(err, errShellNotExecutable): + log.Println("Shell not executable in container, only external port validated") + return nil + case errors.Is(err, errShellNotFound): + log.Println("Shell not found in container") + return nil + default: + return fmt.Errorf("internal check: %w", err) + } } return nil @@ -148,9 +181,9 @@ func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target dialer := net.Dialer{} address := net.JoinHostPort(ipAddress, portString) - for { + for i := 0; ; i++ { if err := checkTarget(ctx, target); err != nil { - return err + return fmt.Errorf("check target: retries: %d address: %s: %w", i, address, err) } conn, err := dialer.DialContext(ctx, proto, address) if err != nil { @@ -164,13 +197,12 @@ func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target } } } - return err - } else { - _ = conn.Close() - break + return fmt.Errorf("dial: %w", err) } + + conn.Close() + return nil } - return nil } func internalCheck(ctx context.Context, internalPort nat.Port, target StrategyTarget) error { @@ -187,13 +219,18 @@ func internalCheck(ctx context.Context, internalPort nat.Port, target StrategyTa return fmt.Errorf("%w, host port waiting failed", err) } - if exitCode == 0 { - break - } else if exitCode == 126 { + // Docker has a issue which override exit code 127 to 126 due to: + // https://github.com/moby/moby/issues/45795 + // Handle both to ensure compatibility with Docker and Podman for now. + switch exitCode { + case 0: + return nil + case exitEaccess: return errShellNotExecutable + case exitCmdNotFound: + return errShellNotFound } } - return nil } func buildInternalCheckCommand(internalPort int) string { diff --git a/wait/host_port_test.go b/wait/host_port_test.go index 92212989eb..4dbaad741f 100644 --- a/wait/host_port_test.go +++ b/wait/host_port_test.go @@ -1,8 +1,10 @@ package wait import ( + "bytes" "context" "io" + "log" "net" "strconv" "testing" @@ -10,22 +12,19 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/exec" ) func TestWaitForListeningPortSucceeds(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var mappedPortCount, execCount int target := &MockStrategyTarget{ @@ -57,35 +56,36 @@ func TestWaitForListeningPortSucceeds(t *testing.T) { WithStartupTimeout(5 * time.Second). WithPollInterval(100 * time.Millisecond) - if err := wg.WaitUntilReady(context.Background(), target); err != nil { - t.Fatal(err) - } + err = wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) } func TestWaitForExposedPortSucceeds(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var mappedPortCount, execCount int target := &MockStrategyTarget{ HostImpl: func(_ context.Context) (string, error) { return "localhost", nil }, - PortsImpl: func(_ context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "80": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - HostPort: port.Port(), + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "80": []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: port.Port(), + }, + }, + }, }, }, }, nil @@ -115,9 +115,8 @@ func TestWaitForExposedPortSucceeds(t *testing.T) { WithStartupTimeout(5 * time.Second). WithPollInterval(100 * time.Millisecond) - if err := wg.WaitUntilReady(context.Background(), target); err != nil { - t.Fatal(err) - } + err = wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) } func TestHostPortStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) { @@ -146,14 +145,7 @@ func TestHostPortStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container crashed with out-of-memory (OOMKilled)") } } @@ -184,14 +176,7 @@ func TestHostPortStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container exited with code 1") } } @@ -221,14 +206,7 @@ func TestHostPortStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t * { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "unexpected container status \"dead\"") } } @@ -253,14 +231,7 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToOOMKilledContainer(t *te { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container crashed with out-of-memory (OOMKilled)") } } @@ -286,14 +257,7 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToExitedContainer(t *testi { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container exited with code 1") } } @@ -318,29 +282,18 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToUnexpectedContainerStatu { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "unexpected container status \"dead\"") } } func TestHostPortStrategyFailsWhileInternalCheckingDueToOOMKilledContainer(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var stateCount int target := &MockStrategyTarget{ @@ -369,29 +322,18 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToOOMKilledContainer(t *te { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container crashed with out-of-memory (OOMKilled)") } } func TestHostPortStrategyFailsWhileInternalCheckingDueToExitedContainer(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var stateCount int target := &MockStrategyTarget{ @@ -421,29 +363,18 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToExitedContainer(t *testi { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "container exited with code 1") } } func TestHostPortStrategyFailsWhileInternalCheckingDueToUnexpectedContainerStatus(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var stateCount int target := &MockStrategyTarget{ @@ -472,40 +403,35 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToUnexpectedContainerStatu { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.ErrorContains(t, err, "unexpected container status \"dead\"") } } func TestHostPortStrategySucceedsGivenShellIsNotInstalled(t *testing.T) { listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer listener.Close() rawPort := listener.Addr().(*net.TCPAddr).Port port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) target := &MockStrategyTarget{ HostImpl: func(_ context.Context) (string, error) { return "localhost", nil }, - PortsImpl: func(_ context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "80": []nat.PortBinding{ - { - HostIP: "0.0.0.0", - HostPort: port.Port(), + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "80": []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: port.Port(), + }, + }, + }, }, }, }, nil @@ -520,7 +446,7 @@ func TestHostPortStrategySucceedsGivenShellIsNotInstalled(t *testing.T) { }, ExecImpl: func(_ context.Context, _ []string, _ ...exec.ProcessOption) (int, io.Reader, error) { // This is the error that would be returned if the shell is not installed. - return 126, nil, nil + return exitEaccess, nil, nil }, } @@ -528,7 +454,75 @@ func TestHostPortStrategySucceedsGivenShellIsNotInstalled(t *testing.T) { WithStartupTimeout(5 * time.Second). WithPollInterval(100 * time.Millisecond) - if err := wg.WaitUntilReady(context.Background(), target); err != nil { - t.Fatal(err) + oldWriter := log.Default().Writer() + var buf bytes.Buffer + log.Default().SetOutput(&buf) + t.Cleanup(func() { + log.Default().SetOutput(oldWriter) + }) + + err = wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) + + require.Contains(t, buf.String(), "Shell not executable in container, only external port validated") +} + +func TestHostPortStrategySucceedsGivenShellIsNotFound(t *testing.T) { + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + defer listener.Close() + + rawPort := listener.Addr().(*net.TCPAddr).Port + port, err := nat.NewPort("tcp", strconv.Itoa(rawPort)) + require.NoError(t, err) + + target := &MockStrategyTarget{ + HostImpl: func(_ context.Context) (string, error) { + return "localhost", nil + }, + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "80": []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: port.Port(), + }, + }, + }, + }, + }, + }, nil + }, + MappedPortImpl: func(_ context.Context, _ nat.Port) (nat.Port, error) { + return port, nil + }, + StateImpl: func(_ context.Context) (*types.ContainerState, error) { + return &types.ContainerState{ + Running: true, + }, nil + }, + ExecImpl: func(_ context.Context, _ []string, _ ...exec.ProcessOption) (int, io.Reader, error) { + // This is the error that would be returned if the shell is not found. + return exitCmdNotFound, nil, nil + }, } + + wg := NewHostPortStrategy("80"). + WithStartupTimeout(5 * time.Second). + WithPollInterval(100 * time.Millisecond) + + oldWriter := log.Default().Writer() + var buf bytes.Buffer + log.Default().SetOutput(&buf) + t.Cleanup(func() { + log.Default().SetOutput(oldWriter) + }) + + err = wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) + + require.Contains(t, buf.String(), "Shell not found in container") } diff --git a/wait/http.go b/wait/http.go index 89e4f092d2..11452ecbf0 100644 --- a/wait/http.go +++ b/wait/http.go @@ -76,6 +76,8 @@ func (ws *HTTPStrategy) WithStartupTimeout(timeout time.Duration) *HTTPStrategy return ws } +// WithPort set the port to wait for. +// Default is the lowest numbered port. func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy { ws.Port = port return ws @@ -173,33 +175,43 @@ func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarge var mappedPort nat.Port if ws.Port == "" { - var err error - var ports nat.PortMap - // we wait one polling interval before we grab the ports otherwise they might not be bound yet on startup - for err != nil || ports == nil { - select { - case <-ctx.Done(): - return fmt.Errorf("%w: %w", ctx.Err(), err) - case <-time.After(ws.PollInterval): - if err := checkTarget(ctx, target); err != nil { - return err - } + // We wait one polling interval before we grab the ports + // otherwise they might not be bound yet on startup. + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(ws.PollInterval): + // Port should now be bound so just continue. + } - ports, err = target.Ports(ctx) - } + if err := checkTarget(ctx, target); err != nil { + return err + } + + inspect, err := target.Inspect(ctx) + if err != nil { + return err } - for k, bindings := range ports { - if len(bindings) == 0 || k.Proto() != "tcp" { + // Find the lowest numbered exposed tcp port. + var lowestPort nat.Port + var hostPort string + for port, bindings := range inspect.NetworkSettings.Ports { + if len(bindings) == 0 || port.Proto() != "tcp" { continue } - mappedPort, _ = nat.NewPort(k.Proto(), bindings[0].HostPort) - break + + if lowestPort == "" || port.Int() < lowestPort.Int() { + lowestPort = port + hostPort = bindings[0].HostPort + } } - if mappedPort == "" { + if lowestPort == "" { return errors.New("No exposed tcp ports or mapped ports - cannot wait for status") } + + mappedPort, _ = nat.NewPort(lowestPort.Proto(), hostPort) } else { mappedPort, err = target.MappedPort(ctx, ws.Port) diff --git a/wait/http_test.go b/wait/http_test.go index e9e065ab33..73e32d44d7 100644 --- a/wait/http_test.go +++ b/wait/http_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "crypto/x509" + _ "embed" "fmt" "io" "log" @@ -17,11 +18,15 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) +//go:embed testdata/root.pem +var caBytes []byte + // https://github.com/testcontainers/testcontainers-go/issues/183 func ExampleHTTPStrategy() { // waitForHTTPWithDefaultPort { @@ -36,20 +41,21 @@ func ExampleHTTPStrategy() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -62,12 +68,14 @@ func ExampleHTTPStrategy_WithHeaders() { capath := filepath.Join("testdata", "root.pem") cafile, err := os.ReadFile(capath) if err != nil { - log.Fatalf("can't load ca file: %v", err) + log.Printf("can't load ca file: %v", err) + return } certpool := x509.NewCertPool() if !certpool.AppendCertsFromPEM(cafile) { - log.Fatalf("the ca file isn't valid") + log.Printf("the ca file isn't valid") + return } ctx := context.Background() @@ -76,7 +84,7 @@ func ExampleHTTPStrategy_WithHeaders() { tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} req := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: "testdata", + Context: "testdata/http", }, ExposedPorts: []string{"6443/tcp"}, WaitingFor: wait.ForHTTP("/headers"). @@ -94,19 +102,20 @@ func ExampleHTTPStrategy_WithHeaders() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -128,20 +137,21 @@ func ExampleHTTPStrategy_WithPort() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -162,19 +172,20 @@ func ExampleHTTPStrategy_WithForcedIPv4LocalHost() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -196,20 +207,21 @@ func ExampleHTTPStrategy_WithBasicAuth() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := gogs.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(gogs); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := gogs.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -219,29 +231,13 @@ func ExampleHTTPStrategy_WithBasicAuth() { } func TestHTTPStrategyWaitUntilReady(t *testing.T) { - workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } - - capath := filepath.Join(workdir, "testdata", "root.pem") - cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } - certpool := x509.NewCertPool() - if !certpool.AppendCertsFromPEM(cafile) { - t.Errorf("the ca file isn't valid") - return - } + require.Truef(t, certpool.AppendCertsFromPEM(caBytes), "the ca file isn't valid") tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} dockerReq := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: filepath.Join(workdir, "testdata"), + Context: "testdata/http", }, ExposedPorts: []string{"6443/tcp"}, WaitingFor: wait.NewHTTPStrategy("/auth-ping").WithTLS(true, tlsconfig). @@ -254,24 +250,17 @@ func TestHTTPStrategyWaitUntilReady(t *testing.T) { WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))), } - container, err := testcontainers.GenericContainer(context.Background(), + ctr, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - defer container.Terminate(context.Background()) // nolint: errcheck + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(context.Background()) + require.NoError(t, err) + + port, err := ctr.MappedPort(context.Background(), "6443/tcp") + require.NoError(t, err) - host, err := container.Host(context.Background()) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(context.Background(), "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -289,41 +278,20 @@ func TestHTTPStrategyWaitUntilReady(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { - workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } - - capath := filepath.Join(workdir, "testdata", "root.pem") - cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } - certpool := x509.NewCertPool() - if !certpool.AppendCertsFromPEM(cafile) { - t.Errorf("the ca file isn't valid") - return - } + require.Truef(t, certpool.AppendCertsFromPEM(caBytes), "the ca file isn't valid") tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} dockerReq := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: filepath.Join(workdir, "testdata"), + Context: "testdata/http", }, ExposedPorts: []string{"6443/tcp"}, @@ -335,24 +303,17 @@ func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { }), } - container, err := testcontainers.GenericContainer(context.Background(), + ctr, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - defer container.Terminate(context.Background()) // nolint: errcheck + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(context.Background()) + require.NoError(t, err) + + port, err := ctr.MappedPort(context.Background(), "6443/tcp") + require.NoError(t, err) - host, err := container.Host(context.Background()) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(context.Background(), "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -370,43 +331,22 @@ func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { - workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } - - capath := filepath.Join(workdir, "testdata", "root.pem") - cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } - certpool := x509.NewCertPool() - if !certpool.AppendCertsFromPEM(cafile) { - t.Errorf("the ca file isn't valid") - return - } + require.Truef(t, certpool.AppendCertsFromPEM(caBytes), "the ca file isn't valid") // waitForHTTPStatusCode { tlsconfig := &tls.Config{RootCAs: certpool, ServerName: "testcontainer.go.test"} var i int dockerReq := testcontainers.ContainerRequest{ FromDockerfile: testcontainers.FromDockerfile{ - Context: filepath.Join(workdir, "testdata"), + Context: "testdata/http", }, ExposedPorts: []string{"6443/tcp"}, WaitingFor: wait.NewHTTPStrategy("/ping").WithTLS(true, tlsconfig). @@ -424,27 +364,16 @@ func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { // } ctx := context.Background() - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(ctx) + require.NoError(t, err) + + port, err := ctr.MappedPort(ctx, "6443/tcp") + require.NoError(t, err) - host, err := container.Host(ctx) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(ctx, "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -462,15 +391,10 @@ func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHttpStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) { @@ -491,12 +415,18 @@ func TestHttpStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) OOMKilled: true, }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -507,17 +437,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { @@ -539,12 +461,18 @@ func TestHttpStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { ExitCode: 1, }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -555,17 +483,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t *testing.T) { @@ -586,12 +506,18 @@ func TestHttpStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t *test Status: "dead", }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -602,17 +528,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t *test WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) } func TestHTTPStrategyFailsWhileRequestSendingDueToOOMKilledContainer(t *testing.T) { @@ -628,12 +546,18 @@ func TestHTTPStrategyFailsWhileRequestSendingDueToOOMKilledContainer(t *testing. OOMKilled: true, }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -644,17 +568,9 @@ func TestHTTPStrategyFailsWhileRequestSendingDueToOOMKilledContainer(t *testing. WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileRequestSendingDueToExitedContainer(t *testing.T) { @@ -671,12 +587,18 @@ func TestHttpStrategyFailsWhileRequestSendingDueToExitedContainer(t *testing.T) ExitCode: 1, }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -687,17 +609,9 @@ func TestHttpStrategyFailsWhileRequestSendingDueToExitedContainer(t *testing.T) WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileRequestSendingDueToUnexpectedContainerStatus(t *testing.T) { @@ -713,12 +627,18 @@ func TestHttpStrategyFailsWhileRequestSendingDueToUnexpectedContainerStatus(t *t Status: "dead", }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -729,17 +649,9 @@ func TestHttpStrategyFailsWhileRequestSendingDueToUnexpectedContainerStatus(t *t WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToNoExposedPorts(t *testing.T) { @@ -761,8 +673,14 @@ func TestHttpStrategyFailsWhileGettingPortDueToNoExposedPorts(t *testing.T) { Running: true, }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{}, nil + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{}, + }, + }, + }, nil }, } @@ -770,17 +688,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToNoExposedPorts(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToOnlyUDPPorts(t *testing.T) { @@ -802,12 +712,18 @@ func TestHttpStrategyFailsWhileGettingPortDueToOnlyUDPPorts(t *testing.T) { Status: "running", }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/udp": []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: "49152", + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/udp": []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "49152", + }, + }, + }, }, }, }, nil @@ -818,17 +734,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToOnlyUDPPorts(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToExposedPortNoBindings(t *testing.T) { @@ -850,9 +758,15 @@ func TestHttpStrategyFailsWhileGettingPortDueToExposedPortNoBindings(t *testing. Status: "running", }, nil }, - PortsImpl: func(ctx context.Context) (nat.PortMap, error) { - return nat.PortMap{ - "8080/tcp": []nat.PortBinding{}, + InspectImpl: func(_ context.Context) (*types.ContainerJSON, error) { + return &types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + "8080/tcp": []nat.PortBinding{}, + }, + }, + }, }, nil }, } @@ -861,15 +775,7 @@ func TestHttpStrategyFailsWhileGettingPortDueToExposedPortNoBindings(t *testing. WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } diff --git a/wait/log_test.go b/wait/log_test.go index 78c5019ae8..7c767c0e25 100644 --- a/wait/log_test.go +++ b/wait/log_test.go @@ -8,8 +8,11 @@ import ( "time" "github.com/docker/docker/api/types" + "github.com/stretchr/testify/require" ) +const logTimeout = time.Second + const loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. @@ -26,11 +29,9 @@ func TestWaitForLog(t *testing.T) { target := NopStrategyTarget{ ReaderCloser: io.NopCloser(bytes.NewReader([]byte("docker"))), } - wg := NewLogStrategy("docker").WithStartupTimeout(100 * time.Microsecond) + wg := NewLogStrategy("docker").WithStartupTimeout(100 * time.Millisecond) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) t.Run("no regexp", func(t *testing.T) { @@ -39,11 +40,9 @@ func TestWaitForLog(t *testing.T) { } // get all words that start with "ip", end with "m" and has a whitespace before the "ip" - wg := NewLogStrategy(`\sip[\w]+m`).WithStartupTimeout(100 * time.Microsecond).AsRegexp() + wg := NewLogStrategy(`\sip[\w]+m`).WithStartupTimeout(100 * time.Millisecond).AsRegexp() err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) } @@ -53,12 +52,10 @@ func TestWaitWithExactNumberOfOccurrences(t *testing.T) { ReaderCloser: io.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker\n\rdocker"))), } wg := NewLogStrategy("docker"). - WithStartupTimeout(100 * time.Microsecond). + WithStartupTimeout(100 * time.Millisecond). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) t.Run("as regexp", func(t *testing.T) { @@ -69,11 +66,9 @@ func TestWaitWithExactNumberOfOccurrences(t *testing.T) { // get texts from "ip" to the next "m". // there are three occurrences of this pattern in the string: // one "ipsum mauris" and two "ipsum dolor sit am" - wg := NewLogStrategy(`ip(.*)m`).WithStartupTimeout(100 * time.Microsecond).AsRegexp().WithOccurrence(3) + wg := NewLogStrategy(`ip(.*)m`).WithStartupTimeout(100 * time.Millisecond).AsRegexp().WithOccurrence(3) err := wg.WaitUntilReady(context.Background(), target) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) } @@ -83,12 +78,10 @@ func TestWaitWithExactNumberOfOccurrencesButItWillNeverHappen(t *testing.T) { ReaderCloser: io.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker"))), } wg := NewLogStrategy("containerd"). - WithStartupTimeout(100 * time.Microsecond). + WithStartupTimeout(logTimeout). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("expected error") - } + require.Error(t, err) }) t.Run("as regexp", func(t *testing.T) { @@ -98,11 +91,9 @@ func TestWaitWithExactNumberOfOccurrencesButItWillNeverHappen(t *testing.T) { // get texts from "ip" to the next "m". // there are only three occurrences matching - wg := NewLogStrategy(`do(.*)ck.+`).WithStartupTimeout(100 * time.Microsecond).AsRegexp().WithOccurrence(4) + wg := NewLogStrategy(`do(.*)ck.+`).WithStartupTimeout(100 * time.Millisecond).AsRegexp().WithOccurrence(4) err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("expected error") - } + require.Error(t, err) }) } @@ -112,12 +103,10 @@ func TestWaitShouldFailWithExactNumberOfOccurrences(t *testing.T) { ReaderCloser: io.NopCloser(bytes.NewReader([]byte("kubernetes\r\ndocker"))), } wg := NewLogStrategy("docker"). - WithStartupTimeout(100 * time.Microsecond). + WithStartupTimeout(logTimeout). WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("expected error") - } + require.Error(t, err) }) t.Run("as regexp", func(t *testing.T) { @@ -127,11 +116,9 @@ func TestWaitShouldFailWithExactNumberOfOccurrences(t *testing.T) { // get "Maecenas". // there are only one occurrence matching - wg := NewLogStrategy(`^Mae[\w]?enas\s`).WithStartupTimeout(100 * time.Microsecond).AsRegexp().WithOccurrence(2) + wg := NewLogStrategy(`^Mae[\w]?enas\s`).WithStartupTimeout(100 * time.Millisecond).AsRegexp().WithOccurrence(2) err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("expected error") - } + require.Error(t, err) }) } @@ -148,37 +135,19 @@ func TestWaitForLogFailsDueToOOMKilledContainer(t *testing.T) { } t.Run("no regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond) + wg := ForLog("docker").WithStartupTimeout(logTimeout) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) }) t.Run("as regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond).AsRegexp() - - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + wg := ForLog("docker").WithStartupTimeout(logTimeout).AsRegexp() - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) }) } @@ -196,37 +165,19 @@ func TestWaitForLogFailsDueToExitedContainer(t *testing.T) { } t.Run("no regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond) - - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + wg := ForLog("docker").WithStartupTimeout(logTimeout) - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) }) t.Run("as regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond).AsRegexp() + wg := ForLog("docker").WithStartupTimeout(logTimeout).AsRegexp() - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) }) } @@ -243,36 +194,18 @@ func TestWaitForLogFailsDueToUnexpectedContainerStatus(t *testing.T) { } t.Run("no regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond) + wg := ForLog("docker").WithStartupTimeout(logTimeout) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) }) t.Run("as regexp", func(t *testing.T) { - wg := ForLog("docker"). - WithStartupTimeout(100 * time.Microsecond).AsRegexp() + wg := ForLog("docker").WithStartupTimeout(logTimeout).AsRegexp() - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) }) } diff --git a/wait/nop.go b/wait/nop.go index e7c86bea5e..4206eefc1e 100644 --- a/wait/nop.go +++ b/wait/nop.go @@ -51,6 +51,11 @@ func (st NopStrategyTarget) Host(_ context.Context) (string, error) { return "", nil } +func (st NopStrategyTarget) Inspect(_ context.Context) (*types.ContainerJSON, error) { + return nil, nil +} + +// Deprecated: use Inspect instead func (st NopStrategyTarget) Ports(_ context.Context) (nat.PortMap, error) { return nil, nil } @@ -70,3 +75,7 @@ func (st NopStrategyTarget) Exec(_ context.Context, _ []string, _ ...exec.Proces func (st NopStrategyTarget) State(_ context.Context) (*types.ContainerState, error) { return &st.ContainerState, nil } + +func (st NopStrategyTarget) CopyFileFromContainer(context.Context, string) (io.ReadCloser, error) { + return st.ReaderCloser, nil +} diff --git a/wait/sql_test.go b/wait/sql_test.go index 053ed965e8..63179ee8ab 100644 --- a/wait/sql_test.go +++ b/wait/sql_test.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" ) func Test_waitForSql_WithQuery(t *testing.T) { @@ -17,9 +18,7 @@ func Test_waitForSql_WithQuery(t *testing.T) { return "fake-url" }) - if got := w.query; got != defaultForSqlQuery { - t.Fatalf("expected %s, got %s", defaultForSqlQuery, got) - } + require.Equal(t, defaultForSqlQuery, w.query) }) t.Run("custom query", func(t *testing.T) { const q = "SELECT 100;" @@ -28,9 +27,7 @@ func Test_waitForSql_WithQuery(t *testing.T) { return "fake-url" }).WithQuery(q) - if got := w.query; got != q { - t.Fatalf("expected %s, got %s", q, got) - } + require.Equal(t, q, w.query) }) } @@ -102,9 +99,8 @@ func TestWaitForSQLSucceeds(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - if err := wg.WaitUntilReady(context.Background(), target); err != nil { - t.Fatal(err) - } + err := wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) } func TestWaitForSQLFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) { @@ -133,14 +129,7 @@ func TestWaitForSQLFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) { { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "container crashed with out-of-memory (OOMKilled)") } } @@ -171,14 +160,7 @@ func TestWaitForSQLFailsWhileGettingPortDueToExitedContainer(t *testing.T) { { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "container exited with code 1") } } @@ -208,14 +190,7 @@ func TestWaitForSQLFailsWhileGettingPortDueToUnexpectedContainerStatus(t *testin { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "unexpected container status \"dead\"") } } @@ -240,14 +215,7 @@ func TestWaitForSQLFailsWhileQueryExecutingDueToOOMKilledContainer(t *testing.T) { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "container crashed with out-of-memory (OOMKilled)") } } @@ -273,14 +241,7 @@ func TestWaitForSQLFailsWhileQueryExecutingDueToExitedContainer(t *testing.T) { { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "container exited with code 1") } } @@ -305,13 +266,6 @@ func TestWaitForSQLFailsWhileQueryExecutingDueToUnexpectedContainerStatus(t *tes { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.EqualError(t, err, "unexpected container status \"dead\"") } } diff --git a/wait/strategytarget_mock_test.go b/wait/strategytarget_mock_test.go new file mode 100644 index 0000000000..525ff08e1d --- /dev/null +++ b/wait/strategytarget_mock_test.go @@ -0,0 +1,528 @@ +// Code generated by mockery. DO NOT EDIT. + +package wait_test + +import ( + context "context" + io "io" + + exec "github.com/testcontainers/testcontainers-go/exec" + + mock "github.com/stretchr/testify/mock" + + nat "github.com/docker/go-connections/nat" + + types "github.com/docker/docker/api/types" +) + +// mockStrategyTarget is an autogenerated mock type for the StrategyTarget type +type mockStrategyTarget struct { + mock.Mock +} + +type mockStrategyTarget_Expecter struct { + mock *mock.Mock +} + +func (_m *mockStrategyTarget) EXPECT() *mockStrategyTarget_Expecter { + return &mockStrategyTarget_Expecter{mock: &_m.Mock} +} + +// CopyFileFromContainer provides a mock function with given fields: ctx, filePath +func (_m *mockStrategyTarget) CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error) { + ret := _m.Called(ctx, filePath) + + if len(ret) == 0 { + panic("no return value specified for CopyFileFromContainer") + } + + var r0 io.ReadCloser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (io.ReadCloser, error)); ok { + return rf(ctx, filePath) + } + if rf, ok := ret.Get(0).(func(context.Context, string) io.ReadCloser); ok { + r0 = rf(ctx, filePath) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.ReadCloser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, filePath) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_CopyFileFromContainer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CopyFileFromContainer' +type mockStrategyTarget_CopyFileFromContainer_Call struct { + *mock.Call +} + +// CopyFileFromContainer is a helper method to define mock.On call +// - ctx context.Context +// - filePath string +func (_e *mockStrategyTarget_Expecter) CopyFileFromContainer(ctx interface{}, filePath interface{}) *mockStrategyTarget_CopyFileFromContainer_Call { + return &mockStrategyTarget_CopyFileFromContainer_Call{Call: _e.mock.On("CopyFileFromContainer", ctx, filePath)} +} + +func (_c *mockStrategyTarget_CopyFileFromContainer_Call) Run(run func(ctx context.Context, filePath string)) *mockStrategyTarget_CopyFileFromContainer_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *mockStrategyTarget_CopyFileFromContainer_Call) Return(_a0 io.ReadCloser, _a1 error) *mockStrategyTarget_CopyFileFromContainer_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_CopyFileFromContainer_Call) RunAndReturn(run func(context.Context, string) (io.ReadCloser, error)) *mockStrategyTarget_CopyFileFromContainer_Call { + _c.Call.Return(run) + return _c +} + +// Exec provides a mock function with given fields: _a0, _a1, _a2 +func (_m *mockStrategyTarget) Exec(_a0 context.Context, _a1 []string, _a2 ...exec.ProcessOption) (int, io.Reader, error) { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0, _a1) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Exec") + } + + var r0 int + var r1 io.Reader + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []string, ...exec.ProcessOption) (int, io.Reader, error)); ok { + return rf(_a0, _a1, _a2...) + } + if rf, ok := ret.Get(0).(func(context.Context, []string, ...exec.ProcessOption) int); ok { + r0 = rf(_a0, _a1, _a2...) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, []string, ...exec.ProcessOption) io.Reader); ok { + r1 = rf(_a0, _a1, _a2...) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(io.Reader) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []string, ...exec.ProcessOption) error); ok { + r2 = rf(_a0, _a1, _a2...) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// mockStrategyTarget_Exec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exec' +type mockStrategyTarget_Exec_Call struct { + *mock.Call +} + +// Exec is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 []string +// - _a2 ...exec.ProcessOption +func (_e *mockStrategyTarget_Expecter) Exec(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *mockStrategyTarget_Exec_Call { + return &mockStrategyTarget_Exec_Call{Call: _e.mock.On("Exec", + append([]interface{}{_a0, _a1}, _a2...)...)} +} + +func (_c *mockStrategyTarget_Exec_Call) Run(run func(_a0 context.Context, _a1 []string, _a2 ...exec.ProcessOption)) *mockStrategyTarget_Exec_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]exec.ProcessOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(exec.ProcessOption) + } + } + run(args[0].(context.Context), args[1].([]string), variadicArgs...) + }) + return _c +} + +func (_c *mockStrategyTarget_Exec_Call) Return(_a0 int, _a1 io.Reader, _a2 error) *mockStrategyTarget_Exec_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *mockStrategyTarget_Exec_Call) RunAndReturn(run func(context.Context, []string, ...exec.ProcessOption) (int, io.Reader, error)) *mockStrategyTarget_Exec_Call { + _c.Call.Return(run) + return _c +} + +// Host provides a mock function with given fields: _a0 +func (_m *mockStrategyTarget) Host(_a0 context.Context) (string, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Host") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_Host_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Host' +type mockStrategyTarget_Host_Call struct { + *mock.Call +} + +// Host is a helper method to define mock.On call +// - _a0 context.Context +func (_e *mockStrategyTarget_Expecter) Host(_a0 interface{}) *mockStrategyTarget_Host_Call { + return &mockStrategyTarget_Host_Call{Call: _e.mock.On("Host", _a0)} +} + +func (_c *mockStrategyTarget_Host_Call) Run(run func(_a0 context.Context)) *mockStrategyTarget_Host_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockStrategyTarget_Host_Call) Return(_a0 string, _a1 error) *mockStrategyTarget_Host_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_Host_Call) RunAndReturn(run func(context.Context) (string, error)) *mockStrategyTarget_Host_Call { + _c.Call.Return(run) + return _c +} + +// Inspect provides a mock function with given fields: _a0 +func (_m *mockStrategyTarget) Inspect(_a0 context.Context) (*types.ContainerJSON, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Inspect") + } + + var r0 *types.ContainerJSON + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*types.ContainerJSON, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) *types.ContainerJSON); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ContainerJSON) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_Inspect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Inspect' +type mockStrategyTarget_Inspect_Call struct { + *mock.Call +} + +// Inspect is a helper method to define mock.On call +// - _a0 context.Context +func (_e *mockStrategyTarget_Expecter) Inspect(_a0 interface{}) *mockStrategyTarget_Inspect_Call { + return &mockStrategyTarget_Inspect_Call{Call: _e.mock.On("Inspect", _a0)} +} + +func (_c *mockStrategyTarget_Inspect_Call) Run(run func(_a0 context.Context)) *mockStrategyTarget_Inspect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockStrategyTarget_Inspect_Call) Return(_a0 *types.ContainerJSON, _a1 error) *mockStrategyTarget_Inspect_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_Inspect_Call) RunAndReturn(run func(context.Context) (*types.ContainerJSON, error)) *mockStrategyTarget_Inspect_Call { + _c.Call.Return(run) + return _c +} + +// Logs provides a mock function with given fields: _a0 +func (_m *mockStrategyTarget) Logs(_a0 context.Context) (io.ReadCloser, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Logs") + } + + var r0 io.ReadCloser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (io.ReadCloser, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) io.ReadCloser); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.ReadCloser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_Logs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Logs' +type mockStrategyTarget_Logs_Call struct { + *mock.Call +} + +// Logs is a helper method to define mock.On call +// - _a0 context.Context +func (_e *mockStrategyTarget_Expecter) Logs(_a0 interface{}) *mockStrategyTarget_Logs_Call { + return &mockStrategyTarget_Logs_Call{Call: _e.mock.On("Logs", _a0)} +} + +func (_c *mockStrategyTarget_Logs_Call) Run(run func(_a0 context.Context)) *mockStrategyTarget_Logs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockStrategyTarget_Logs_Call) Return(_a0 io.ReadCloser, _a1 error) *mockStrategyTarget_Logs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_Logs_Call) RunAndReturn(run func(context.Context) (io.ReadCloser, error)) *mockStrategyTarget_Logs_Call { + _c.Call.Return(run) + return _c +} + +// MappedPort provides a mock function with given fields: _a0, _a1 +func (_m *mockStrategyTarget) MappedPort(_a0 context.Context, _a1 nat.Port) (nat.Port, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for MappedPort") + } + + var r0 nat.Port + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, nat.Port) (nat.Port, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, nat.Port) nat.Port); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(nat.Port) + } + + if rf, ok := ret.Get(1).(func(context.Context, nat.Port) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_MappedPort_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MappedPort' +type mockStrategyTarget_MappedPort_Call struct { + *mock.Call +} + +// MappedPort is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 nat.Port +func (_e *mockStrategyTarget_Expecter) MappedPort(_a0 interface{}, _a1 interface{}) *mockStrategyTarget_MappedPort_Call { + return &mockStrategyTarget_MappedPort_Call{Call: _e.mock.On("MappedPort", _a0, _a1)} +} + +func (_c *mockStrategyTarget_MappedPort_Call) Run(run func(_a0 context.Context, _a1 nat.Port)) *mockStrategyTarget_MappedPort_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(nat.Port)) + }) + return _c +} + +func (_c *mockStrategyTarget_MappedPort_Call) Return(_a0 nat.Port, _a1 error) *mockStrategyTarget_MappedPort_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_MappedPort_Call) RunAndReturn(run func(context.Context, nat.Port) (nat.Port, error)) *mockStrategyTarget_MappedPort_Call { + _c.Call.Return(run) + return _c +} + +// Ports provides a mock function with given fields: ctx +func (_m *mockStrategyTarget) Ports(ctx context.Context) (nat.PortMap, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Ports") + } + + var r0 nat.PortMap + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (nat.PortMap, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) nat.PortMap); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(nat.PortMap) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_Ports_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ports' +type mockStrategyTarget_Ports_Call struct { + *mock.Call +} + +// Ports is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockStrategyTarget_Expecter) Ports(ctx interface{}) *mockStrategyTarget_Ports_Call { + return &mockStrategyTarget_Ports_Call{Call: _e.mock.On("Ports", ctx)} +} + +func (_c *mockStrategyTarget_Ports_Call) Run(run func(ctx context.Context)) *mockStrategyTarget_Ports_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockStrategyTarget_Ports_Call) Return(_a0 nat.PortMap, _a1 error) *mockStrategyTarget_Ports_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_Ports_Call) RunAndReturn(run func(context.Context) (nat.PortMap, error)) *mockStrategyTarget_Ports_Call { + _c.Call.Return(run) + return _c +} + +// State provides a mock function with given fields: _a0 +func (_m *mockStrategyTarget) State(_a0 context.Context) (*types.ContainerState, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for State") + } + + var r0 *types.ContainerState + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*types.ContainerState, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) *types.ContainerState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ContainerState) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockStrategyTarget_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State' +type mockStrategyTarget_State_Call struct { + *mock.Call +} + +// State is a helper method to define mock.On call +// - _a0 context.Context +func (_e *mockStrategyTarget_Expecter) State(_a0 interface{}) *mockStrategyTarget_State_Call { + return &mockStrategyTarget_State_Call{Call: _e.mock.On("State", _a0)} +} + +func (_c *mockStrategyTarget_State_Call) Run(run func(_a0 context.Context)) *mockStrategyTarget_State_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockStrategyTarget_State_Call) Return(_a0 *types.ContainerState, _a1 error) *mockStrategyTarget_State_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockStrategyTarget_State_Call) RunAndReturn(run func(context.Context) (*types.ContainerState, error)) *mockStrategyTarget_State_Call { + _c.Call.Return(run) + return _c +} + +// newMockStrategyTarget creates a new instance of mockStrategyTarget. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockStrategyTarget(t interface { + mock.TestingT + Cleanup(func()) +}) *mockStrategyTarget { + mock := &mockStrategyTarget{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/wait/testdata/tls.pem b/wait/testdata/cert.crt similarity index 100% rename from wait/testdata/tls.pem rename to wait/testdata/cert.crt diff --git a/wait/testdata/tls-key.pem b/wait/testdata/cert.key similarity index 100% rename from wait/testdata/tls-key.pem rename to wait/testdata/cert.key diff --git a/wait/testdata/Dockerfile b/wait/testdata/http/Dockerfile similarity index 100% rename from wait/testdata/Dockerfile rename to wait/testdata/http/Dockerfile diff --git a/wait/testdata/go.mod b/wait/testdata/http/go.mod similarity index 68% rename from wait/testdata/go.mod rename to wait/testdata/http/go.mod index 0b51175838..ab1b280416 100644 --- a/wait/testdata/go.mod +++ b/wait/testdata/http/go.mod @@ -1,3 +1,3 @@ module httptest -go 1.21 +go 1.22 diff --git a/wait/testdata/main.go b/wait/testdata/http/main.go similarity index 91% rename from wait/testdata/main.go rename to wait/testdata/http/main.go index b18008db75..523278ba0b 100644 --- a/wait/testdata/main.go +++ b/wait/testdata/http/main.go @@ -5,6 +5,7 @@ import ( "context" "encoding/base64" "errors" + "fmt" "io" "log" "net/http" @@ -15,7 +16,7 @@ import ( "time" ) -func main() { +func run() error { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) @@ -82,7 +83,7 @@ func main() { go func() { log.Println("serving...") if err := server.ListenAndServeTLS("tls.pem", "tls-key.pem"); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal(err) + log.Println(err) } }() @@ -93,5 +94,15 @@ func main() { log.Println("stopping...") ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - _ = server.Shutdown(ctx) + if err := server.Shutdown(ctx); err != nil { + return fmt.Errorf("shutdown: %w", err) + } + + return nil +} + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } } diff --git a/wait/testdata/http/tls-key.pem b/wait/testdata/http/tls-key.pem new file mode 100644 index 0000000000..00789d2371 --- /dev/null +++ b/wait/testdata/http/tls-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIM8HuDwcZyVqBBy2C6db6zNb/dAJ69bq5ejAEz7qGOIQoAoGCCqGSM49 +AwEHoUQDQgAEBL2ioRmfTc70WT0vyx+amSQOGbMeoMRAfF2qaPzpzOqpKTk0aLOG +0735iy9Fz16PX4vqnLMiM/ZupugAhB//yA== +-----END EC PRIVATE KEY----- diff --git a/wait/testdata/http/tls.pem b/wait/testdata/http/tls.pem new file mode 100644 index 0000000000..46348b7900 --- /dev/null +++ b/wait/testdata/http/tls.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxTCCAWugAwIBAgIUWBLNpiF1o4r+5ZXwawzPOfBM1F8wCgYIKoZIzj0EAwIw +ADAeFw0yMDA4MTkxMzM4MDBaFw0zMDA4MTcxMzM4MDBaMBkxFzAVBgNVBAMTDnRl +c3Rjb250YWluZXJzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBL2ioRmfTc70 +WT0vyx+amSQOGbMeoMRAfF2qaPzpzOqpKTk0aLOG0735iy9Fz16PX4vqnLMiM/Zu +pugAhB//yKOBqTCBpjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH +AwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUTMdz5PIZ+Gix4jYUzRIHfByrW+Yw +HwYDVR0jBBgwFoAUFdfV6PSYUlHs+lSQNouRwSfR2ZgwMQYDVR0RBCowKIIVdGVz +dGNvbnRhaW5lci5nby50ZXN0gglsb2NhbGhvc3SHBH8AAAEwCgYIKoZIzj0EAwID +SAAwRQIhAJznPNumi2Plf0GsP9DpC+8WukT/jUhnhcDWCfZ6Ini2AiBLhnhFebZX +XWfSsdSNxIo20OWvy6z3wqdybZtRUfdU+g== +-----END CERTIFICATE----- diff --git a/wait/tls.go b/wait/tls.go new file mode 100644 index 0000000000..ab904b271e --- /dev/null +++ b/wait/tls.go @@ -0,0 +1,167 @@ +package wait + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "time" +) + +// Validate we implement interface. +var _ Strategy = (*TLSStrategy)(nil) + +// TLSStrategy is a strategy for handling TLS. +type TLSStrategy struct { + // General Settings. + timeout *time.Duration + pollInterval time.Duration + + // Custom Settings. + certFiles *x509KeyPair + rootFiles []string + + // State. + tlsConfig *tls.Config +} + +// x509KeyPair is a pair of certificate and key files. +type x509KeyPair struct { + certPEMFile string + keyPEMFile string +} + +// ForTLSCert returns a CertStrategy that will add a Certificate to the [tls.Config] +// constructed from PEM formatted certificate key file pair in the container. +func ForTLSCert(certPEMFile, keyPEMFile string) *TLSStrategy { + return &TLSStrategy{ + certFiles: &x509KeyPair{ + certPEMFile: certPEMFile, + keyPEMFile: keyPEMFile, + }, + tlsConfig: &tls.Config{}, + pollInterval: defaultPollInterval(), + } +} + +// ForTLSRootCAs returns a CertStrategy that sets the root CAs for the [tls.Config] +// using the given PEM formatted files from the container. +func ForTLSRootCAs(pemFiles ...string) *TLSStrategy { + return &TLSStrategy{ + rootFiles: pemFiles, + tlsConfig: &tls.Config{}, + pollInterval: defaultPollInterval(), + } +} + +// WithRootCAs sets the root CAs for the [tls.Config] using the given files from +// the container. +func (ws *TLSStrategy) WithRootCAs(files ...string) *TLSStrategy { + ws.rootFiles = files + return ws +} + +// WithCert sets the [tls.Config] Certificates using the given files from the container. +func (ws *TLSStrategy) WithCert(certPEMFile, keyPEMFile string) *TLSStrategy { + ws.certFiles = &x509KeyPair{ + certPEMFile: certPEMFile, + keyPEMFile: keyPEMFile, + } + return ws +} + +// WithServerName sets the server for the [tls.Config]. +func (ws *TLSStrategy) WithServerName(serverName string) *TLSStrategy { + ws.tlsConfig.ServerName = serverName + return ws +} + +// WithStartupTimeout can be used to change the default startup timeout. +func (ws *TLSStrategy) WithStartupTimeout(startupTimeout time.Duration) *TLSStrategy { + ws.timeout = &startupTimeout + return ws +} + +// WithPollInterval can be used to override the default polling interval of 100 milliseconds. +func (ws *TLSStrategy) WithPollInterval(pollInterval time.Duration) *TLSStrategy { + ws.pollInterval = pollInterval + return ws +} + +// TLSConfig returns the TLS config once the strategy is ready. +// If the strategy is nil, it returns nil. +func (ws *TLSStrategy) TLSConfig() *tls.Config { + if ws == nil { + return nil + } + + return ws.tlsConfig +} + +// WaitUntilReady implements the [Strategy] interface. +// It waits for the CA, client cert and key files to be available in the container and +// uses them to setup the TLS config. +func (ws *TLSStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error { + size := len(ws.rootFiles) + if ws.certFiles != nil { + size += 2 + } + strategies := make([]Strategy, 0, size) + for _, file := range ws.rootFiles { + strategies = append(strategies, + ForFile(file).WithMatcher(func(r io.Reader) error { + buf, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("read CA cert file %q: %w", file, err) + } + + if ws.tlsConfig.RootCAs == nil { + ws.tlsConfig.RootCAs = x509.NewCertPool() + } + + if !ws.tlsConfig.RootCAs.AppendCertsFromPEM(buf) { + return fmt.Errorf("invalid CA cert file %q", file) + } + + return nil + }).WithPollInterval(ws.pollInterval), + ) + } + + if ws.certFiles != nil { + var certPEMBlock []byte + strategies = append(strategies, + ForFile(ws.certFiles.certPEMFile).WithMatcher(func(r io.Reader) error { + var err error + if certPEMBlock, err = io.ReadAll(r); err != nil { + return fmt.Errorf("read certificate cert %q: %w", ws.certFiles.certPEMFile, err) + } + + return nil + }).WithPollInterval(ws.pollInterval), + ForFile(ws.certFiles.keyPEMFile).WithMatcher(func(r io.Reader) error { + keyPEMBlock, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("read certificate key %q: %w", ws.certFiles.keyPEMFile, err) + } + + cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err != nil { + return fmt.Errorf("x509 key pair %q %q: %w", ws.certFiles.certPEMFile, ws.certFiles.keyPEMFile, err) + } + + ws.tlsConfig.Certificates = []tls.Certificate{cert} + + return nil + }).WithPollInterval(ws.pollInterval), + ) + } + + strategy := ForAll(strategies...) + if ws.timeout != nil { + strategy.WithStartupTimeout(*ws.timeout) + } + + return strategy.WaitUntilReady(ctx, target) +} diff --git a/wait/tls_test.go b/wait/tls_test.go new file mode 100644 index 0000000000..babc17b3d0 --- /dev/null +++ b/wait/tls_test.go @@ -0,0 +1,150 @@ +package wait_test + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + _ "embed" + "errors" + "fmt" + "io" + "log" + "testing" + "time" + + "github.com/docker/docker/errdefs" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + serverName = "127.0.0.1" + caFilename = "/tmp/ca.pem" + clientCertFilename = "/tmp/cert.crt" + clientKeyFilename = "/tmp/cert.key" +) + +var ( + //go:embed testdata/cert.crt + certBytes []byte + + //go:embed testdata/cert.key + keyBytes []byte +) + +// testForTLSCert creates a new CertStrategy for testing. +func testForTLSCert() *wait.TLSStrategy { + return wait.ForTLSCert(clientCertFilename, clientKeyFilename). + WithRootCAs(caFilename). + WithServerName(serverName). + WithStartupTimeout(time.Millisecond * 50). + WithPollInterval(time.Millisecond) +} + +func TestForCert(t *testing.T) { + errNotFound := errdefs.NotFound(errors.New("file not found")) + ctx := context.Background() + + t.Run("ca-not-found", func(t *testing.T) { + target := newRunningTarget() + target.EXPECT().CopyFileFromContainer(anyContext, caFilename).Return(nil, errNotFound) + err := testForTLSCert().WaitUntilReady(ctx, target) + require.EqualError(t, err, context.DeadlineExceeded.Error()) + }) + + t.Run("cert-not-found", func(t *testing.T) { + target := newRunningTarget() + caFile := io.NopCloser(bytes.NewBuffer(caBytes)) + target.EXPECT().CopyFileFromContainer(anyContext, caFilename).Return(caFile, nil) + target.EXPECT().CopyFileFromContainer(anyContext, clientCertFilename).Return(nil, errNotFound) + err := testForTLSCert().WaitUntilReady(ctx, target) + require.EqualError(t, err, context.DeadlineExceeded.Error()) + }) + + t.Run("key-not-found", func(t *testing.T) { + target := newRunningTarget() + caFile := io.NopCloser(bytes.NewBuffer(caBytes)) + certFile := io.NopCloser(bytes.NewBuffer(certBytes)) + target.EXPECT().CopyFileFromContainer(anyContext, caFilename).Return(caFile, nil) + target.EXPECT().CopyFileFromContainer(anyContext, clientCertFilename).Return(certFile, nil) + target.EXPECT().CopyFileFromContainer(anyContext, clientKeyFilename).Return(nil, errNotFound) + err := testForTLSCert().WaitUntilReady(ctx, target) + require.EqualError(t, err, context.DeadlineExceeded.Error()) + }) + + t.Run("valid", func(t *testing.T) { + target := newRunningTarget() + caFile := io.NopCloser(bytes.NewBuffer(caBytes)) + certFile := io.NopCloser(bytes.NewBuffer(certBytes)) + keyFile := io.NopCloser(bytes.NewBuffer(keyBytes)) + target.EXPECT().CopyFileFromContainer(anyContext, caFilename).Return(caFile, nil) + target.EXPECT().CopyFileFromContainer(anyContext, clientCertFilename).Return(certFile, nil) + target.EXPECT().CopyFileFromContainer(anyContext, clientKeyFilename).Return(keyFile, nil) + + certStrategy := testForTLSCert() + err := certStrategy.WaitUntilReady(ctx, target) + require.NoError(t, err) + + pool := x509.NewCertPool() + require.True(t, pool.AppendCertsFromPEM(caBytes)) + cert, err := tls.X509KeyPair(certBytes, keyBytes) + require.NoError(t, err) + got := certStrategy.TLSConfig() + require.Equal(t, serverName, got.ServerName) + require.Equal(t, []tls.Certificate{cert}, got.Certificates) + require.True(t, pool.Equal(got.RootCAs)) + }) +} + +func ExampleForTLSCert() { + ctx := context.Background() + + // waitForTLSCert { + // The file names passed to ForTLSCert are the paths where the files will + // be copied to in the container as detailed by the Dockerfile. + forCert := wait.ForTLSCert("/app/tls.pem", "/app/tls-key.pem"). + WithServerName("testcontainer.go.test") + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: "testdata/http", + }, + WaitingFor: forCert, + } + // } + + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + defer func() { + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + state, err := c.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // waitTLSConfig { + config := forCert.TLSConfig() + // } + fmt.Println(config.ServerName) + fmt.Println(len(config.Certificates)) + + // Output: + // true + // testcontainer.go.test + // 1 +} diff --git a/wait/wait.go b/wait/wait.go index 46a66f7dfa..7211d49b2d 100644 --- a/wait/wait.go +++ b/wait/wait.go @@ -25,17 +25,19 @@ type StrategyTimeout interface { type StrategyTarget interface { Host(context.Context) (string, error) - Ports(ctx context.Context) (nat.PortMap, error) + Inspect(context.Context) (*types.ContainerJSON, error) + Ports(ctx context.Context) (nat.PortMap, error) // Deprecated: use Inspect instead MappedPort(context.Context, nat.Port) (nat.Port, error) Logs(context.Context) (io.ReadCloser, error) Exec(context.Context, []string, ...exec.ProcessOption) (int, io.Reader, error) State(context.Context) (*types.ContainerState, error) + CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error) } func checkTarget(ctx context.Context, target StrategyTarget) error { state, err := target.State(ctx) if err != nil { - return err + return fmt.Errorf("get state: %w", err) } return checkState(state) diff --git a/wait/wait_test.go b/wait/wait_test.go index 0e07ba87b6..b8c6248703 100644 --- a/wait/wait_test.go +++ b/wait/wait_test.go @@ -14,20 +14,32 @@ import ( var ErrPortNotFound = errors.New("port not found") type MockStrategyTarget struct { - HostImpl func(context.Context) (string, error) - PortsImpl func(context.Context) (nat.PortMap, error) - MappedPortImpl func(context.Context, nat.Port) (nat.Port, error) - LogsImpl func(context.Context) (io.ReadCloser, error) - ExecImpl func(context.Context, []string, ...tcexec.ProcessOption) (int, io.Reader, error) - StateImpl func(context.Context) (*types.ContainerState, error) + HostImpl func(context.Context) (string, error) + InspectImpl func(context.Context) (*types.ContainerJSON, error) + PortsImpl func(context.Context) (nat.PortMap, error) + MappedPortImpl func(context.Context, nat.Port) (nat.Port, error) + LogsImpl func(context.Context) (io.ReadCloser, error) + ExecImpl func(context.Context, []string, ...tcexec.ProcessOption) (int, io.Reader, error) + StateImpl func(context.Context) (*types.ContainerState, error) + CopyFileFromContainerImpl func(context.Context, string) (io.ReadCloser, error) } func (st MockStrategyTarget) Host(ctx context.Context) (string, error) { return st.HostImpl(ctx) } +func (st MockStrategyTarget) Inspect(ctx context.Context) (*types.ContainerJSON, error) { + return st.InspectImpl(ctx) +} + +// Deprecated: use Inspect instead func (st MockStrategyTarget) Ports(ctx context.Context) (nat.PortMap, error) { - return st.PortsImpl(ctx) + inspect, err := st.InspectImpl(ctx) + if err != nil { + return nil, err + } + + return inspect.NetworkSettings.Ports, nil } func (st MockStrategyTarget) MappedPort(ctx context.Context, port nat.Port) (nat.Port, error) { @@ -45,3 +57,7 @@ func (st MockStrategyTarget) Exec(ctx context.Context, cmd []string, options ... func (st MockStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) { return st.StateImpl(ctx) } + +func (st MockStrategyTarget) CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error) { + return st.CopyFileFromContainerImpl(ctx, filePath) +} diff --git a/wait/walk.go b/wait/walk.go new file mode 100644 index 0000000000..4685e50088 --- /dev/null +++ b/wait/walk.go @@ -0,0 +1,74 @@ +package wait + +import ( + "errors" +) + +var ( + // VisitStop is used as a return value from [VisitFunc] to stop the walk. + // It is not returned as an error by any function. + VisitStop = errors.New("stop the walk") + + // VisitRemove is used as a return value from [VisitFunc] to have the current node removed. + // It is not returned as an error by any function. + VisitRemove = errors.New("remove this strategy") +) + +// VisitFunc is a function that visits a strategy node. +// If it returns [VisitStop], the walk stops. +// If it returns [VisitRemove], the current node is removed. +type VisitFunc func(root Strategy) error + +// Walk walks the strategies tree and calls the visit function for each node. +func Walk(root *Strategy, visit VisitFunc) error { + if root == nil { + return errors.New("root strategy is nil") + } + + if err := walk(root, visit); err != nil { + if errors.Is(err, VisitRemove) || errors.Is(err, VisitStop) { + return nil + } + return err + } + + return nil +} + +// walk walks the strategies tree and calls the visit function for each node. +// It returns an error if the visit function returns an error. +func walk(root *Strategy, visit VisitFunc) error { + if *root == nil { + // No strategy. + return nil + } + + // Allow the visit function to customize the behaviour of the walk before visiting the children. + if err := visit(*root); err != nil { + if errors.Is(err, VisitRemove) { + *root = nil + } + + return err + } + + if s, ok := (*root).(*MultiStrategy); ok { + var i int + for range s.Strategies { + if err := walk(&s.Strategies[i], visit); err != nil { + if errors.Is(err, VisitRemove) { + s.Strategies = append(s.Strategies[:i], s.Strategies[i+1:]...) + if errors.Is(err, VisitStop) { + return VisitStop + } + continue + } + + return err + } + i++ + } + } + + return nil +} diff --git a/wait/walk_test.go b/wait/walk_test.go new file mode 100644 index 0000000000..e8f8df2f2b --- /dev/null +++ b/wait/walk_test.go @@ -0,0 +1,127 @@ +package wait_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestWalk(t *testing.T) { + req := testcontainers.ContainerRequest{ + WaitingFor: wait.ForAll( + wait.ForFile("/tmp/file"), + wait.ForHTTP("/health"), + wait.ForAll( + wait.ForFile("/tmp/other"), + ), + ), + } + + t.Run("walk", func(t *testing.T) { + var count int + err := wait.Walk(&req.WaitingFor, func(_ wait.Strategy) error { + count++ + return nil + }) + require.NoError(t, err) + require.Equal(t, 5, count) + }) + + t.Run("stop", func(t *testing.T) { + var count int + err := wait.Walk(&req.WaitingFor, func(_ wait.Strategy) error { + count++ + return wait.VisitStop + }) + require.NoError(t, err) + require.Equal(t, 1, count) + }) + + t.Run("remove", func(t *testing.T) { + // walkRemoveFileStrategy { + var count, matched int + err := wait.Walk(&req.WaitingFor, func(s wait.Strategy) error { + count++ + if _, ok := s.(*wait.FileStrategy); ok { + matched++ + return wait.VisitRemove + } + + return nil + }) + // } + require.NoError(t, err) + require.Equal(t, 5, count) + require.Equal(t, 2, matched) + + count = 0 + matched = 0 + err = wait.Walk(&req.WaitingFor, func(s wait.Strategy) error { + count++ + if _, ok := s.(*wait.FileStrategy); ok { + matched++ + } + return nil + }) + require.NoError(t, err) + require.Equal(t, 3, count) + require.Zero(t, matched) + }) + + t.Run("remove-stop", func(t *testing.T) { + req := testcontainers.ContainerRequest{ + WaitingFor: wait.ForAll( + wait.ForFile("/tmp/file"), + wait.ForHTTP("/health"), + ), + } + var count int + err := wait.Walk(&req.WaitingFor, func(_ wait.Strategy) error { + count++ + return errors.Join(wait.VisitRemove, wait.VisitStop) + }) + require.NoError(t, err) + require.Equal(t, 1, count) + require.Nil(t, req.WaitingFor) + }) + + t.Run("nil-root", func(t *testing.T) { + err := wait.Walk(nil, func(_ wait.Strategy) error { + return nil + }) + require.EqualError(t, err, "root strategy is nil") + }) + + t.Run("direct-single", func(t *testing.T) { + req := testcontainers.ContainerRequest{ + WaitingFor: wait.ForFile("/tmp/file"), + } + requireVisits(t, req, 1) + }) + + t.Run("for-all-single", func(t *testing.T) { + req := testcontainers.ContainerRequest{ + WaitingFor: wait.ForAll( + wait.ForFile("/tmp/file"), + ), + } + requireVisits(t, req, 2) + }) +} + +// requireVisits validates the number of visits for a given request. +func requireVisits(t *testing.T, req testcontainers.ContainerRequest, expected int) { + t.Helper() + + var count int + err := wait.Walk(&req.WaitingFor, func(_ wait.Strategy) error { + count++ + return nil + }) + require.NoError(t, err) + require.Equal(t, expected, count) +}