Merge pull request #51685 from vvoland/sync-29.x

[docker-29.x] Move to `29.1`
This commit is contained in:
Paweł Gronowski
2025-12-11 15:25:58 +00:00
committed by GitHub
1509 changed files with 159305 additions and 29766 deletions

View File

@@ -6,9 +6,18 @@ runs:
steps:
- run: |
set -e
# Jaeger is set up on Windows through an inline run step. If you update Jaeger here, don't forget to update
# the version set in .github/workflows/.windows.yml.
docker run -d --net=host --name jaeger -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.46
# The OTEL Collector is set up on Windows through an inline run step. If
# you update the collector here, don't forget to update the version set
# in .github/workflows/.windows.yml.
mkdir -p /tmp/reports
chmod 777 /tmp/reports
docker run -d --net=host --name otelcol \
-v "$(pwd)/otelcol-ci-config.yml:/etc/otelcol-contrib/config.yaml" \
-v "/tmp/reports:/data" \
otel/opentelemetry-collector-contrib:0.140.0 \
--config file:/etc/otelcol-contrib/config.yaml \
--config "yaml:exporters::file::path: /data/otel-trace.jsonl"
docker0_ip="$(ip -f inet addr show docker0 | grep -Po 'inet \K[\d.]+')"
echo "OTEL_EXPORTER_OTLP_ENDPOINT=http://${docker0_ip}:4318" >> "${GITHUB_ENV}"
echo "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf" >> "${GITHUB_ENV}"
shell: bash

View File

@@ -25,7 +25,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-

View File

@@ -16,7 +16,7 @@ on:
workflow_call:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
SETUP_BUILDX_VERSION: edge
@@ -36,7 +36,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -87,7 +87,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-unit--${{ matrix.mode }}
path: /tmp/reports/*
@@ -103,13 +103,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
pattern: test-reports-unit-*
path: /tmp/reports

View File

@@ -21,7 +21,7 @@ on:
default: "graphdriver"
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
ITG_CLI_MATRIX_SIZE: 6
@@ -39,7 +39,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -68,13 +68,12 @@ jobs:
name: Prepare reports
if: always()
run: |
docker stop otelcol
mkdir -p bundles /tmp/reports
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C /tmp/reports
sudo chown -R $(id -u):$(id -g) /tmp/reports
tree -nh /tmp/reports
curl -sSLf localhost:16686/api/traces?service=integration-test-client > /tmp/reports/jaeger-trace.json
-
name: Test daemon logs
if: always()
@@ -83,7 +82,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-docker-py-${{ inputs.storage }}
path: /tmp/reports/*
@@ -96,7 +95,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -170,7 +169,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -228,13 +227,13 @@ jobs:
reportsPath="/tmp/reports/$reportsName"
echo "TESTREPORTS_NAME=$reportsName" >> $GITHUB_ENV
docker stop otelcol
mkdir -p bundles $reportsPath
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -251,7 +250,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -267,13 +266,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-${{ inputs.storage }}-*
@@ -296,10 +295,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -394,7 +393,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -443,14 +442,14 @@ jobs:
reportsPath=/tmp/reports/$reportsName
echo "TESTREPORTS_NAME=$reportsName" >> $GITHUB_ENV
docker stop otelcol
mkdir -p bundles $reportsPath
echo "${{ matrix.test }}" | tr -s '|' '\n' | tee -a "$reportsPath/tests.txt"
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -467,7 +466,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -483,13 +482,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-*

View File

@@ -20,7 +20,7 @@ on:
type: string
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
TESTSTAT_VERSION: v0.1.25
jobs:
@@ -39,7 +39,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Lima
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
@@ -167,7 +167,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -183,7 +183,7 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -192,7 +192,7 @@ jobs:
run: echo "TESTREPORTS_NAME=$(basename ${{ inputs.template }})*" >> $GITHUB_ENV
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-${{ env.TESTREPORTS_NAME }}

View File

@@ -28,7 +28,7 @@ on:
default: false
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
@@ -53,7 +53,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
@@ -98,7 +98,7 @@ jobs:
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd-shim-runhcs-v1.exe" ${{ env.BIN_OUT }}\
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-${{ inputs.storage }}-${{ inputs.os }}
path: ${{ env.BIN_OUT }}/*
@@ -117,7 +117,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
@@ -166,7 +166,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
@@ -181,13 +181,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
path: /tmp/artifacts
@@ -208,10 +208,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -264,24 +264,27 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Set up Jaeger
name: Set up OpenTelemetry Collector
run: |
# Jaeger is set up on Linux through the setup-tracing action. If you update Jaeger here, don't forget to
# The collectors is set up on Linux through the setup-tracing action. If you update the collector here, don't forget to
# update the version set in .github/actions/setup-tracing/action.yml.
Invoke-WebRequest -Uri "https://github.com/jaegertracing/jaeger/releases/download/v1.46.0/jaeger-1.46.0-windows-amd64.tar.gz" -OutFile ".\jaeger-1.46.0-windows-amd64.tar.gz"
tar -zxvf ".\jaeger-1.46.0-windows-amd64.tar.gz"
Start-Process '.\jaeger-1.46.0-windows-amd64\jaeger-all-in-one.exe'
echo "OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
New-Item -ItemType Directory -Force -Path bundles -ErrorAction Continue
Start-Process "msiexec" -ArgumentList "/i",
"https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.140.0/otelcol_0.140.0_windows_x64.msi",
"/qn", "/l*v", "$(Join-Path (Get-Location) "bundles/otelcol-install.log")",
"COLLECTOR_SVC_ARGS=`"--config=`"`"file:$(Join-Path (Get-Location) "otelcol-ci-config.yml")`"`" --config=`"`"yaml:exporters::file::path: $(Join-Path (Get-Location) "bundles/otel-trace.jsonl")`"`"`"" `
-NoNewWindow -Wait
@("OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318", "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf") | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
shell: pwsh
-
name: Env
@@ -289,14 +292,14 @@ jobs:
Get-ChildItem Env: | Out-String
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: build-${{ inputs.storage }}-${{ inputs.os }}
path: ${{ env.BIN_OUT }}
-
name: Init
run: |
New-Item -ItemType "directory" -Path "bundles"
New-Item -ItemType "directory" -Path "bundles" -ErrorAction SilentlyContinue
If ("${{ inputs.os }}" -eq "windows-2025") {
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
@@ -342,14 +345,14 @@ jobs:
"--exec-root=$env:TEMP\moby-exec", `
"--pidfile=$env:TEMP\docker.pid", `
"--register-service"
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
$dockerEnviron = @("DOCKER_MIN_API_VERSION=1.24")
$dockerEnviron += @(Get-Item Env:\OTEL_* | ForEach-Object { "$($_.Name)=$($_.Value)" })
If ("${{ inputs.storage }}" -eq "graphdriver") {
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d "DOCKER_MIN_API_VERSION=1.24@TEST_INTEGRATION_USE_GRAPHDRIVER=1"
$dockerEnviron += @("TEST_INTEGRATION_USE_GRAPHDRIVER=1")
echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} Else {
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d DOCKER_MIN_API_VERSION=1.24
}
New-ItemProperty -Name "Environment" -Path "HKLM:\SYSTEM\CurrentControlSet\Services\docker" -PropertyType MultiString -Value $dockerEnviron
Write-Host "Starting service"
Start-Service -Name docker
Write-Host "Service started successfully!"
@@ -459,16 +462,14 @@ jobs:
ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} |
Tee-Object -file ".\bundles\daemon.log"
-
name: Download Jaeger traces
name: Stop OpenTelemetry Collector
if: always()
run: |
Invoke-WebRequest `
-Uri "http://127.0.0.1:16686/api/traces?service=integration-test-client" `
-OutFile ".\bundles\jaeger-trace.json"
(Stop-Service -DisplayName "OpenTelemetry Collector" -PassThru).WaitForStatus('Stopped', (New-TimeSpan -Seconds 30))
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-${{ env.TESTREPORTS_NAME }}
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
@@ -495,13 +496,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-*

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
TESTSTAT_VERSION: v0.1.25
DESTDIR: ./build
SETUP_BUILDX_VERSION: edge
@@ -101,7 +101,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -146,7 +146,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-unit-arm64-graphdriver
path: /tmp/reports/*
@@ -162,13 +162,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
pattern: test-reports-unit-arm64-*
path: /tmp/reports
@@ -191,7 +191,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -226,13 +226,14 @@ jobs:
name: Prepare reports
if: always()
run: |
docker stop otelcol
reportsPath="/tmp/reports/arm64-graphdriver"
mkdir -p bundles $reportsPath
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -249,7 +250,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-arm64-graphdriver
path: /tmp/reports/*
@@ -265,13 +266,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-arm64-*

View File

@@ -49,7 +49,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Docker meta
id: meta
@@ -83,7 +83,7 @@ jobs:
mv "${bakeFile#cwd://}" "/tmp/bake-meta.json"
-
name: Upload meta bake definition
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: bake-meta
path: /tmp/bake-meta.json
@@ -109,7 +109,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-
@@ -119,7 +119,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Download meta bake definition
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: bake-meta
path: /tmp
@@ -164,7 +164,7 @@ jobs:
-
name: Upload digest
if: github.event_name != 'pull_request' && github.repository == 'moby/moby'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -180,13 +180,13 @@ jobs:
steps:
-
name: Download meta bake definition
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: bake-meta
path: /tmp
-
name: Download digests
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/digests
pattern: digests-*

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
DESTDIR: ./build
SETUP_BUILDX_VERSION: edge
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
@@ -53,7 +53,7 @@ jobs:
targets: binary
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: binary
path: ${{ env.DESTDIR }}
@@ -100,12 +100,12 @@ jobs:
uses: crazy-max/ghaction-github-runtime@v3
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: moby
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -116,7 +116,7 @@ jobs:
working-directory: moby
-
name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ env.BUILDKIT_REPO }}
ref: ${{ env.BUILDKIT_REF }}
@@ -133,7 +133,7 @@ jobs:
buildkitd-flags: --debug
-
name: Download binary artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: binary
path: ./buildkit/build/moby/
@@ -184,7 +184,7 @@ jobs:
working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
@@ -199,7 +199,7 @@ jobs:
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -225,7 +225,7 @@ jobs:
go install github.com/distribution/distribution/v3/cmd/registry@latest
- name: Checkout BuildKit
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: moby/buildkit
ref: master
@@ -248,7 +248,7 @@ jobs:
cp ${{ env.GOPATH }}\bin\buildctl.exe ${{ env.BIN_OUT }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-windows
path: ${{ env.BIN_OUT }}/*
@@ -307,12 +307,12 @@ jobs:
uses: crazy-max/ghaction-github-runtime@v3
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: moby
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -324,14 +324,14 @@ jobs:
working-directory: moby
- name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ env.BUILDKIT_REPO }}
ref: ${{ env.BUILDKIT_REF }}
path: buildkit
- name: Download Moby artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: build-windows
path: ${{ env.BIN_OUT }}

View File

@@ -75,7 +75,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: platforms

View File

@@ -34,7 +34,7 @@ on:
- cron: '0 9 * * 4'
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
jobs:
codeql:
@@ -47,11 +47,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 2
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GIT_PAGER: "cat"
PAGER: "cat"
SETUP_BUILDX_VERSION: edge
@@ -67,7 +67,14 @@ jobs:
set: |
*.cache-from=type=gha,scope=dev${{ matrix.mode }}
*.cache-to=type=gha,scope=dev${{ matrix.mode }}
*.output=type=cacheonly
${{ matrix.mode == '' && '*.output=type=docker,dest=/tmp/dev-image.tar' || '*.output=type=cacheonly' }}
-
name: Cache dev image
if: matrix.mode == ''
uses: actions/cache/save@v4
with:
key: dev-image-${{ github.run_id }}
path: /tmp/dev-image.tar
test:
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
@@ -103,7 +110,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: scripts
@@ -122,13 +129,12 @@ jobs:
- validate-prepare
- build-dev
strategy:
fail-fast: true
matrix:
script: ${{ fromJson(needs.validate-prepare.outputs.matrix) }}
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-
@@ -139,15 +145,19 @@ jobs:
uses: docker/setup-buildx-action@v3
with:
version: ${{ env.SETUP_BUILDX_VERSION }}
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
driver: docker
buildkitd-flags: --debug
-
name: Build dev image
uses: docker/bake-action@v6
name: Restore dev image
uses: actions/cache/restore@v4
with:
targets: dev
set: |
dev.cache-from=type=gha,scope=dev
key: dev-image-${{ github.run_id }}
path: /tmp/dev-image.tar
fail-on-cache-miss: true
-
name: Load dev image
run: |
docker load -i /tmp/dev-image.tar
-
name: Validate
run: |
@@ -164,7 +174,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: platforms

View File

@@ -3,7 +3,7 @@ version: "2"
run:
# prevent golangci-lint from deducting the go version to lint for through go.mod,
# which causes it to fallback to go1.17 semantics.
go: "1.25.4"
go: "1.25.5"
concurrency: 2
# Only supported with go modules enabled (build flag -mod=vendor only valid when using modules)
# modules-download-mode: vendor

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
ARG BASE_DEBIAN_DISTRO="bookworm"
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
@@ -13,7 +13,7 @@ ARG XX_VERSION=1.7.0
ARG VPNKIT_VERSION=0.6.0
# DOCKERCLI_VERSION is the version of the CLI to install in the dev-container.
ARG DOCKERCLI_VERSION=v28.5.0
ARG DOCKERCLI_VERSION=v29.0.1
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
# cli version used for integration-cli tests
@@ -21,7 +21,7 @@ ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
ARG DOCKERCLI_INTEGRATION_VERSION=v25.0.5
# BUILDX_VERSION is the version of buildx to install in the dev container.
ARG BUILDX_VERSION=0.29.1
ARG BUILDX_VERSION=0.30.1
# COMPOSE_VERSION is the version of compose to install in the dev container.
ARG COMPOSE_VERSION=v2.40.0
@@ -163,7 +163,7 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
# integration tests. The distributed docker .deb and .rpm packages depend on a
# separate (containerd.io) package, which may be a different version as is
# specified here.
ARG CONTAINERD_VERSION=v2.1.5
ARG CONTAINERD_VERSION=v2.2.0
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
FROM base AS containerd-build
@@ -254,7 +254,7 @@ RUN git init . && git remote add origin "https://github.com/opencontainers/runc.
# This version should usually match the version that is used by the containerd version
# that is used. If you need to update runc, open a pull request in the containerd
# project first, and update both after that is merged.
ARG RUNC_VERSION=v1.3.3
ARG RUNC_VERSION=v1.3.4
RUN git fetch -q --depth 1 origin "${RUNC_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
FROM base AS runc-build

View File

@@ -5,7 +5,7 @@
# This represents the bare minimum required to build and test Docker.
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
ARG BASE_DEBIAN_DISTRO="bookworm"
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"

View File

@@ -161,7 +161,7 @@ FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
# Use PowerShell as the default shell
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
ARG GOTESTSUM_VERSION=v1.13.0

View File

@@ -23,19 +23,28 @@ import (
)
func main() {
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
// Create a new client that handles common environment variables
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv)
if err != nil {
panic(err)
}
defer apiClient.Close()
containers, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{All: true})
// List all containers (both stopped and running).
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{
All: true,
})
if err != nil {
panic(err)
}
for _, ctr := range containers {
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
// Print each container's ID, status and the image it was created from.
fmt.Printf("%s %-22s %s\n", "ID", "STATUS", "IMAGE")
for _, ctr := range result.Items {
fmt.Printf("%s %-22s %s\n", ctr.ID, ctr.Status, ctr.Image)
}
}
```

View File

@@ -8,10 +8,8 @@ https://docs.docker.com/reference/api/engine/
You use the library by constructing a client object using [New]
and calling methods on it. The client can be configured from environment
variables by passing the [FromEnv] option, and the [WithAPIVersionNegotiation]
option to allow downgrading the API version used when connecting with an older
daemon version. Other options cen be configured manually by passing any of
the available [Opt] options.
variables by passing the [FromEnv] option. Other options can be configured
manually by passing any of the available [Opt] options.
For example, to list running containers (the equivalent of "docker ps"):
@@ -30,7 +28,7 @@ For example, to list running containers (the equivalent of "docker ps"):
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv)
if err != nil {
log.Fatal(err)
}
@@ -103,18 +101,16 @@ import (
const DummyHost = "api.moby.localhost"
// MaxAPIVersion is the highest REST API version supported by the client.
// If API-version negotiation is enabled (see [WithAPIVersionNegotiation],
// [Client.NegotiateAPIVersion]), the client may downgrade its API version.
// Similarly, the [WithVersion] and [WithVersionFromEnv] allow overriding
// the version.
// If API-version negotiation is enabled, the client may downgrade its API version.
// Similarly, the [WithAPIVersion] and [WithAPIVersionFromEnv] options allow
// overriding the version and disable API-version negotiation.
//
// This version may be lower than the version of the api library module used.
const MaxAPIVersion = "1.52"
// fallbackAPIVersion is the version to fall back to if API-version negotiation
// fails. API versions below this version are not supported by the client,
// and not considered when negotiating.
const fallbackAPIVersion = "1.44"
// MinAPIVersion is the minimum API version supported by the client. API versions
// below this version are not considered when performing API-version negotiation.
const MinAPIVersion = "1.44"
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}
@@ -174,8 +170,13 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
// It takes an optional list of [Opt] functional arguments, which are applied in
// the order they're provided, which allows modifying the defaults when creating
// the client. For example, the following initializes a client that configures
// itself with values from environment variables ([FromEnv]), and has automatic
// API version negotiation enabled ([WithAPIVersionNegotiation]).
// itself with values from environment variables ([FromEnv]).
//
// By default, the client automatically negotiates the API version to use when
// making requests. API version negotiation is performed on the first request;
// subsequent requests do not re-negotiate. Use [WithAPIVersion] or
// [WithAPIVersionFromEnv] to configure the client with a fixed API version
// and disable API version negotiation.
//
// cli, err := client.New(
// client.FromEnv,
@@ -213,6 +214,12 @@ func New(ops ...Opt) (*Client, error) {
}
}
if cfg.envAPIVersion != "" {
c.setAPIVersion(cfg.envAPIVersion)
} else if cfg.manualAPIVersion != "" {
c.setAPIVersion(cfg.manualAPIVersion)
}
if tr, ok := c.client.Transport.(*http.Transport); ok {
// Store the base transport before we wrap it in tracing libs below
// This is used, as an example, to close idle connections when the client is closed
@@ -278,7 +285,7 @@ func (cli *Client) Close() error {
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) error {
if cli.manualOverride || !cli.negotiateVersion || cli.negotiated.Load() {
if cli.negotiated.Load() {
return nil
}
_, err := cli.Ping(ctx, PingOptions{
@@ -306,36 +313,47 @@ func (cli *Client) ClientVersion() string {
}
// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than
// the lowest supported API version, in which case the version is not modified.
// the ping response.
//
// It returns an error if version is invalid, or lower than the minimum
// supported API version in which case the client's API version is not
// updated, and negotiation is not marked as completed.
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
pingVersion = strings.TrimPrefix(pingVersion, "v")
if pingVersion == "" {
// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
pingVersion = fallbackAPIVersion
} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
var err error
pingVersion, err = parseAPIVersion(pingVersion)
if err != nil {
return err
}
if versions.LessThan(pingVersion, MinAPIVersion) {
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, MinAPIVersion))
}
// if the client is not initialized with a version, start with the latest supported version
if cli.version == "" {
cli.version = MaxAPIVersion
negotiatedVersion := cli.version
if negotiatedVersion == "" {
negotiatedVersion = MaxAPIVersion
}
// if server version is lower than the client version, downgrade
if versions.LessThan(pingVersion, cli.version) {
cli.version = pingVersion
if versions.LessThan(pingVersion, negotiatedVersion) {
negotiatedVersion = pingVersion
}
// Store the results, so that automatic API version negotiation (if enabled)
// won't be performed on the next request.
if cli.negotiateVersion {
cli.negotiated.Store(true)
}
cli.setAPIVersion(negotiatedVersion)
return nil
}
// setAPIVersion sets the client's API version and marks API version negotiation
// as completed, so that automatic API version negotiation (if enabled) won't
// be performed on the next request.
func (cli *Client) setAPIVersion(version string) {
cli.version = version
cli.negotiated.Store(true)
}
// DaemonHost returns the host address used by the client
func (cli *Client) DaemonHost() string {
return cli.host

View File

@@ -13,10 +13,11 @@ func ExampleNew() {
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv)
if err != nil {
log.Fatal(err)
}
defer apiClient.Close()
// List all containers (both stopped and running).
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{

View File

@@ -5,9 +5,13 @@ import (
"fmt"
"io"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/common"
"github.com/moby/moby/api/types/swarm"
)
// defaultAPIPath is the API path prefix for the default API version used.
@@ -40,20 +44,88 @@ func assertRequestWithQuery(req *http.Request, expMethod string, expectedPath st
return nil
}
// ensureBody makes sure the response has a Body, using [http.NoBody] if
// none is present, and returns it as a testRoundTripper.
// ensureBody makes sure the response has a Body (using [http.NoBody] if
// none is present), and that the request is set on the response, then returns
// it as a testRoundTripper.
func ensureBody(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
return func(req *http.Request) (*http.Response, error) {
resp, err := f(req)
if resp != nil && resp.Body == nil {
resp.Body = http.NoBody
if resp != nil {
if resp.Body == nil {
resp.Body = http.NoBody
}
if resp.Request == nil {
resp.Request = req
}
}
return resp, err
}
}
// WithMockClient is a test helper that allows you to inject a mock client for testing.
// makeTestRoundTripper makes sure the response has a Body, using [http.NoBody] if
// none is present, and returns it as a testRoundTripper. If withDefaults is set,
// it also mocks the "/_ping" endpoint and sets default headers as returned
// by the daemon.
func makeTestRoundTripper(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
return func(req *http.Request) (*http.Response, error) {
if req.URL.Path == "/_ping" {
return mockPingResponse(http.StatusOK, PingResult{
APIVersion: MaxAPIVersion,
OSType: runtime.GOOS,
Experimental: true,
BuilderVersion: build.BuilderBuildKit,
SwarmStatus: &SwarmStatus{
NodeState: swarm.LocalNodeStateActive,
ControlAvailable: true,
},
})(req)
}
resp, err := f(req)
if resp != nil {
if resp.Body == nil {
resp.Body = http.NoBody
}
if resp.Request == nil {
resp.Request = req
}
}
applyDefaultHeaders(resp)
return resp, err
}
}
// applyDefaultHeaders mocks the headers set by the daemon's VersionMiddleware.
func applyDefaultHeaders(resp *http.Response) {
if resp == nil {
return
}
if resp.Header == nil {
resp.Header = make(http.Header)
}
if resp.Header.Get("Server") == "" {
resp.Header.Set("Server", fmt.Sprintf("Docker/%s (%s)", "v99.99.99", runtime.GOOS))
}
if resp.Header.Get("Api-Version") == "" {
resp.Header.Set("Api-Version", MaxAPIVersion)
}
if resp.Header.Get("Ostype") == "" {
resp.Header.Set("Ostype", runtime.GOOS)
}
}
// WithMockClient is a test helper that allows you to inject a mock client for
// testing. By default, it mocks the "/_ping" endpoint, so allow the client
// to perform API-version negotiation. Other endpoints are handled by "doer".
func WithMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
return WithHTTPClient(&http.Client{
Transport: makeTestRoundTripper(doer),
})
}
// WithBaseMockClient is a test helper that allows you to inject a mock client
// for testing. It is identical to [WithMockClient], but does not mock the "/_ping"
// endpoint, and doesn't set the default headers.
func WithBaseMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
return WithHTTPClient(&http.Client{
Transport: ensureBody(doer),
})
@@ -78,21 +150,39 @@ func mockJSONResponse[T any](statusCode int, headers http.Header, resp T) func(r
return mockResponse(statusCode, hdr, string(respBody))
}
// mockPingResponse mocks the headers set for a "/_ping" response.
func mockPingResponse(statusCode int, ping PingResult) func(req *http.Request) (*http.Response, error) {
headers := http.Header{}
if s := ping.SwarmStatus; s != nil {
role := "worker"
if s.ControlAvailable {
role = "manager"
}
headers.Set("Swarm", fmt.Sprintf("%s/%s", string(swarm.LocalNodeStateActive), role))
}
headers.Set("Api-Version", ping.APIVersion)
headers.Set("Ostype", ping.OSType)
headers.Set("Docker-Experimental", strconv.FormatBool(ping.Experimental))
headers.Set("Builder-Version", string(ping.BuilderVersion))
headers.Set("Content-Type", "text/plain; charset=utf-8")
headers.Set("Cache-Control", "no-cache, no-store, must-revalidate")
headers.Set("Pragma", "no-cache")
return mockResponse(statusCode, headers, "OK")
}
func mockResponse(statusCode int, headers http.Header, respBody string) func(req *http.Request) (*http.Response, error) {
if headers == nil {
headers = make(http.Header)
}
var body io.ReadCloser
if respBody == "" {
body = http.NoBody
} else {
body = io.NopCloser(strings.NewReader(respBody))
}
return func(req *http.Request) (*http.Response, error) {
var body io.ReadCloser
if respBody == "" || req.Method == http.MethodHead {
body = http.NoBody
} else {
body = io.NopCloser(strings.NewReader(respBody))
}
return &http.Response{
Status: fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode)),
StatusCode: statusCode,
Header: headers,
Header: headers.Clone(),
Body: body,
Request: req,
}, nil

View File

@@ -38,14 +38,22 @@ type clientConfig struct {
userAgent *string
// custom HTTP headers configured by users.
customHTTPHeaders map[string]string
// manualOverride is set to true when the version was set by users.
manualOverride bool
// negotiateVersion indicates if the client should automatically negotiate
// the API version to use when making requests. API version negotiation is
// performed on the first request, after which negotiated is set to "true"
// so that subsequent requests do not re-negotiate.
negotiateVersion bool
// manualAPIVersion contains the API version set by users. This field
// will only be non-empty if a valid-formed version was set through
// [WithAPIVersion].
//
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
// takes precedence. Either field disables API-version negotiation.
manualAPIVersion string
// envAPIVersion contains the API version set by users. This field
// will only be non-empty if a valid-formed version was set through
// [WithAPIVersionFromEnv].
//
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
// takes precedence. Either field disables API-version negotiation.
envAPIVersion string
// traceOpts is a list of options to configure the tracing span.
traceOpts []otelhttp.Option
@@ -56,7 +64,7 @@ type Opt func(*clientConfig) error
// FromEnv configures the client with values from environment variables. It
// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
// and [WithVersionFromEnv] options.
// and [WithAPIVersionFromEnv] options.
//
// FromEnv uses the following environment variables:
//
@@ -71,7 +79,7 @@ func FromEnv(c *clientConfig) error {
ops := []Opt{
WithTLSClientConfigFromEnv(),
WithHostFromEnv(),
WithVersionFromEnv(),
WithAPIVersionFromEnv(),
}
for _, op := range ops {
if err := op(c); err != nil {
@@ -241,18 +249,59 @@ func WithTLSClientConfigFromEnv() Opt {
}
}
// WithVersion overrides the client version with the specified one. If an empty
// version is provided, the value is ignored to allow version negotiation
// (see [WithAPIVersionNegotiation]).
// WithAPIVersion overrides the client's API version with the specified one,
// and disables API version negotiation. If an empty version is provided,
// this option is ignored to allow version negotiation. The given version
// should be formatted "<major>.<minor>" (for example, "1.52"). It returns
// an error if the given value not in the correct format.
//
// WithVersion does not validate if the client supports the given version,
// and callers should verify if the version is in the correct format and
// lower than the maximum supported version as defined by [MaxAPIVersion].
func WithVersion(version string) Opt {
// WithAPIVersion does not validate if the client supports the given version,
// and callers should verify if the version lower than the maximum supported
// version as defined by [MaxAPIVersion].
//
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
// [WithAPIVersionFromEnv] are both set.
func WithAPIVersion(version string) Opt {
return func(c *clientConfig) error {
if v := strings.TrimPrefix(version, "v"); v != "" {
c.version = v
c.manualOverride = true
version = strings.TrimSpace(version)
if val := strings.TrimPrefix(version, "v"); val != "" {
ver, err := parseAPIVersion(val)
if err != nil {
return fmt.Errorf("invalid API version (%s): %w", version, err)
}
c.manualAPIVersion = ver
}
return nil
}
}
// WithVersion overrides the client version with the specified one.
//
// Deprecated: use [WithAPIVersion] instead.
func WithVersion(version string) Opt {
return WithAPIVersion(version)
}
// WithAPIVersionFromEnv overrides the client version with the version specified in
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
// is not modified.
//
// WithAPIVersion does not validate if the client supports the given version,
// and callers should verify if the version lower than the maximum supported
// version as defined by [MaxAPIVersion].
//
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
// [WithAPIVersionFromEnv] are both set.
func WithAPIVersionFromEnv() Opt {
return func(c *clientConfig) error {
version := strings.TrimSpace(os.Getenv(EnvOverrideAPIVersion))
if val := strings.TrimPrefix(version, "v"); val != "" {
ver, err := parseAPIVersion(val)
if err != nil {
return fmt.Errorf("invalid API version (%s): %w", version, err)
}
c.envAPIVersion = ver
}
return nil
}
@@ -260,25 +309,21 @@ func WithVersion(version string) Opt {
// WithVersionFromEnv overrides the client version with the version specified in
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
// is not modified.
//
// WithVersion does not validate if the client supports the given version,
// and callers should verify if the version is in the correct format and
// lower than the maximum supported version as defined by [MaxAPIVersion].
// Deprecated: use [WithAPIVersionFromEnv] instead.
func WithVersionFromEnv() Opt {
return func(c *clientConfig) error {
return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
}
return WithAPIVersionFromEnv()
}
// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
// With this option enabled, the client automatically negotiates the API version
// to use when making requests. API version negotiation is performed on the first
// request; subsequent requests do not re-negotiate.
//
// Deprecated: API-version negotiation is now enabled by default. Use [WithAPIVersion]
// or [WithAPIVersionFromEnv] to disable API version negotiation.
func WithAPIVersionNegotiation() Opt {
return func(c *clientConfig) error {
c.negotiateVersion = true
return nil
}
}

View File

@@ -0,0 +1,369 @@
package client
import (
"net/http"
"runtime"
"strings"
"testing"
"time"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestOptionWithHostFromEnv(t *testing.T) {
c, err := New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, ""))
if runtime.GOOS == "windows" {
assert.Check(t, is.Equal(c.host, "npipe:////./pipe/docker_engine"))
assert.Check(t, is.Equal(c.proto, "npipe"))
assert.Check(t, is.Equal(c.addr, "//./pipe/docker_engine"))
} else {
assert.Check(t, is.Equal(c.host, "unix:///var/run/docker.sock"))
assert.Check(t, is.Equal(c.proto, "unix"))
assert.Check(t, is.Equal(c.addr, "/var/run/docker.sock"))
}
t.Setenv("DOCKER_HOST", "tcp://foo.example.com:2376/test/")
c, err = New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, "/test/"))
assert.Check(t, is.Equal(c.host, "tcp://foo.example.com:2376/test/"))
assert.Check(t, is.Equal(c.proto, "tcp"))
assert.Check(t, is.Equal(c.addr, "foo.example.com:2376"))
}
func TestOptionWithTimeout(t *testing.T) {
timeout := 10 * time.Second
c, err := New(WithTimeout(timeout))
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.client.Timeout, timeout))
}
func TestOptionWithAPIVersion(t *testing.T) {
tests := []struct {
doc string
version string
expected string
expError string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version with whitespace, no v-prefix",
version: " 1.50 ",
expected: "1.50",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We currently allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "whitespace, with v-prefix",
version: " v1.0 ",
expected: "1.0",
},
{
doc: "downgrade unsupported version, with v-prefix",
version: "v1.0",
expected: "1.0",
},
{
doc: "custom lower version with whitespace and v-prefix",
version: " v1.50 ",
expected: "1.50",
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
doc: "malformed version",
version: "something-weird",
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
},
{
doc: "no minor",
version: "1",
expError: "invalid API version (1): must be formatted <major>.<minor>",
},
{
doc: "too many digits",
version: "1.2.3",
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
},
{
doc: "embedded whitespace",
version: "v 1.0",
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithAPIVersion(tc.version))
if tc.expError != "" {
assert.Check(t, is.ErrorContains(err, tc.expError))
assert.Check(t, client == nil)
} else {
assert.NilError(t, err)
assert.Check(t, client != nil)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
}
})
}
}
func TestOptionWithAPIVersionFromEnv(t *testing.T) {
tests := []struct {
doc string
version string
expected string
expError string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version with whitespace, no v-prefix",
version: " 1.50 ",
expected: "1.50",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We currently allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "whitespace, with v-prefix",
version: " v1.0 ",
expected: "1.0",
},
{
doc: "downgrade unsupported version, with v-prefix",
version: "v1.0",
expected: "1.0",
},
{
doc: "custom lower version with whitespace and v-prefix",
version: " v1.50 ",
expected: "1.50",
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
doc: "malformed version",
version: "something-weird",
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
},
{
doc: "no minor",
version: "1",
expError: "invalid API version (1): must be formatted <major>.<minor>",
},
{
doc: "too many digits",
version: "1.2.3",
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
},
{
doc: "embedded whitespace",
version: "v 1.0",
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
t.Setenv(EnvOverrideAPIVersion, tc.version)
client, err := New(WithAPIVersionFromEnv())
if tc.expError != "" {
assert.Check(t, is.ErrorContains(err, tc.expError))
assert.Check(t, client == nil)
} else {
assert.NilError(t, err)
assert.Check(t, client != nil)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
}
})
}
}
// TestOptionOverridePriority validates that overriding the API version through
// [WithAPIVersionFromEnv] takes precedence over other manual options, regardless
// the order in which they're passed.
func TestOptionOverridePriority(t *testing.T) {
t.Run("no env-var set", func(t *testing.T) {
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), "1.50"))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
const expected = "1.51"
t.Setenv(EnvOverrideAPIVersion, expected)
t.Run("WithAPIVersionFromEnv first", func(t *testing.T) {
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("WithAPIVersionFromEnv last", func(t *testing.T) {
client, err := New(WithAPIVersion("1.50"), WithAPIVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("FromEnv first", func(t *testing.T) {
client, err := New(FromEnv, WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("FromEnv last", func(t *testing.T) {
client, err := New(WithAPIVersion("1.50"), FromEnv)
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
}
func TestWithUserAgent(t *testing.T) {
const userAgent = "Magic-Client/v1.2.3"
t.Run("user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("user-agent and custom headers", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithHTTPHeaders(map[string]string{"User-Agent": "should-be-ignored/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("custom headers", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), "from-custom-headers/1.0.0"))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("no user-agent set", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("reset custom user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(""),
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
}

View File

@@ -175,7 +175,7 @@ func TestGetAPIPath(t *testing.T) {
ctx := context.TODO()
for _, tc := range tests {
client, err := New(
WithVersion(tc.version),
WithAPIVersion(tc.version),
WithHost("tcp://localhost:2375"),
)
assert.NilError(t, err)
@@ -254,11 +254,10 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
// if no version from server, expect the earliest
// version before APIVersion was implemented
const expected = fallbackAPIVersion
const expected = MinAPIVersion
client, err := New(FromEnv,
WithAPIVersionNegotiation(),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{expected}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
)
assert.NilError(t, err)
@@ -301,12 +300,10 @@ func TestNegotiateAPIVersion(t *testing.T) {
expectedVersion: "1.51",
},
{
// client should downgrade to the last version before version
// negotiation was added (1.24) if the daemon does not report
// a version.
// client should not downgrade if the daemon didn't report a version.
doc: "downgrade legacy",
pingVersion: "",
expectedVersion: fallbackAPIVersion,
expectedVersion: MaxAPIVersion,
},
{
// client should not downgrade to the version reported by the daemon
@@ -314,7 +311,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
doc: "no downgrade old",
pingVersion: "1.19",
expectedVersion: MaxAPIVersion,
expectedErr: "API version 1.19 is not supported by this client: the minimum supported API version is " + fallbackAPIVersion,
expectedErr: "API version 1.19 is not supported by this client: the minimum supported API version is " + MinAPIVersion,
},
{
// client should not upgrade to a newer version if a version was set,
@@ -330,15 +327,14 @@ func TestNegotiateAPIVersion(t *testing.T) {
t.Run(tc.doc, func(t *testing.T) {
opts := []Opt{
FromEnv,
WithAPIVersionNegotiation(),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{tc.pingVersion}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
}
if tc.clientVersion != "" {
// Note that this check is redundant, as WithVersion() considers
// an empty version equivalent to "not setting a version", but
// doing this just to be explicit we are using the default.
opts = append(opts, WithVersion(tc.clientVersion))
opts = append(opts, WithAPIVersion(tc.clientVersion))
}
client, err := New(opts...)
assert.NilError(t, err)
@@ -363,7 +359,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
client, err := New(
FromEnv,
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.45"}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
)
assert.NilError(t, err)
@@ -393,11 +389,9 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
ctx := t.Context()
client, err := New(
WithMockClient(func(req *http.Request) (*http.Response, error) {
hdr := http.Header{"Api-Version": []string{pingVersion}}
return mockResponse(http.StatusOK, hdr, "OK")(req)
WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})(req)
}),
WithAPIVersionNegotiation(),
)
assert.NilError(t, err)
@@ -422,8 +416,8 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
// with an empty version string does still allow API-version negotiation
func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
client, err := New(
WithVersion(""),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.50"}}, "OK")),
WithAPIVersion(""),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
)
assert.NilError(t, err)
@@ -437,94 +431,28 @@ func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
// TestNegotiateAPIVersionWithFixedVersion asserts that initializing a client
// with a fixed version disables API-version negotiation
func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
const customVersion = "1.50"
const (
customVersion = "1.50"
pingVersion = "1.49"
)
client, err := New(
WithVersion(customVersion),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.49"}}, "OK")),
WithAPIVersion(customVersion),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
)
assert.NilError(t, err)
_, err = client.Ping(t.Context(), PingOptions{
NegotiateAPIVersion: true,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
_, err = client.Ping(t.Context(), PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
}
// TestCustomAPIVersion tests initializing the client with a custom
// version.
func TestCustomAPIVersion(t *testing.T) {
tests := []struct {
doc string
version string
expected string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
// We currently ignore malformed versions.
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
// We allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "v1.0",
expected: "1.0",
},
{
// When manually setting a version, no validation happens.
// so anything is accepted.
doc: "malformed version",
version: "something-weird",
expected: "something-weird",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithVersion(tc.version))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
t.Setenv(EnvOverrideAPIVersion, tc.expected)
client, err = New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
})
}
assert.Check(t, is.Equal(client.ClientVersion(), pingVersion))
}
func TestClientRedirect(t *testing.T) {

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestConfigCreateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigCreate(context.Background(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
_, err = client.ConfigCreate(t.Context(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,7 +34,7 @@ func TestConfigCreate(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ConfigCreate(context.Background(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
r, err := client.ConfigCreate(t.Context(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "test_config"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestConfigInspectNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "unknown", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "unknown", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -29,11 +28,11 @@ func TestConfigInspectWithEmptyID(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigInspect(context.Background(), " ", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), " ", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -44,7 +43,7 @@ func TestConfigInspectError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "nothing", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "nothing", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -54,7 +53,7 @@ func TestConfigInspectConfigNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "unknown", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "unknown", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -72,7 +71,7 @@ func TestConfigInspect(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ConfigInspect(context.Background(), "config_id", ConfigInspectOptions{})
result, err := client.ConfigInspect(t.Context(), "config_id", ConfigInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(result.Config.ID, "config_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestConfigListError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigList(context.Background(), ConfigListOptions{})
_, err = client.ConfigList(t.Context(), ConfigListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -67,7 +66,7 @@ func TestConfigList(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ConfigList(context.Background(), listCase.options)
result, err := client.ConfigList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(result.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestConfigRemoveError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigRemove(context.Background(), "config_id", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "config_id", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ConfigRemove(context.Background(), "", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigRemove(context.Background(), " ", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), " ", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -41,6 +40,6 @@ func TestConfigRemove(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigRemove(context.Background(), "config_id", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "config_id", ConfigRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestConfigUpdateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigUpdate(context.Background(), "config_id", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "config_id", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ConfigUpdate(context.Background(), "", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigUpdate(context.Background(), " ", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), " ", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -41,6 +40,6 @@ func TestConfigUpdate(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigUpdate(context.Background(), "config_id", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "config_id", ConfigUpdateOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,14 +17,14 @@ func TestContainerCommitError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCommit(context.Background(), "nothing", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), "nothing", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerCommit(context.Background(), "", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), "", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerCommit(context.Background(), " ", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), " ", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -83,7 +82,7 @@ func TestContainerCommit(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ContainerCommit(context.Background(), expectedContainerID, ContainerCommitOptions{
r, err := client.ContainerCommit(t.Context(), expectedContainerID, ContainerCommitOptions{
Reference: specifiedReference,
Comment: expectedComment,
Author: expectedAuthor,

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
@@ -24,14 +23,14 @@ func TestContainerStatPathError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerStatPath(context.Background(), "", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerStatPath(context.Background(), " ", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), " ", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -42,7 +41,7 @@ func TestContainerStatPathNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -52,7 +51,7 @@ func TestContainerStatPathNoHeaderError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path/to/file"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path/to/file"})
assert.Check(t, err != nil, "expected an error, got nothing")
}
@@ -86,7 +85,7 @@ func TestContainerStatPath(t *testing.T) {
}),
)
assert.NilError(t, err)
res, err := client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: expectedPath})
res, err := client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: expectedPath})
assert.NilError(t, err)
assert.Check(t, is.Equal(res.Stat.Name, "name"))
assert.Check(t, is.Equal(res.Stat.Mode, os.FileMode(0o700)))
@@ -98,20 +97,20 @@ func TestCopyToContainerError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.CopyToContainer(context.Background(), "", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.CopyToContainer(context.Background(), " ", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), " ", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -125,7 +124,7 @@ func TestCopyToContainerNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -140,7 +139,7 @@ func TestCopyToContainerEmptyResponse(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -183,7 +182,7 @@ func TestCopyToContainer(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: expectedPath,
Content: bytes.NewReader([]byte("content")),
AllowOverwriteDirWithFile: false,
@@ -197,14 +196,14 @@ func TestCopyFromContainerError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.CopyFromContainer(context.Background(), "", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.CopyFromContainer(context.Background(), " ", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), " ", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -215,7 +214,7 @@ func TestCopyFromContainerNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -240,7 +239,7 @@ func TestCopyFromContainerEmptyResponse(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.NilError(t, err)
}
@@ -250,7 +249,7 @@ func TestCopyFromContainerNoHeaderError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, err != nil, "expected an error, got nothing")
}
@@ -285,7 +284,7 @@ func TestCopyFromContainer(t *testing.T) {
}),
)
assert.NilError(t, err)
res2, err := client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: expectedPath})
res2, err := client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: expectedPath})
assert.NilError(t, err)
assert.Check(t, is.Equal(res2.Stat.Name, "name"))
assert.Check(t, is.Equal(res2.Stat.Mode, os.FileMode(0o700)))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -20,11 +19,11 @@ func TestContainerCreateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, Name: "nothing"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: nil, Name: "nothing"})
assert.Error(t, err, "config.Image or Image is required")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
}
@@ -34,7 +33,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, Name: "unknown"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, Name: "unknown"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -56,7 +55,7 @@ func TestContainerCreateWithName(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "container_name"})
r, err := client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "container_name"})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "container_id"))
}
@@ -78,7 +77,7 @@ func TestContainerCreateAutoRemove(t *testing.T) {
)
assert.NilError(t, err)
resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{AutoRemove: true}})
resp, err := client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{AutoRemove: true}})
assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, "container_id"))
}
@@ -88,10 +87,10 @@ func TestContainerCreateAutoRemove(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
@@ -133,6 +132,6 @@ func TestContainerCreateCapabilities(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,14 +16,14 @@ func TestContainerDiffError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerDiff(context.Background(), "nothing", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), "nothing", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerDiff(context.Background(), "", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), "", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerDiff(context.Background(), " ", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), " ", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -57,7 +56,7 @@ func TestContainerDiff(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ContainerDiff(context.Background(), "container_id", ContainerDiffOptions{})
result, err := client.ContainerDiff(t.Context(), "container_id", ContainerDiffOptions{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(result.Changes, expected))
}

View File

@@ -36,7 +36,7 @@ func TestExecCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestExecCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ExecCreate(t.Context(), "container_id", ExecCreateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestContainerListError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerList(context.Background(), ContainerListOptions{})
_, err = client.ContainerList(t.Context(), ContainerListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -65,7 +64,7 @@ func TestContainerList(t *testing.T) {
)
assert.NilError(t, err)
list, err := client.ContainerList(context.Background(), ContainerListOptions{
list, err := client.ContainerList(t.Context(), ContainerListOptions{
Size: true,
All: true,
Since: "container",

View File

@@ -167,7 +167,7 @@ func TestContainerLogs(t *testing.T) {
}
func ExampleClient_ContainerLogs_withTimeout() {
client, err := New(FromEnv, WithAPIVersionNegotiation())
client, err := New(FromEnv)
if err != nil {
log.Fatal(err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestContainerPruneError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerPrune(context.Background(), ContainerPruneOptions{})
_, err = client.ContainerPrune(t.Context(), ContainerPruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -89,7 +88,7 @@ func TestContainerPrune(t *testing.T) {
}))
assert.NilError(t, err)
req, err := client.ContainerPrune(context.Background(), ContainerPruneOptions{Filters: listCase.filters})
req, err := client.ContainerPrune(t.Context(), ContainerPruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(req.Report.ContainersDeleted, 2))
assert.Check(t, is.Equal(uint64(9999), req.Report.SpaceReclaimed))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -14,14 +13,14 @@ import (
func TestContainerRenameError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerRename(context.Background(), "nothing", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), "nothing", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerRename(context.Background(), "", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), "", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerRename(context.Background(), " ", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), " ", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -40,6 +39,6 @@ func TestContainerRename(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ContainerRename(context.Background(), "container_id", ContainerRenameOptions{NewName: "newName"})
_, err = client.ContainerRename(t.Context(), "container_id", ContainerRenameOptions{NewName: "newName"})
assert.NilError(t, err)
}

View File

@@ -30,7 +30,7 @@ func TestContainerRestartError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerRestartConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerRestart(t.Context(), "nothing", ContainerRestartOptions{})

View File

@@ -30,7 +30,7 @@ func TestContainerStopError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerStopConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerStop(t.Context(), "container_id", ContainerStopOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,14 +14,14 @@ import (
func TestContainerTopError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerTop(context.Background(), "nothing", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), "nothing", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerTop(context.Background(), "", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), "", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerTop(context.Background(), " ", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), " ", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestContainerTop(t *testing.T) {
}))
assert.NilError(t, err)
processList, err := client.ContainerTop(context.Background(), "container_id", ContainerTopOptions{
processList, err := client.ContainerTop(t.Context(), "container_id", ContainerTopOptions{
Arguments: []string{"arg1", "arg2"},
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ import (
func TestContainerUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerUpdate(context.Background(), "nothing", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), "nothing", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerUpdate(context.Background(), "", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), "", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerUpdate(context.Background(), " ", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), " ", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,7 +36,7 @@ func TestContainerUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ContainerUpdate(context.Background(), "container_id", ContainerUpdateOptions{
_, err = client.ContainerUpdate(t.Context(), "container_id", ContainerUpdateOptions{
Resources: &container.Resources{
CPUPeriod: 1,
},

View File

@@ -19,9 +19,12 @@ import (
)
func TestContainerWaitError(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "nothing", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "nothing", ContainerWaitOptions{})
select {
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
@@ -35,10 +38,13 @@ func TestContainerWaitError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerWaitConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "nothing", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "nothing", ContainerWaitOptions{})
select {
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
@@ -48,6 +54,9 @@ func TestContainerWaitConnectionError(t *testing.T) {
}
func TestContainerWait(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const expectedURL = "/containers/container_id/wait"
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodPost, expectedURL); err != nil {
@@ -59,7 +68,7 @@ func TestContainerWait(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
assert.NilError(t, err)
@@ -69,6 +78,9 @@ func TestContainerWait(t *testing.T) {
}
func TestContainerWaitProxyInterrupt(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const (
expectedURL = "/containers/container_id/wait"
expErr = "copying response body from Docker: unexpected EOF"
@@ -82,16 +94,19 @@ func TestContainerWaitProxyInterrupt(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
assert.Check(t, is.ErrorContains(err, expErr))
case result := <-wait.Result:
t.Fatalf("Unexpected result: %v", result)
t.Errorf("Unexpected result: %v", result)
}
}
func TestContainerWaitProxyInterruptLong(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const expectedURL = "/containers/container_id/wait"
msg := strings.Repeat("x", containerWaitErrorMsgLimit*5)
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
@@ -102,13 +117,13 @@ func TestContainerWaitProxyInterruptLong(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
// LimitReader limiting isn't exact, because of how the Readers do chunking.
assert.Check(t, len(err.Error()) <= containerWaitErrorMsgLimit*2, "Expected error to be limited around %d, actual length: %d", containerWaitErrorMsgLimit, len(err.Error()))
case result := <-wait.Result:
t.Fatalf("Unexpected result: %v", result)
t.Errorf("Unexpected result: %v", result)
}
}
@@ -140,7 +155,7 @@ func TestContainerWaitErrorHandling(t *testing.T) {
assert.Check(t, is.Equal(err.Error(), test.exp.Error()))
return
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
t.Errorf("expected to not get a wait result, got %d", result.StatusCode)
return
}
// Unexpected - we should not reach this line

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,6 +15,6 @@ func TestDistributionInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.DistributionInspect(context.Background(), "", DistributionInspectOptions{})
_, err = client.DistributionInspect(t.Context(), "", DistributionInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}

View File

@@ -13,7 +13,7 @@ const (
// be used to override the API version to use. Value must be
// formatted as MAJOR.MINOR, for example, "1.19".
//
// This env-var is read by [FromEnv] and [WithVersionFromEnv] and when set to a
// This env-var is read by [FromEnv] and [WithAPIVersionFromEnv] and when set to a
// non-empty value, takes precedence over API version negotiation.
//
// This environment variable should be used for debugging purposes only, as

View File

@@ -18,11 +18,24 @@ import (
func TestTLSCloseWriter(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
var chErr chan error
ts := &httptest.Server{Config: &http.Server{
ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/_ping" {
resp, err := mockPingResponse(http.StatusOK, PingResult{APIVersion: MaxAPIVersion})(req)
if err != nil {
chErr <- fmt.Errorf("sending ping response: %w", err)
return
}
_ = resp.Header.Write(w)
w.WriteHeader(resp.StatusCode)
return
}
chErr = make(chan error, 1)
defer close(chErr)
@@ -38,12 +51,16 @@ func TestTLSCloseWriter(t *testing.T) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
defer func() { _ = conn.Close() }()
// Flush the options to make sure the client sets the raw mode
_, _ = conn.Write([]byte{})
fmt.Fprint(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\n")
_, err = fmt.Fprint(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\n")
if err != nil {
chErr <- fmt.Errorf("writing update response: %w", err)
return
}
buf := make([]byte, 5)
_, err = conn.Read(buf)
@@ -72,7 +89,7 @@ func TestTLSCloseWriter(t *testing.T) {
assert.NilError(t, err)
ts.Listener = l
defer l.Close()
defer func() { _ = l.Close() }()
defer func() {
if chErr != nil {
@@ -86,10 +103,12 @@ func TestTLSCloseWriter(t *testing.T) {
serverURL, err := url.Parse(ts.URL)
assert.NilError(t, err)
client, err := New(WithHost("tcp://"+serverURL.Host), WithHTTPClient(ts.Client()))
httpClient := ts.Client()
defer httpClient.CloseIdleConnections()
client, err := New(WithHost("tcp://"+serverURL.Host), WithHTTPClient(httpClient))
assert.NilError(t, err)
resp, err := client.postHijacked(context.Background(), "/asdf", url.Values{}, nil, map[string][]string{"Content-Type": {"text/plain"}})
resp, err := client.postHijacked(ctx, "/asdf", url.Values{}, nil, map[string][]string{"Content-Type": {"text/plain"}})
assert.NilError(t, err)
defer resp.Close()

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"io"
"net/http"
@@ -18,7 +17,7 @@ import (
func TestImageBuildError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageBuild(context.Background(), nil, ImageBuildOptions{})
_, err = client.ImageBuild(t.Context(), nil, ImageBuildOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -188,7 +187,7 @@ func TestImageBuild(t *testing.T) {
return mockResponse(http.StatusOK, nil, "body")(req)
}))
assert.NilError(t, err)
buildResponse, err := client.ImageBuild(context.Background(), nil, buildCase.buildOptions)
buildResponse, err := client.ImageBuild(t.Context(), nil, buildCase.buildOptions)
assert.NilError(t, err)
response, err := io.ReadAll(buildResponse.Body)
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ import (
func TestImageHistoryError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageHistory(context.Background(), "nothing")
_, err = client.ImageHistory(t.Context(), "nothing")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -44,7 +43,7 @@ func TestImageHistory(t *testing.T) {
},
}
imageHistories, err := client.ImageHistory(context.Background(), "image_id", ImageHistoryWithPlatform(ocispec.Platform{
imageHistories, err := client.ImageHistory(t.Context(), "image_id", ImageHistoryWithPlatform(ocispec.Platform{
Architecture: "arm64",
OS: "linux",
Variant: "v8",

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"io"
"net/http"
"net/url"
@@ -17,7 +16,7 @@ import (
func TestImageImportError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageImport(context.Background(), ImageImportSource{}, "image:tag", ImageImportOptions{})
_, err = client.ImageImport(t.Context(), ImageImportSource{}, "image:tag", ImageImportOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -77,7 +76,7 @@ func TestImageImport(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
result, err := client.ImageImport(context.Background(), ImageImportSource{
result, err := client.ImageImport(t.Context(), ImageImportSource{
Source: strings.NewReader("source"),
SourceName: "image_source",
}, "repository_name:imported", tc.options)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"fmt"
"net/http"
@@ -18,7 +17,7 @@ func TestImageInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "nothing")
_, err = client.ImageInspect(t.Context(), "nothing")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,7 +25,7 @@ func TestImageInspectImageNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "unknown")
_, err = client.ImageInspect(t.Context(), "unknown")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -35,7 +34,7 @@ func TestImageInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "")
_, err = client.ImageInspect(t.Context(), "")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -53,7 +52,7 @@ func TestImageInspect(t *testing.T) {
}))
assert.NilError(t, err)
imageInspect, err := client.ImageInspect(context.Background(), "image_id")
imageInspect, err := client.ImageInspect(t.Context(), "image_id")
assert.NilError(t, err)
assert.Check(t, is.Equal(imageInspect.ID, "image_id"))
assert.Check(t, is.DeepEqual(imageInspect.RepoTags, expectedTags))
@@ -88,7 +87,7 @@ func TestImageInspectWithPlatform(t *testing.T) {
}))
assert.NilError(t, err)
imageInspect, err := client.ImageInspect(context.Background(), "image_id", ImageInspectWithPlatform(requestedPlatform))
imageInspect, err := client.ImageInspect(t.Context(), "image_id", ImageInspectWithPlatform(requestedPlatform))
assert.NilError(t, err)
assert.Check(t, is.Equal(imageInspect.ID, "image_id"))
assert.Check(t, is.Equal(imageInspect.Architecture, "arm64"))

View File

@@ -1,10 +1,8 @@
package client
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
cerrdefs "github.com/containerd/errdefs"
@@ -17,7 +15,7 @@ func TestImageListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), ImageListOptions{})
_, err = client.ImageList(t.Context(), ImageListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,21 +24,23 @@ func TestImageListError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestImageListConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), ImageListOptions{})
_, err = client.ImageList(t.Context(), ImageListOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
func TestImageList(t *testing.T) {
const expectedURL = "/images/json"
listCases := []struct {
tests := []struct {
doc string
options ImageListOptions
expectedQueryParams map[string]string
}{
{
doc: "no options",
options: ImageListOptions{},
expectedQueryParams: map[string]string{
"all": "",
@@ -49,6 +49,7 @@ func TestImageList(t *testing.T) {
},
},
{
doc: "label filters and dangling",
options: ImageListOptions{
Filters: make(Filters).
Add("label", "label1").
@@ -62,6 +63,7 @@ func TestImageList(t *testing.T) {
},
},
{
doc: "label filters no dangling",
options: ImageListOptions{
Filters: make(Filters).Add("dangling", "false"),
},
@@ -71,59 +73,40 @@ func TestImageList(t *testing.T) {
"filters": `{"dangling":{"false":true}}`,
},
},
{
doc: "with shared size",
options: ImageListOptions{
SharedSize: true,
},
expectedQueryParams: map[string]string{
"shared-size": "1",
},
},
}
for _, listCase := range listCases {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodGet, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
for key, expected := range listCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return mockJSONResponse(http.StatusOK, nil, []image.Summary{
{ID: "image_id2"},
{ID: "image_id2"},
})(req)
}))
assert.NilError(t, err)
images, err := client.ImageList(context.Background(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(images.Items, 2))
}
}
// Checks if shared-size query parameter is set/not being set correctly
// for /images/json.
func TestImageListWithSharedSize(t *testing.T) {
t.Parallel()
const sharedSize = "shared-size"
for _, tc := range []struct {
name string
version string
options ImageListOptions
sharedSize string // expected value for the shared-size query param, or empty if it should not be set.
}{
{name: "unset, no options set"},
{name: "set", options: ImageListOptions{SharedSize: true}, sharedSize: "1"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var query url.Values
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
query = req.URL.Query()
return mockResponse(http.StatusOK, nil, "[]")(req)
}), WithVersion(tc.version))
if err := assertRequest(req, http.MethodGet, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
for key, expected := range tc.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return mockJSONResponse(http.StatusOK, nil, []image.Summary{
{ID: "image_id2"},
{ID: "image_id2"},
})(req)
}))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), tc.options)
defer func() { _ = client.Close() }()
images, err := client.ImageList(t.Context(), tc.options)
assert.NilError(t, err)
expectedSet := tc.sharedSize != ""
assert.Check(t, is.Equal(query.Has(sharedSize), expectedSet))
assert.Check(t, is.Equal(query.Get(sharedSize), tc.sharedSize))
assert.Check(t, is.Len(images.Items, 2))
})
}
}

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
@@ -19,7 +18,7 @@ func TestImageLoadError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageLoad(context.Background(), nil, ImageLoadWithQuiet(true))
_, err = client.ImageLoad(t.Context(), nil, ImageLoadWithQuiet(true))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -81,7 +80,7 @@ func TestImageLoad(t *testing.T) {
assert.NilError(t, err)
input := bytes.NewReader([]byte(expectedInput))
imageLoadResponse, err := client.ImageLoad(context.Background(), input,
imageLoadResponse, err := client.ImageLoad(t.Context(), input,
ImageLoadWithQuiet(tc.quiet),
ImageLoadWithPlatforms(tc.platforms...),
)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestImagePruneError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePrune(context.Background(), ImagePruneOptions{})
_, err = client.ImagePrune(t.Context(), ImagePruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -83,7 +82,7 @@ func TestImagePrune(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.ImagePrune(context.Background(), ImagePruneOptions{Filters: listCase.filters})
res, err := client.ImagePrune(t.Context(), ImagePruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(res.Report.ImagesDeleted, 2))
assert.Check(t, is.Equal(uint64(9999), res.Report.SpaceReclaimed))

View File

@@ -23,28 +23,28 @@ func TestImagePullReferenceParseError(t *testing.T) {
}))
assert.NilError(t, err)
// An empty reference is an invalid reference
_, err = client.ImagePull(context.Background(), "", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "", ImagePullOptions{})
assert.Check(t, is.ErrorContains(err, "invalid reference format"))
}
func TestImagePullAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImagePullStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{
PrivilegeFunc: func(_ context.Context) (string, error) {
return "", errors.New("error requesting privilege")
},
@@ -55,7 +55,7 @@ func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{
PrivilegeFunc: staticAuth("a-auth-header"),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -88,7 +88,7 @@ func TestImagePullWithPrivilegedFuncNoError(t *testing.T) {
return mockResponse(http.StatusOK, nil, "hello world")(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePull(context.Background(), "myimage", ImagePullOptions{
resp, err := client.ImagePull(t.Context(), "myimage", ImagePullOptions{
RegistryAuth: invalidAuth,
PrivilegeFunc: staticAuth(validAuth),
})
@@ -177,7 +177,7 @@ func TestImagePullWithoutErrors(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePull(context.Background(), pullCase.reference, ImagePullOptions{
resp, err := client.ImagePull(t.Context(), pullCase.reference, ImagePullOptions{
All: pullCase.all,
})
assert.NilError(t, err)

View File

@@ -20,24 +20,24 @@ func TestImagePushReferenceError(t *testing.T) {
}))
assert.NilError(t, err)
// An empty reference is an invalid reference
_, err = client.ImagePush(context.Background(), "", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "", ImagePushOptions{})
assert.Check(t, is.ErrorContains(err, "invalid reference format"))
// A canonical reference cannot be pushed
_, err = client.ImagePush(context.Background(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{})
assert.Check(t, is.Error(err, "cannot push a digest reference"))
}
func TestImagePushAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImagePushStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
@@ -47,7 +47,7 @@ func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "", errors.New("error requesting privilege")
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.Error(err, "error requesting privilege"))
@@ -59,7 +59,7 @@ func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T)
privilegeFunc := func(_ context.Context) (string, error) {
return "a-auth-header", nil
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -88,7 +88,7 @@ func TestImagePushWithPrivilegedFuncNoError(t *testing.T) {
return mockResponse(http.StatusOK, nil, "hello world")(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), "myname/myimage:tag", ImagePushOptions{
resp, err := client.ImagePush(t.Context(), "myname/myimage:tag", ImagePushOptions{
RegistryAuth: invalidAuth,
PrivilegeFunc: staticAuth(validAuth),
})
@@ -174,7 +174,7 @@ func TestImagePushWithoutErrors(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), tc.reference, ImagePushOptions{
resp, err := client.ImagePush(t.Context(), tc.reference, ImagePushOptions{
All: tc.all,
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestImageRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageRemove(context.Background(), "image_id", ImageRemoveOptions{})
_, err = client.ImageRemove(t.Context(), "image_id", ImageRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -25,7 +24,7 @@ func TestImageRemoveImageNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "no such image: unknown")))
assert.NilError(t, err)
_, err = client.ImageRemove(context.Background(), "unknown", ImageRemoveOptions{})
_, err = client.ImageRemove(t.Context(), "unknown", ImageRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "no such image: unknown"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -91,7 +90,7 @@ func TestImageRemove(t *testing.T) {
opts.Platforms = []ocispec.Platform{*removeCase.platform}
}
res, err := client.ImageRemove(context.Background(), "image_id", opts)
res, err := client.ImageRemove(t.Context(), "image_id", opts)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 2))
}

View File

@@ -16,14 +16,14 @@ import (
func TestImageSearchAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{})
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImageSearchStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{})
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
@@ -33,7 +33,7 @@ func TestImageSearchWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "", errors.New("Error requesting privilege")
}
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.Error(err, "Error requesting privilege"))
@@ -45,7 +45,7 @@ func TestImageSearchWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.
privilegeFunc := func(_ context.Context) (string, error) {
return "a-auth-header", nil
}
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -77,7 +77,7 @@ func TestImageSearchWithPrivilegedFuncNoError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "IAmValid", nil
}
results, err := client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
results, err := client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
RegistryAuth: "NotValid",
PrivilegeFunc: privilegeFunc,
})
@@ -107,7 +107,7 @@ func TestImageSearchWithoutErrors(t *testing.T) {
})(req)
}))
assert.NilError(t, err)
results, err := client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
results, err := client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
Filters: make(Filters).Add("is-automated", "true").Add("stars", "3"),
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"math/rand"
"net/http"
@@ -16,7 +15,7 @@ func TestImageTagError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "repo:tag"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: "repo:tag"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,13 +25,13 @@ func TestImageTagInvalidReference(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "aa/asdf$$^/aa"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: "aa/asdf$$^/aa"})
assert.Check(t, is.Error(err, `error parsing reference: "aa/asdf$$^/aa" is not a valid repository/tag: invalid reference format`))
}
// Ensure we don't allow the use of invalid repository names or tags; these tag operations should fail.
func TestImageTagInvalidSourceImageName(t *testing.T) {
ctx := context.Background()
ctx := t.Context()
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "client should not have made an API call")))
assert.NilError(t, err)
@@ -89,7 +88,7 @@ func TestImageTagHexSource(t *testing.T) {
client, err := New(WithMockClient(mockResponse(http.StatusOK, nil, "OK")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", Target: "repo:tag"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", Target: "repo:tag"})
assert.NilError(t, err)
}
@@ -164,7 +163,7 @@ func TestImageTag(t *testing.T) {
return mockResponse(http.StatusOK, nil, "")(req)
}))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: tagCase.reference})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: tagCase.reference})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -17,19 +16,19 @@ func TestNetworkConnectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
// Empty network ID or container ID
_, err = client.NetworkConnect(context.Background(), "", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "", NetworkConnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{})
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -59,7 +58,7 @@ func TestNetworkConnectEmptyNilEndpointSettings(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
})
assert.NilError(t, err)
@@ -94,7 +93,7 @@ func TestNetworkConnect(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
EndpointConfig: &network.EndpointSettings{
NetworkID: "NetworkID",

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestNetworkCreateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{})
_, err = client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,10 +23,10 @@ func TestNetworkCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestNetworkCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{})
_, err = client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
@@ -46,7 +45,7 @@ func TestNetworkCreate(t *testing.T) {
assert.NilError(t, err)
enableIPv6 := true
networkResponse, err := client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{
networkResponse, err := client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{
Driver: "mydriver",
EnableIPv6: &enableIPv6,
Internal: true,

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -17,19 +16,19 @@ func TestNetworkDisconnectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
// Empty network ID or container ID
_, err = client.NetworkDisconnect(context.Background(), "", NetworkDisconnectOptions{
_, err = client.NetworkDisconnect(t.Context(), "", NetworkDisconnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{})
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -59,6 +58,6 @@ func TestNetworkDisconnect(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{Container: "container_id", Force: true})
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{Container: "container_id", Force: true})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"strings"
"testing"
@@ -52,39 +51,39 @@ func TestNetworkInspect(t *testing.T) {
t.Run("empty ID", func(t *testing.T) {
// verify that the client does not create a request if the network-ID/name is empty.
_, err := client.NetworkInspect(context.Background(), "", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkInspect(context.Background(), " ", NetworkInspectOptions{})
_, err = client.NetworkInspect(t.Context(), " ", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
})
t.Run("no options", func(t *testing.T) {
r, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{})
r, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.Network.Name, "mynetwork"))
})
t.Run("verbose", func(t *testing.T) {
r, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{Verbose: true})
r, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{Verbose: true})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.Network.Name, "mynetwork"))
_, ok := r.Network.Services["web"]
assert.Check(t, ok, "expected service `web` missing in the verbose output")
})
t.Run("global scope", func(t *testing.T) {
_, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{Scope: "global"})
_, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{Scope: "global"})
assert.Check(t, is.ErrorContains(err, "Error: No such network: network_id"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
})
t.Run("unknown network", func(t *testing.T) {
_, err := client.NetworkInspect(context.Background(), "unknown", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "unknown", NetworkInspectOptions{})
assert.Check(t, is.ErrorContains(err, "Error: No such network: unknown"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
})
t.Run("server error", func(t *testing.T) {
// Just testing that an internal server error is converted correctly by the client
_, err := client.NetworkInspect(context.Background(), "test-500-response", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "test-500-response", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
})
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNetworkListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkList(context.Background(), NetworkListOptions{})
_, err = client.NetworkList(t.Context(), NetworkListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -74,7 +73,7 @@ func TestNetworkList(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.NetworkList(context.Background(), listCase.options)
res, err := client.NetworkList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 1))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestNetworkPruneError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.NetworkPrune(context.Background(), NetworkPruneOptions{})
_, err = client.NetworkPrune(t.Context(), NetworkPruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -82,7 +81,7 @@ func TestNetworkPrune(t *testing.T) {
)
assert.NilError(t, err)
res, err := client.NetworkPrune(context.Background(), NetworkPruneOptions{Filters: listCase.filters})
res, err := client.NetworkPrune(t.Context(), NetworkPruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(res.Report.NetworksDeleted, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestNetworkRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkRemove(context.Background(), "network_id", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "network_id", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NetworkRemove(context.Background(), "", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkRemove(context.Background(), " ", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), " ", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestNetworkRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkRemove(context.Background(), "network_id", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "network_id", NetworkRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNodeInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "nothing", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "nothing", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestNodeInspectNodeNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "unknown", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "unknown", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestNodeInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeInspect(context.Background(), " ", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), " ", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestNodeInspect(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.NodeInspect(context.Background(), "node_id", NodeInspectOptions{})
result, err := client.NodeInspect(t.Context(), "node_id", NodeInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(result.Node.ID, "node_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNodeListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeList(context.Background(), NodeListOptions{})
_, err = client.NodeList(t.Context(), NodeListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestNodeList(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.NodeList(context.Background(), listCase.options)
result, err := client.NodeList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(result.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,14 +14,14 @@ func TestNodeRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), "node_id", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NodeRemove(context.Background(), "", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), "", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeRemove(context.Background(), " ", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), " ", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -57,7 +56,7 @@ func TestNodeRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: removeCase.force})
_, err = client.NodeRemove(t.Context(), "node_id", NodeRemoveOptions{Force: removeCase.force})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,20 +14,20 @@ func TestNodeUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NodeUpdate(context.Background(), "", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeUpdate(context.Background(), " ", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), " ", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
@@ -47,7 +46,7 @@ func TestNodeUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})

View File

@@ -1,136 +0,0 @@
package client
import (
"net/http"
"runtime"
"testing"
"time"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestOptionWithHostFromEnv(t *testing.T) {
c, err := New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, ""))
if runtime.GOOS == "windows" {
assert.Check(t, is.Equal(c.host, "npipe:////./pipe/docker_engine"))
assert.Check(t, is.Equal(c.proto, "npipe"))
assert.Check(t, is.Equal(c.addr, "//./pipe/docker_engine"))
} else {
assert.Check(t, is.Equal(c.host, "unix:///var/run/docker.sock"))
assert.Check(t, is.Equal(c.proto, "unix"))
assert.Check(t, is.Equal(c.addr, "/var/run/docker.sock"))
}
t.Setenv("DOCKER_HOST", "tcp://foo.example.com:2376/test/")
c, err = New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, "/test/"))
assert.Check(t, is.Equal(c.host, "tcp://foo.example.com:2376/test/"))
assert.Check(t, is.Equal(c.proto, "tcp"))
assert.Check(t, is.Equal(c.addr, "foo.example.com:2376"))
}
func TestOptionWithTimeout(t *testing.T) {
timeout := 10 * time.Second
c, err := New(WithTimeout(timeout))
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.client.Timeout, timeout))
}
func TestOptionWithVersionFromEnv(t *testing.T) {
c, err := New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.version, MaxAPIVersion))
assert.Check(t, is.Equal(c.manualOverride, false))
t.Setenv("DOCKER_API_VERSION", "2.9999")
c, err = New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.version, "2.9999"))
assert.Check(t, is.Equal(c.manualOverride, true))
}
func TestWithUserAgent(t *testing.T) {
const userAgent = "Magic-Client/v1.2.3"
t.Run("user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("user-agent and custom headers", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithHTTPHeaders(map[string]string{"User-Agent": "should-be-ignored/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("custom headers", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), "from-custom-headers/1.0.0"))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("no user-agent set", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("reset custom user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(""),
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
}

View File

@@ -20,7 +20,7 @@ type PingOptions struct {
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
// with a fixed version ([WithAPIVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
@@ -29,9 +29,8 @@ type PingOptions struct {
NegotiateAPIVersion bool
// ForceNegotiate forces the client to re-negotiate the API version, even if
// API-version negotiation already happened. This option cannot be
// used if the client is configured with a fixed version using (using
// [WithVersion] or [WithVersionFromEnv]).
// API-version negotiation already happened or it the client is configured
// with a fixed version (using [WithAPIVersion] or [WithAPIVersionFromEnv]).
//
// This option has no effect if NegotiateAPIVersion is not set.
ForceNegotiate bool
@@ -72,10 +71,12 @@ type SwarmStatus struct {
// for other non-success status codes, failing to connect to the API, or failing
// to parse the API response.
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
if cli.manualOverride {
if !options.NegotiateAPIVersion {
// No API version negotiation needed; just return ping response.
return cli.ping(ctx)
}
if !options.NegotiateAPIVersion && !cli.negotiateVersion {
if cli.negotiated.Load() && !options.ForceNegotiate {
// API version was already negotiated or manually set.
return cli.ping(ctx)
}
@@ -85,10 +86,19 @@ func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, e
ping, err := cli.ping(ctx)
if err != nil {
return cli.ping(ctx)
return ping, err
}
if cli.negotiated.Load() && !options.ForceNegotiate {
// API version was already negotiated or manually set.
//
// We check cli.negotiated again under lock, to account for race
// conditions with the check at the start of this function.
return ping, nil
}
if ping.APIVersion == "" {
cli.setAPIVersion(MaxAPIVersion)
return ping, nil
}
@@ -112,10 +122,15 @@ func (cli *Client) ping(ctx context.Context) (PingResult, error) {
// response-body to get error details from.
return newPingResult(resp), nil
}
// close to allow reusing connection.
ensureReaderClosed(resp)
// HEAD failed or returned a non-OK status; fallback to GET.
req.Method = http.MethodGet
resp, err = cli.doRequest(req)
req2, err := cli.buildRequest(ctx, http.MethodGet, path.Join(cli.basePath, "/_ping"), nil, nil)
if err != nil {
return PingResult{}, err
}
resp, err = cli.doRequest(req2)
defer ensureReaderClosed(resp)
if err != nil {
// Failed to connect.

View File

@@ -18,7 +18,7 @@ import (
// panics.
func TestPingFail(t *testing.T) {
var withHeader bool
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
var hdr http.Header
if withHeader {
hdr = http.Header{}
@@ -48,7 +48,7 @@ func TestPingFail(t *testing.T) {
// TestPingWithError tests the case where there is a protocol error in the ping.
// This test is mostly just testing that there are no panics in this code path.
func TestPingWithError(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("some connection error")
}))
assert.NilError(t, err)
@@ -64,7 +64,7 @@ func TestPingWithError(t *testing.T) {
// TestPingSuccess tests that we are able to get the expected API headers/ping
// details on success.
func TestPingSuccess(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
hdr := http.Header{}
hdr.Set("Api-Version", "awesome")
hdr.Set("Docker-Experimental", "true")
@@ -76,6 +76,7 @@ func TestPingSuccess(t *testing.T) {
assert.NilError(t, err)
assert.Check(t, is.Equal(true, ping.Experimental))
assert.Check(t, is.Equal("awesome", ping.APIVersion))
assert.Check(t, is.Equal(MaxAPIVersion, client.version))
assert.Check(t, is.Equal(SwarmStatus{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
}
@@ -109,7 +110,7 @@ func TestPingHeadFallback(t *testing.T) {
for _, tc := range tests {
t.Run(http.StatusText(tc.status), func(t *testing.T) {
var reqs []string
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedPath) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedPath, req.URL.Path)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginDisableError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "plugin_name", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginDisable(context.Background(), "", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginDisable(context.Background(), " ", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), " ", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginDisable(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "plugin_name", PluginDisableOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginEnableError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "plugin_name", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginEnable(context.Background(), "", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginEnable(context.Background(), " ", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), " ", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginEnable(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "plugin_name", PluginEnableOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestPluginListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginList(context.Background(), PluginListOptions{})
_, err = client.PluginList(t.Context(), PluginListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -71,7 +70,7 @@ func TestPluginList(t *testing.T) {
}))
assert.NilError(t, err)
list, err := client.PluginList(context.Background(), PluginListOptions{
list, err := client.PluginList(t.Context(), PluginListOptions{
Filters: listCase.filters,
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestPluginPushError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), "plugin_name", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginPush(context.Background(), "", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), "", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginPush(context.Background(), " ", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), " ", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -43,6 +42,6 @@ func TestPluginPush(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{RegistryAuth: "authtoken"})
_, err = client.PluginPush(t.Context(), "plugin_name", PluginPushOptions{RegistryAuth: "authtoken"})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "plugin_name", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginRemove(context.Background(), "", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginRemove(context.Background(), " ", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), " ", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "plugin_name", PluginRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginSetError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), "plugin_name", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginSet(context.Background(), "", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), "", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginSet(context.Background(), " ", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), " ", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginSet(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{Args: []string{"arg1"}})
_, err = client.PluginSet(t.Context(), "plugin_name", PluginSetOptions{Args: []string{"arg1"}})
assert.NilError(t, err)
}

View File

@@ -67,31 +67,22 @@ func (cli *Client) delete(ctx context.Context, path string, query url.Values, he
// prepareJSONRequest encodes the given body to JSON and returns it as an [io.Reader], and sets the Content-Type
// header. If body is nil, or a nil-interface, a "nil" body is returned without
// error.
//
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
// TODO(thaJeztah): is "nil" the appropriate approach for an empty body, or should we use [http.NoBody] (or similar)?
func prepareJSONRequest(body any, headers http.Header) (io.Reader, http.Header, error) {
if body == nil {
return nil, headers, nil
}
// encoding/json encodes a nil pointer as the JSON document `null`,
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
// That is almost certainly not what the caller intended as the request body.
//
// TODO(thaJeztah): consider moving this to jsonEncode, which would also allow returning an (empty) reader instead of nil.
if reflect.TypeOf(body).Kind() == reflect.Ptr && reflect.ValueOf(body).IsNil() {
return nil, headers, nil
}
jsonBody, err := jsonEncode(body)
if err != nil {
return nil, headers, err
}
if jsonBody == nil || jsonBody == http.NoBody {
// no content-type is set on empty requests.
return jsonBody, headers, nil
}
hdr := http.Header{}
if headers != nil {
hdr = headers.Clone()
}
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
hdr.Set("Content-Type", "application/json")
return jsonBody, hdr, nil
}
@@ -110,9 +101,6 @@ func (cli *Client) buildRequest(ctx context.Context, method, path string, body i
req.Host = DummyHost
}
if body != nil && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
return req, nil
}
@@ -248,7 +236,11 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
if statusMsg == "" {
statusMsg = http.StatusText(serverResp.StatusCode)
}
if serverResp.Body != nil {
var reqMethod string
if serverResp.Request != nil {
reqMethod = serverResp.Request.Method
}
if serverResp.Body != nil && reqMethod != http.MethodHead {
bodyMax := 1 * 1024 * 1024 // 1 MiB
bodyR := &io.LimitedReader{
R: serverResp.Body,
@@ -333,25 +325,49 @@ func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Requ
}
func jsonEncode(data any) (io.Reader, error) {
var params bytes.Buffer
if data != nil {
if err := json.NewEncoder(&params).Encode(data); err != nil {
return nil, err
switch x := data.(type) {
case nil:
return http.NoBody, nil
case io.Reader:
// http.NoBody or other readers
return x, nil
case json.RawMessage:
if len(x) == 0 {
return http.NoBody, nil
}
return bytes.NewReader(x), nil
}
return &params, nil
// encoding/json encodes a nil pointer as the JSON document `null`,
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
// That is almost certainly not what the caller intended as the request body.
if v := reflect.ValueOf(data); v.Kind() == reflect.Ptr && v.IsNil() {
return http.NoBody, nil
}
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
func ensureReaderClosed(response *http.Response) {
if response != nil && response.Body != nil {
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
// see https://github.com/google/go-github/pull/317/files#r57536827
//
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
// and check if context-cancellation should handle this as well. If still needed, consider
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
// methods.
_, _ = io.CopyN(io.Discard, response.Body, 512)
_ = response.Body.Close()
if response == nil || response.Body == nil {
return
}
if response.ContentLength == 0 || (response.Request != nil && response.Request.Method == http.MethodHead) {
// No need to drain head requests or zero-length responses.
_ = response.Body.Close()
return
}
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
// see https://github.com/google/go-github/pull/317/files#r57536827
//
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
// and check if context-cancellation should handle this as well. If still needed, consider
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
// methods.
_, _ = io.CopyN(io.Discard, response.Body, 512)
_ = response.Body.Close()
}

View File

@@ -7,7 +7,6 @@ import (
"io"
"math/rand"
"net/http"
"strings"
"testing"
"time"
@@ -64,7 +63,7 @@ func TestSetHostHeader(t *testing.T) {
}), WithHost(tc.host))
assert.NilError(t, err)
_, err = client.sendRequest(context.Background(), http.MethodGet, testEndpoint, nil, nil, nil)
_, err = client.sendRequest(t.Context(), http.MethodGet, testEndpoint, nil, nil, nil)
assert.NilError(t, err)
})
}
@@ -76,7 +75,7 @@ func TestSetHostHeader(t *testing.T) {
func TestPlainTextError(t *testing.T) {
client, err := New(WithMockClient(mockResponse(http.StatusInternalServerError, nil, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerList(context.Background(), ContainerListOptions{})
_, err = client.ContainerList(t.Context(), ContainerListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -195,11 +194,11 @@ func TestResponseErrors(t *testing.T) {
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return mockResponse(http.StatusBadRequest, http.Header{"Content-Type": []string{tc.contentType}}, tc.response)(req)
}))
if tc.apiVersion != "" {
client, err = New(WithHTTPClient(client.client), WithVersion(tc.apiVersion))
client, err = New(WithHTTPClient(client.client), WithAPIVersion(tc.apiVersion))
}
assert.NilError(t, err)
_, err = client.Ping(t.Context(), PingOptions{})
@@ -211,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
func TestInfiniteError(t *testing.T) {
infinitR := rand.New(rand.NewSource(42))
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
resp := &http.Response{
StatusCode: http.StatusInternalServerError,
Header: http.Header{},
@@ -235,7 +234,7 @@ func TestCanceledContext(t *testing.T) {
}))
assert.NilError(t, err)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
cancel()
_, err = client.sendRequest(ctx, http.MethodGet, testEndpoint, nil, nil, nil)
@@ -251,7 +250,7 @@ func TestDeadlineExceededContext(t *testing.T) {
}))
assert.NilError(t, err)
ctx, cancel := context.WithDeadline(context.Background(), time.Now())
ctx, cancel := context.WithDeadline(t.Context(), time.Now())
defer cancel()
<-ctx.Done()
@@ -266,26 +265,25 @@ func TestPrepareJSONRequest(t *testing.T) {
body any
headers http.Header
expBody string
expNilBody bool
expHeaders http.Header
}{
{
doc: "nil body",
body: nil,
headers: http.Header{"Something": []string{"something"}},
expNilBody: true,
doc: "nil body",
body: nil,
headers: http.Header{"Something": []string{"something"}},
expBody: "",
expHeaders: http.Header{
// currently, no content-type is set on empty requests.
// no content-type is set on empty requests.
"Something": []string{"something"},
},
},
{
doc: "nil interface body",
body: (*struct{})(nil),
headers: http.Header{"Something": []string{"something"}},
expNilBody: true,
doc: "nil interface body",
body: (*struct{})(nil),
headers: http.Header{"Something": []string{"something"}},
expBody: "",
expHeaders: http.Header{
// currently, no content-type is set on empty requests.
// no content-type is set on empty requests.
"Something": []string{"something"},
},
},
@@ -308,12 +306,16 @@ func TestPrepareJSONRequest(t *testing.T) {
},
},
{
doc: "empty body",
body: http.NoBody,
expBody: `{}`,
expHeaders: http.Header{
"Content-Type": []string{"application/json"},
},
doc: "empty json raw message",
body: json.RawMessage(""),
expBody: "",
expHeaders: nil, // no content-type is set on empty requests.
},
{
doc: "empty body",
body: http.NoBody,
expBody: "",
expHeaders: nil, // no content-type is set on empty requests.
},
}
@@ -322,16 +324,9 @@ func TestPrepareJSONRequest(t *testing.T) {
req, hdr, err := prepareJSONRequest(tc.body, tc.headers)
assert.NilError(t, err)
var body string
if tc.expNilBody {
assert.Check(t, is.Nil(req))
} else {
assert.Assert(t, req != nil)
resp, err := io.ReadAll(req)
assert.NilError(t, err)
body = strings.TrimSpace(string(resp))
}
resp, err := io.ReadAll(req)
assert.NilError(t, err)
body := string(resp)
assert.Check(t, is.Equal(body, tc.expBody))
assert.Check(t, is.DeepEqual(hdr, tc.expHeaders))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ import (
func TestSecretCreateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretCreate(context.Background(), SecretCreateOptions{})
_, err = client.SecretCreate(t.Context(), SecretCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -30,7 +29,7 @@ func TestSecretCreate(t *testing.T) {
}))
assert.NilError(t, err)
r, err := client.SecretCreate(context.Background(), SecretCreateOptions{})
r, err := client.SecretCreate(t.Context(), SecretCreateOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "test_secret"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestSecretInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "nothing", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "nothing", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestSecretInspectSecretNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "unknown", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "unknown", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestSecretInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretInspect(context.Background(), " ", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), " ", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestSecretInspect(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.SecretInspect(context.Background(), "secret_id", SecretInspectOptions{})
res, err := client.SecretInspect(t.Context(), "secret_id", SecretInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(res.Secret.ID, "secret_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestSecretListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretList(context.Background(), SecretListOptions{})
_, err = client.SecretList(t.Context(), SecretListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestSecretList(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.SecretList(context.Background(), listCase.options)
res, err := client.SecretList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestSecretRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "secret_id", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.SecretRemove(context.Background(), "", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretRemove(context.Background(), " ", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), " ", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestSecretRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "secret_id", SecretRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestSecretUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "secret_id", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.SecretUpdate(context.Background(), "", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretUpdate(context.Background(), " ", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), " ", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestSecretUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "secret_id", SecretUpdateOptions{})
assert.NilError(t, err)
}

View File

@@ -59,16 +59,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
}
@@ -93,35 +95,33 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
}
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
var warning string
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
warning = digestWarning(taskSpec.ContainerSpec.Image)
} else {
taskSpec.ContainerSpec.Image = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth)
if err != nil {
return digestWarning(taskSpec.ContainerSpec.Image)
}
return warning
taskSpec.ContainerSpec.Image = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
return ""
}
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
var warning string
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
warning = digestWarning(taskSpec.PluginSpec.Remote)
} else {
taskSpec.PluginSpec.Remote = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth)
if err != nil {
return digestWarning(taskSpec.PluginSpec.Remote)
}
return warning
taskSpec.PluginSpec.Remote = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
return ""
}
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {

View File

@@ -29,7 +29,7 @@ func TestServiceCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestServiceCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ServiceCreate(t.Context(), ServiceCreateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestServiceInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "nothing", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "nothing", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestServiceInspectServiceNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "unknown", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "unknown", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestServiceInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceInspect(context.Background(), " ", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), " ", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestServiceInspect(t *testing.T) {
}))
assert.NilError(t, err)
inspect, err := client.ServiceInspect(context.Background(), "service_id", ServiceInspectOptions{})
inspect, err := client.ServiceInspect(t.Context(), "service_id", ServiceInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.Service.ID, "service_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestServiceListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceList(context.Background(), ServiceListOptions{})
_, err = client.ServiceList(t.Context(), ServiceListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestServiceList(t *testing.T) {
}))
assert.NilError(t, err)
list, err := client.ServiceList(context.Background(), listCase.options)
list, err := client.ServiceList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(list.Items, 2))
}

View File

@@ -129,7 +129,7 @@ func TestServiceLogs(t *testing.T) {
}
func ExampleClient_ServiceLogs_withTimeout() {
client, err := New(FromEnv, WithAPIVersionNegotiation())
client, err := New(FromEnv)
if err != nil {
log.Fatal(err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestServiceRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ServiceRemove(context.Background(), "", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceRemove(context.Background(), " ", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), " ", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -30,7 +29,7 @@ func TestServiceRemoveNotFoundError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "no such service: service_id")))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "no such service: service_id"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -46,6 +45,6 @@ func TestServiceRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -82,16 +82,18 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
}

View File

@@ -32,7 +32,7 @@ func TestServiceUpdateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestServiceUpdateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ServiceUpdate(t.Context(), "service_id", ServiceUpdateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestSwarmGetUnlockKeyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmGetUnlockKey(context.Background())
_, err = client.SwarmGetUnlockKey(t.Context())
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,7 +34,7 @@ func TestSwarmGetUnlockKey(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.SwarmGetUnlockKey(context.Background())
result, err := client.SwarmGetUnlockKey(t.Context())
assert.NilError(t, err)
assert.Check(t, is.Equal(unlockKey, result.Key))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmInitError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmInit(context.Background(), SwarmInitOptions{})
_, err = client.SwarmInit(t.Context(), SwarmInitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,7 +28,7 @@ func TestSwarmInit(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.SwarmInit(context.Background(), SwarmInitOptions{
result, err := client.SwarmInit(t.Context(), SwarmInitOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmJoinError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{})
_, err = client.SwarmJoin(t.Context(), SwarmJoinOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,7 +28,7 @@ func TestSwarmJoin(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{
_, err = client.SwarmJoin(t.Context(), SwarmJoinOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestSwarmLeaveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{})
_, err = client.SwarmLeave(t.Context(), SwarmLeaveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -48,7 +47,7 @@ func TestSwarmLeave(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{Force: leaveCase.force})
_, err = client.SwarmLeave(t.Context(), SwarmLeaveOptions{Force: leaveCase.force})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmUnlockError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(t.Context(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,6 +28,6 @@ func TestSwarmUnlock(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(t.Context(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.NilError(t, err)
}

View File

@@ -133,7 +133,7 @@ func TestLegacyDiskUsage(t *testing.T) {
const legacyVersion = "1.51"
const expectedURL = "/system/df"
client, err := New(
WithVersion(legacyVersion),
WithAPIVersion(legacyVersion),
WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodGet, "/v"+legacyVersion+expectedURL); err != nil {
return nil, err

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@@ -37,8 +36,8 @@ func TestEventsErrorInOptions(t *testing.T) {
for _, tc := range errorCases {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
events := client.Events(context.Background(), tc.options)
err = <-events.Err
res := client.Events(t.Context(), tc.options)
err = <-res.Err
assert.Check(t, is.ErrorContains(err, tc.expectedError))
}
}
@@ -46,8 +45,8 @@ func TestEventsErrorInOptions(t *testing.T) {
func TestEventsErrorFromServer(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
events := client.Events(context.Background(), EventsListOptions{})
err = <-events.Err
res := client.Events(t.Context(), EventsListOptions{})
err = <-res.Err
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -133,18 +132,18 @@ func TestEvents(t *testing.T) {
}))
assert.NilError(t, err)
events := client.Events(context.Background(), eventsCase.options)
res := client.Events(t.Context(), eventsCase.options)
loop:
for {
select {
case err := <-events.Err:
case err := <-res.Err:
if err != nil && !errors.Is(err, io.EOF) {
t.Fatal(err)
}
break loop
case e := <-events.Messages:
case e := <-res.Messages:
_, ok := eventsCase.expectedEvents[e.Actor.ID]
assert.Check(t, ok, "event received not expected with action %s & id %s", e.Action, e.Actor.ID)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ import (
func TestInfoServerError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.Info(context.Background(), InfoOptions{})
_, err = client.Info(t.Context(), InfoOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestInfoInvalidResponseJSONError(t *testing.T) {
client, err := New(WithMockClient(mockResponse(http.StatusOK, nil, "invalid json")))
assert.NilError(t, err)
_, err = client.Info(context.Background(), InfoOptions{})
_, err = client.Info(t.Context(), InfoOptions{})
assert.Check(t, is.ErrorContains(err, "invalid character"))
}
@@ -38,7 +37,7 @@ func TestInfo(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.Info(context.Background(), InfoOptions{})
result, err := client.Info(t.Context(), InfoOptions{})
assert.NilError(t, err)
info := result.Info
@@ -69,7 +68,7 @@ func TestInfoWithDiscoveredDevices(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.Info(context.Background(), InfoOptions{})
result, err := client.Info(t.Context(), InfoOptions{})
assert.NilError(t, err)
info := result.Info

Some files were not shown because too many files have changed in this diff Show More