mirror of
https://github.com/moby/moby.git
synced 2026-01-12 11:11:44 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bea959c7b7 | ||
|
|
3e9ff78b94 | ||
|
|
29ed80aa86 | ||
|
|
da489a11d4 | ||
|
|
f173e45ae9 | ||
|
|
e4b1f89996 | ||
|
|
0c9e14dcce | ||
|
|
bf6d688157 | ||
|
|
4205776b85 | ||
|
|
e77ff99ede | ||
|
|
6e3cf7f12b | ||
|
|
38c0abffce | ||
|
|
3b7d703484 | ||
|
|
d14a60fe24 | ||
|
|
da65c869b5 | ||
|
|
76fbfe9953 | ||
|
|
bfade89ec2 | ||
|
|
a818cfd87b | ||
|
|
653777a522 | ||
|
|
1ad3df4768 | ||
|
|
6323db8e78 | ||
|
|
a3c8f7fa8c | ||
|
|
dfbba63a34 | ||
|
|
5beb93de84 | ||
|
|
e17e96e3c5 | ||
|
|
e0183475e0 | ||
|
|
a2af8bdebd | ||
|
|
265f709647 | ||
|
|
b2a9318a1e | ||
|
|
b3e2e22b2a | ||
|
|
c571cd8513 | ||
|
|
8c713c1af4 | ||
|
|
539c115023 | ||
|
|
8e7ea470cf | ||
|
|
222baf4ccb | ||
|
|
1627e828d7 | ||
|
|
4070ebda88 | ||
|
|
b613ac489e | ||
|
|
0e0ca09ddc | ||
|
|
e62b0e2234 | ||
|
|
06ab9cd1ed | ||
|
|
97aa4e8550 | ||
|
|
e18a9c95b8 | ||
|
|
b959bebdfc | ||
|
|
02ade1a34c | ||
|
|
106c4b0af6 | ||
|
|
54d2eee6d6 | ||
|
|
09fef2b26e | ||
|
|
44c8cd2e8f | ||
|
|
78b6204f9e | ||
|
|
cf98237186 | ||
|
|
fd96b01b0e | ||
|
|
6a1fb46d48 | ||
|
|
7acb079403 | ||
|
|
0df31cf585 | ||
|
|
83b2fc245d | ||
|
|
e079583ab4 | ||
|
|
cfd5e5e4d4 | ||
|
|
576cf73add | ||
|
|
2297ae3e64 | ||
|
|
cc60ec8d3c | ||
|
|
b5b349dbd6 | ||
|
|
35916f0869 | ||
|
|
3eb59ba5a2 | ||
|
|
5d6ae34753 | ||
|
|
ea818a7f6f | ||
|
|
78ccc20545 |
@@ -2,5 +2,5 @@
|
||||
|
||||
# build artifacts
|
||||
/bundles/
|
||||
/cmd/dockerd/winresources/winres.json
|
||||
/cmd/dockerd/*.syso
|
||||
/cli/winresources/dockerd/winres.json
|
||||
/cli/winresources/dockerd/*.syso
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
Dockerfile* linguist-language=Dockerfile
|
||||
vendor.mod linguist-language=Go-Module
|
||||
vendor.sum linguist-language=Go-Checksums
|
||||
|
||||
15
.github/actions/setup-tracing/action.yml
vendored
15
.github/actions/setup-tracing/action.yml
vendored
@@ -6,18 +6,9 @@ runs:
|
||||
steps:
|
||||
- run: |
|
||||
set -e
|
||||
# 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"
|
||||
# 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
|
||||
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
|
||||
|
||||
159
.github/labeler.yml
vendored
159
.github/labeler.yml
vendored
@@ -1,159 +0,0 @@
|
||||
module/client:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'client/**'
|
||||
|
||||
module/api:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'api/**'
|
||||
|
||||
area/daemon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'daemon/**'
|
||||
|
||||
area/builder/buildkit:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*buildkit*'
|
||||
- 'daemon/internal/builder-next/**'
|
||||
|
||||
area/builder/classic-builder:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/images/*_build*'
|
||||
- 'daemon/builder/**'
|
||||
|
||||
area/builder:
|
||||
- labels:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*buildkit*'
|
||||
- 'daemon/internal/builder-next/**'
|
||||
- 'daemon/images/*_build*'
|
||||
- 'daemon/builder/**'
|
||||
|
||||
area/networking:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/network*'
|
||||
- 'daemon/network/**'
|
||||
- 'api/types/network/**'
|
||||
- 'integration/network/**'
|
||||
- 'integration/networking/**'
|
||||
|
||||
area/volumes:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/volume/**'
|
||||
- 'api/types/volume/**'
|
||||
- 'integration/volume/**'
|
||||
|
||||
area/swarm:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/cluster/**'
|
||||
- 'api/types/swarm/**'
|
||||
|
||||
area/images:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/images/**'
|
||||
- 'api/types/image/**'
|
||||
- 'integration/image/**'
|
||||
|
||||
area/logging:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'daemon/logger/**'
|
||||
- '**/*log*'
|
||||
|
||||
area/security:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*seccomp*'
|
||||
- '**/*apparmor*'
|
||||
- '**/*selinux*'
|
||||
|
||||
area/security/apparmor:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*apparmor*'
|
||||
- 'contrib/apparmor/**'
|
||||
|
||||
area/security/selinux:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*selinux*'
|
||||
- 'contrib/selinux/**'
|
||||
|
||||
area/security/seccomp:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*seccomp*'
|
||||
|
||||
area/systemd:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*systemd*'
|
||||
- 'contrib/init/systemd/**'
|
||||
|
||||
area/contrib:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'contrib/**'
|
||||
|
||||
area/packaging:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
# files used in packaging
|
||||
- 'contrib/dockerd-rootless.sh'
|
||||
- 'contrib/dockerd-rootless-setuptool.sh'
|
||||
- 'contrib/init/systemd/**'
|
||||
|
||||
containerd-integration:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'daemon/containerd/**'
|
||||
|
||||
area/rootless:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*rootless*'
|
||||
- 'contrib/dockerd-rootless*'
|
||||
|
||||
area/testing:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files:
|
||||
- 'integration/**'
|
||||
- 'integration-cli/**'
|
||||
- '**/*_test.go'
|
||||
- 'internal/test*'
|
||||
- 'internal/testutil/**'
|
||||
|
||||
area/docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'api/docs/*.yaml'
|
||||
- 'docs/**'
|
||||
- '**/*.md'
|
||||
- 'man/**'
|
||||
|
||||
area/dependencies:
|
||||
- all:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'vendor/**'
|
||||
- all-globs-to-all-files:
|
||||
- '!client/**'
|
||||
- '!api/**'
|
||||
|
||||
area/ci:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '.github/**'
|
||||
|
||||
platform/windows:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*_windows.go'
|
||||
- 'Dockerfile.windows'
|
||||
|
||||
impact/changelog:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'api/docs/CHANGELOG.md'
|
||||
4
.github/workflows/.dco.yml
vendored
4
.github/workflows/.dco.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
ALPINE_VERSION: "3.22"
|
||||
ALPINE_VERSION: "3.21"
|
||||
|
||||
jobs:
|
||||
run:
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
|
||||
45
.github/workflows/.test-prepare.yml
vendored
Normal file
45
.github/workflows/.test-prepare.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# reusable workflow
|
||||
name: .test-prepare
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
matrix:
|
||||
description: Test matrix
|
||||
value: ${{ jobs.run.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
outputs:
|
||||
matrix: ${{ steps.set.outputs.matrix }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: set
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let matrix = ['graphdriver'];
|
||||
if ("${{ contains(github.event.pull_request.labels.*.name, 'containerd-integration') || github.event_name != 'pull_request' }}" == "true") {
|
||||
matrix.push('snapshotter');
|
||||
}
|
||||
await core.group(`Set matrix`, async () => {
|
||||
core.info(`matrix: ${JSON.stringify(matrix)}`);
|
||||
core.setOutput('matrix', JSON.stringify(matrix));
|
||||
});
|
||||
12
.github/workflows/.test-unit.yml
vendored
12
.github/workflows/.test-unit.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.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@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-unit--${{ matrix.mode }}
|
||||
path: /tmp/reports/*
|
||||
@@ -103,13 +103,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: test-reports-unit-*
|
||||
path: /tmp/reports
|
||||
|
||||
93
.github/workflows/.test.yml
vendored
93
.github/workflows/.test.yml
vendored
@@ -21,13 +21,13 @@ on:
|
||||
default: "graphdriver"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.5"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
ITG_CLI_MATRIX_SIZE: 6
|
||||
DOCKER_EXPERIMENTAL: 1
|
||||
DOCKER_GRAPHDRIVER: ${{ inputs.storage == 'snapshotter' && 'overlayfs' || 'overlay2' }}
|
||||
TEST_INTEGRATION_USE_GRAPHDRIVER: ${{ inputs.storage == 'graphdriver' && '1' || '' }}
|
||||
TEST_INTEGRATION_USE_SNAPSHOTTER: ${{ inputs.storage == 'snapshotter' && '1' || '' }}
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -68,12 +68,13 @@ 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()
|
||||
@@ -82,7 +83,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-docker-py-${{ inputs.storage }}
|
||||
path: /tmp/reports/*
|
||||
@@ -95,7 +96,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -143,9 +144,7 @@ jobs:
|
||||
// { os: 'ubuntu-24.04', mode: 'rootless-systemd' }, // FIXME: https://github.com/moby/moby/issues/44084
|
||||
];
|
||||
if ("${{ inputs.storage }}" == "snapshotter") {
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'iptables+firewalld' });
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'nftables' });
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'nftables+firewalld' });
|
||||
includes.push({ os: 'ubuntu-24.04', mode: 'firewalld' });
|
||||
}
|
||||
await core.group(`Set matrix`, async () => {
|
||||
core.info(`matrix: ${JSON.stringify(includes)}`);
|
||||
@@ -169,7 +168,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -191,9 +190,6 @@ jobs:
|
||||
echo "FIREWALLD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}firewalld"
|
||||
fi
|
||||
if [[ "${{ matrix.mode }}" == *"nftables"* ]]; then
|
||||
echo "DOCKER_FIREWALL_BACKEND=nftables" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
@@ -227,13 +223,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
|
||||
@@ -250,7 +246,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-integration-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
@@ -266,13 +262,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: test-reports-integration-${{ inputs.storage }}-*
|
||||
@@ -295,13 +291,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Install gotestlist
|
||||
run:
|
||||
@@ -332,43 +328,19 @@ jobs:
|
||||
// 'include' with other matrix variables that aren't part of the
|
||||
// include items.
|
||||
// Moreover, since the goal is to run only relevant tests with
|
||||
// firewalld/nftables enabled to minimize the number of CI jobs, we
|
||||
// firewalld enabled to minimize the number of CI jobs, we
|
||||
// statically define the list of test suites that we want to run.
|
||||
if ("${{ inputs.storage }}" == "snapshotter") {
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'mode': 'firewalld',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'mode': 'firewalld',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'iptables+firewalld',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'test': 'DockerCLINetworkSuite|DockerCLIPortSuite|DockerDaemonSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'test': 'DockerSwarmSuite'
|
||||
});
|
||||
matrix.include.push({
|
||||
'mode': 'nftables+firewalld',
|
||||
'mode': 'firewalld',
|
||||
'test': 'DockerNetworkSuite'
|
||||
});
|
||||
}
|
||||
@@ -393,7 +365,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -408,9 +380,6 @@ jobs:
|
||||
echo "FIREWALLD=true" >> $GITHUB_ENV
|
||||
CACHE_DEV_SCOPE="${CACHE_DEV_SCOPE}firewalld"
|
||||
fi
|
||||
if [[ "${{ matrix.mode }}" == *"nftables"* ]]; then
|
||||
echo "DOCKER_FIREWALL_BACKEND=nftables" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "CACHE_DEV_SCOPE=${CACHE_DEV_SCOPE}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
@@ -442,14 +411,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
|
||||
@@ -466,9 +435,9 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-${{ env.TESTREPORTS_NAME }}
|
||||
name: test-reports-integration-cli-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
@@ -482,16 +451,16 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-*
|
||||
pattern: test-reports-integration-cli-${{ inputs.storage }}-*
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Install teststat
|
||||
|
||||
212
.github/workflows/.vm.yml
vendored
212
.github/workflows/.vm.yml
vendored
@@ -1,212 +0,0 @@
|
||||
# reusable workflow
|
||||
name: .vm
|
||||
|
||||
# TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
template:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
TEMPLATE_NAME: ${{ inputs.template }}
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mode:
|
||||
- ""
|
||||
- rootless
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Set up Lima
|
||||
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
|
||||
id: lima-actions-setup
|
||||
with:
|
||||
version: v2.0.2
|
||||
-
|
||||
name: Cache ~/.cache/lima
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/lima
|
||||
key: lima-${{ steps.lima-actions-setup.outputs.version }}-${{ inputs.template }}
|
||||
-
|
||||
name: Start the guest VM
|
||||
run: |
|
||||
# --plain is set because the built-in containerd support conflicts with Docker
|
||||
limactl start \
|
||||
--name=default \
|
||||
--cpus=4 \
|
||||
--memory=12 \
|
||||
--plain \
|
||||
${{ inputs.template }}
|
||||
-
|
||||
name: Load kernel modules in the guest VM
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
cat <<-EOF | lima sudo tee /etc/modules-load.d/docker.conf
|
||||
br_netfilter
|
||||
bridge
|
||||
ip6_tables
|
||||
ip6table_filter
|
||||
ip6table_nat
|
||||
ip_tables
|
||||
ip_vs
|
||||
iptable_filter
|
||||
iptable_nat
|
||||
nf_tables
|
||||
overlay
|
||||
tap
|
||||
tun
|
||||
veth
|
||||
x_tables
|
||||
xt_addrtype
|
||||
xt_comment
|
||||
xt_conntrack
|
||||
xt_mark
|
||||
xt_multiport
|
||||
xt_nat
|
||||
xt_tcpudp
|
||||
EOF
|
||||
lima sudo systemctl restart systemd-modules-load.service
|
||||
-
|
||||
name: Install dockerd in the guest VM
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
lima sudo mkdir -p /etc/systemd/system/docker.socket.d
|
||||
cat <<-EOF | lima sudo tee /etc/systemd/system/docker.socket.d/override.conf
|
||||
[Socket]
|
||||
SocketUser=$(whoami)
|
||||
EOF
|
||||
# TODO: use native packages for AlmaLinux: https://github.com/docker/packaging/pull/138
|
||||
lima sudo dnf config-manager --add-repo=https://download.docker.com/linux/rhel/docker-ce.repo
|
||||
lima sudo dnf -q -y install --nobest docker-ce make
|
||||
lima sudo systemctl enable --now docker
|
||||
lima docker info
|
||||
-
|
||||
name: Copy the current directory
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
limactl cp -r . default:/tmp/docker
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
DOCKER_ROOTLESS=
|
||||
DOCKER_GRAPHDRIVER=overlay2
|
||||
if [[ "${{ matrix.mode }}" == *"rootless"* ]]; then
|
||||
DOCKER_ROOTLESS=1
|
||||
if lima grep -q "AlmaLinux release 8" /etc/system-release; then
|
||||
# kernel prior to 5.11 needs fuse-overlayfs
|
||||
DOCKER_GRAPHDRIVER=fuse-overlayfs
|
||||
fi
|
||||
fi
|
||||
|
||||
DOCKER_IGNORE_BR_NETFILTER_ERROR=
|
||||
if lima grep -q "AlmaLinux release 8" /etc/system-release; then
|
||||
# DOCKER_IGNORE_BR_NETFILTER_ERROR=1 is set because /proc/sys/net/bridge does not appear in
|
||||
# a container when the kernel is older than 5.3.
|
||||
# https://web.archive.org/web/20201123224428/github.com/lxc/lxd/issues/3306#issuecomment-502857864
|
||||
DOCKER_IGNORE_BR_NETFILTER_ERROR=1
|
||||
fi
|
||||
|
||||
# TODO: just propagate the env from the host: https://github.com/lima-vm/lima/issues/3430
|
||||
# TODO: enable GHA cache?
|
||||
LIMA_WORKDIR=/tmp/docker lima \
|
||||
TEST_SKIP_INTEGRATION_CLI=1 \
|
||||
TEST_INTEGRATION_USE_GRAPHDRIVER=1 \
|
||||
DOCKER_ROOTLESS=${DOCKER_ROOTLESS} \
|
||||
DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER} \
|
||||
DOCKER_IGNORE_BR_NETFILTER_ERROR=${DOCKER_IGNORE_BR_NETFILTER_ERROR} \
|
||||
make test-integration
|
||||
-
|
||||
name: Prepare reports
|
||||
if: always()
|
||||
run: |
|
||||
set -eux -o pipefail
|
||||
limactl cp -v -r default:/tmp/docker/bundles . || true
|
||||
reportsName="${{ env.TEMPLATE_NAME }}"
|
||||
reportsName="${reportsName#template:}"
|
||||
if [ -n "${{ matrix.mode }}" ]; then
|
||||
reportsName="$reportsName-${{ matrix.mode }}"
|
||||
fi
|
||||
reportsPath="/tmp/reports/$reportsName"
|
||||
echo "TESTREPORTS_NAME=$reportsName" >> $GITHUB_ENV
|
||||
|
||||
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
|
||||
sudo chown -R $(id -u):$(id -g) $reportsPath
|
||||
tree -nh $reportsPath
|
||||
-
|
||||
name: Test daemon logs
|
||||
if: always()
|
||||
run: |
|
||||
cat bundles/test-integration/docker.log
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-reports-integration-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
integration-report:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always() && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only'))
|
||||
needs:
|
||||
- integration
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
-
|
||||
name: Prepare reports
|
||||
run: |
|
||||
TEMPLATE="${{ env.TEMPLATE_NAME }}"
|
||||
TEMPLATE="${TEMPLATE#template:}"
|
||||
echo "TESTREPORTS_NAME=${TEMPLATE}*" >> $GITHUB_ENV
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: test-reports-integration-${{ env.TESTREPORTS_NAME }}
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Install teststat
|
||||
run: |
|
||||
go install github.com/vearutop/teststat@${{ env.TESTSTAT_VERSION }}
|
||||
-
|
||||
name: Create summary
|
||||
run: |
|
||||
find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY
|
||||
110
.github/workflows/.windows.yml
vendored
110
.github/workflows/.windows.yml
vendored
@@ -28,7 +28,7 @@ on:
|
||||
default: false
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.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@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
-
|
||||
@@ -70,6 +70,18 @@ jobs:
|
||||
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
-
|
||||
name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
${{ github.workspace }}\go-build
|
||||
${{ env.GOPATH }}\pkg\mod
|
||||
key: ${{ inputs.os }}-${{ github.job }}-${{ hashFiles('**/vendor.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.os }}-${{ github.job }}-
|
||||
-
|
||||
name: Docker info
|
||||
run: |
|
||||
@@ -86,6 +98,8 @@ jobs:
|
||||
name: Build binaries
|
||||
run: |
|
||||
& docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" `
|
||||
-v "${{ github.workspace }}\go-build:C:\Users\ContainerAdministrator\AppData\Local\go-build" `
|
||||
-v "${{ github.workspace }}\go\pkg\mod:C:\gopath\pkg\mod" `
|
||||
${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -Daemon -Client
|
||||
-
|
||||
name: Copy artifacts
|
||||
@@ -98,7 +112,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@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ inputs.storage }}-${{ inputs.os }}
|
||||
path: ${{ env.BIN_OUT }}/*
|
||||
@@ -117,7 +131,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
-
|
||||
@@ -135,6 +149,18 @@ jobs:
|
||||
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
|
||||
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
-
|
||||
name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
${{ github.workspace }}\go-build
|
||||
${{ env.GOPATH }}\pkg\mod
|
||||
key: ${{ inputs.os }}-${{ github.job }}-${{ hashFiles('**/vendor.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.os }}-${{ github.job }}-
|
||||
-
|
||||
name: Docker info
|
||||
run: |
|
||||
@@ -151,6 +177,8 @@ jobs:
|
||||
name: Test
|
||||
run: |
|
||||
& docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" `
|
||||
-v "${{ github.workspace }}\go-build:C:\Users\ContainerAdministrator\AppData\Local\go-build" `
|
||||
-v "${{ github.workspace }}\go\pkg\mod:C:\gopath\pkg\mod" `
|
||||
-v "${{ env.GOPATH }}\src\github.com\docker\docker\bundles:C:\gopath\src\github.com\docker\docker\bundles" `
|
||||
${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -TestUnit
|
||||
-
|
||||
@@ -166,7 +194,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
|
||||
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
|
||||
@@ -181,13 +209,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
|
||||
path: /tmp/artifacts
|
||||
@@ -208,13 +236,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Install gotestlist
|
||||
run:
|
||||
@@ -264,27 +292,18 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
-
|
||||
name: Set up OpenTelemetry Collector
|
||||
name: Set up Jaeger
|
||||
run: |
|
||||
# The collectors is set up on Linux through the setup-tracing action. If you update the collector here, don't forget to
|
||||
# Jaeger is set up on Linux through the setup-tracing action. If you update Jaeger here, don't forget to
|
||||
# update the version set in .github/actions/setup-tracing/action.yml.
|
||||
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
|
||||
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
|
||||
shell: pwsh
|
||||
-
|
||||
name: Env
|
||||
@@ -292,14 +311,14 @@ jobs:
|
||||
Get-ChildItem Env: | Out-String
|
||||
-
|
||||
name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-${{ inputs.storage }}-${{ inputs.os }}
|
||||
path: ${{ env.BIN_OUT }}
|
||||
-
|
||||
name: Init
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "bundles" -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType "directory" -Path "bundles"
|
||||
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") {
|
||||
@@ -345,14 +364,11 @@ 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") {
|
||||
$dockerEnviron += @("TEST_INTEGRATION_USE_GRAPHDRIVER=1")
|
||||
echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
If ("${{ inputs.storage }}" -eq "snapshotter") {
|
||||
# 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 TEST_INTEGRATION_USE_SNAPSHOTTER=1
|
||||
echo "TEST_INTEGRATION_USE_SNAPSHOTTER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
|
||||
}
|
||||
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!"
|
||||
@@ -410,6 +426,12 @@ jobs:
|
||||
& "${{ env.BIN_OUT }}\docker" images
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Test integration
|
||||
if: matrix.test == './...'
|
||||
@@ -417,6 +439,7 @@ jobs:
|
||||
.\hack\make.ps1 -TestIntegration
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
GO111MODULE: "off"
|
||||
TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker
|
||||
-
|
||||
name: Test integration-cli
|
||||
@@ -425,6 +448,7 @@ jobs:
|
||||
.\hack\make.ps1 -TestIntegrationCli
|
||||
env:
|
||||
DOCKER_HOST: npipe:////./pipe/docker_engine
|
||||
GO111MODULE: "off"
|
||||
TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker
|
||||
INTEGRATION_TESTRUN: ${{ matrix.test }}
|
||||
-
|
||||
@@ -462,14 +486,16 @@ jobs:
|
||||
ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} |
|
||||
Tee-Object -file ".\bundles\daemon.log"
|
||||
-
|
||||
name: Stop OpenTelemetry Collector
|
||||
name: Download Jaeger traces
|
||||
if: always()
|
||||
run: |
|
||||
(Stop-Service -DisplayName "OpenTelemetry Collector" -PassThru).WaitForStatus('Stopped', (New-TimeSpan -Seconds 30))
|
||||
Invoke-WebRequest `
|
||||
-Uri "http://127.0.0.1:16686/api/traces?service=integration-test-client" `
|
||||
-OutFile ".\bundles\jaeger-trace.json"
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
|
||||
@@ -496,13 +522,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-*
|
||||
|
||||
41
.github/workflows/arm64.yml
vendored
41
.github/workflows/arm64.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.5"
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
DESTDIR: ./build
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
@@ -37,7 +37,6 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
strategy:
|
||||
@@ -71,7 +70,6 @@ jobs:
|
||||
build-dev:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
steps:
|
||||
@@ -89,19 +87,18 @@ jobs:
|
||||
targets: dev
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=dev-arm64
|
||||
*.cache-to=type=gha,scope=dev-arm64
|
||||
*.cache-to=type=gha,scope=dev-arm64,mode=max
|
||||
*.output=type=cacheonly
|
||||
|
||||
test-unit:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-dev
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -112,9 +109,6 @@ jobs:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
@@ -146,7 +140,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-unit-arm64-graphdriver
|
||||
path: /tmp/reports/*
|
||||
@@ -156,19 +150,19 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always() && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only'))
|
||||
if: always()
|
||||
needs:
|
||||
- test-unit
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: test-reports-unit-arm64-*
|
||||
path: /tmp/reports
|
||||
@@ -185,13 +179,12 @@ jobs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-dev
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up runner
|
||||
uses: ./.github/actions/setup-runner
|
||||
@@ -205,9 +198,6 @@ jobs:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
@@ -226,14 +216,13 @@ 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
|
||||
@@ -250,7 +239,7 @@ jobs:
|
||||
-
|
||||
name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-integration-arm64-graphdriver
|
||||
path: /tmp/reports/*
|
||||
@@ -260,19 +249,19 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: ${{ github.event_name != 'pull_request' }}
|
||||
if: always() && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only'))
|
||||
if: always()
|
||||
needs:
|
||||
- test-integration
|
||||
steps:
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: Download reports
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/reports
|
||||
pattern: test-reports-integration-arm64-*
|
||||
|
||||
34
.github/workflows/bin-image.yml
vendored
34
.github/workflows/bin-image.yml
vendored
@@ -22,7 +22,6 @@ on:
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'docker-v*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
@@ -37,19 +36,18 @@ env:
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.matrix }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
@@ -60,21 +58,19 @@ jobs:
|
||||
### versioning strategy
|
||||
## push semver tag v23.0.0
|
||||
# moby/moby-bin:23.0.0
|
||||
# moby/moby-bin:23.0
|
||||
# moby/moby-bin:23
|
||||
# moby/moby-bin:latest
|
||||
## push semver prerelease tag v23.0.0-beta.1
|
||||
# moby/moby-bin:23.0.0-beta.1
|
||||
## push on master
|
||||
# moby/moby-bin:master
|
||||
## push on 28.x branch
|
||||
# moby/moby-bin:28.x
|
||||
## push on 23.0 branch
|
||||
# moby/moby-bin:23.0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}},match=docker-(.*)
|
||||
type=semver,pattern={{major}}.{{minor}},match=docker-(.*)
|
||||
type=semver,pattern={{major}},match=docker-(.*)
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Rename meta bake definition file
|
||||
# see https://github.com/docker/metadata-action/issues/381#issuecomment-1918607161
|
||||
@@ -83,7 +79,7 @@ jobs:
|
||||
mv "${bakeFile#cwd://}" "/tmp/bake-meta.json"
|
||||
-
|
||||
name: Upload meta bake definition
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bake-meta
|
||||
path: /tmp/bake-meta.json
|
||||
@@ -98,10 +94,10 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only')) }}
|
||||
needs:
|
||||
- validate-dco
|
||||
- prepare
|
||||
if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -109,7 +105,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
@@ -119,7 +115,7 @@ jobs:
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Download meta bake definition
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bake-meta
|
||||
path: /tmp
|
||||
@@ -164,7 +160,7 @@ jobs:
|
||||
-
|
||||
name: Upload digest
|
||||
if: github.event_name != 'pull_request' && github.repository == 'moby/moby'
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -174,19 +170,19 @@ jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 40 # guardrails timeout for the whole job
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && github.event_name != 'pull_request' && github.repository == 'moby/moby' }}
|
||||
needs:
|
||||
- build
|
||||
if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && github.event_name != 'pull_request' && github.repository == 'moby/moby'
|
||||
steps:
|
||||
-
|
||||
name: Download meta bake definition
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bake-meta
|
||||
path: /tmp
|
||||
-
|
||||
name: Download digests
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
|
||||
56
.github/workflows/buildkit.yml
vendored
56
.github/workflows/buildkit.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.5"
|
||||
DESTDIR: ./build
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
|
||||
@@ -35,7 +35,6 @@ jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
steps:
|
||||
@@ -53,7 +52,7 @@ jobs:
|
||||
targets: binary
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary
|
||||
path: ${{ env.DESTDIR }}
|
||||
@@ -63,7 +62,6 @@ jobs:
|
||||
test-linux:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-linux
|
||||
env:
|
||||
@@ -100,15 +98,15 @@ jobs:
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: moby
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
-
|
||||
name: BuildKit ref
|
||||
run: |
|
||||
@@ -116,7 +114,7 @@ jobs:
|
||||
working-directory: moby
|
||||
-
|
||||
name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.BUILDKIT_REPO }}
|
||||
ref: ${{ env.BUILDKIT_REF }}
|
||||
@@ -133,7 +131,7 @@ jobs:
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Download binary artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: binary
|
||||
path: ./buildkit/build/moby/
|
||||
@@ -168,7 +166,6 @@ jobs:
|
||||
build-windows:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
env:
|
||||
@@ -184,14 +181,13 @@ jobs:
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/docker
|
||||
|
||||
- name: Env
|
||||
run: |
|
||||
Get-ChildItem Env: | Out-String
|
||||
|
||||
- name: Moby - Init
|
||||
run: |
|
||||
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
|
||||
@@ -199,10 +195,21 @@ 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@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
cache-dependency-path: vendor.sum
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
~\go\pkg\mod
|
||||
${{ github.workspace }}\go-build
|
||||
${{ env.GOPATH }}\pkg\mod
|
||||
key: ${{ inputs.os }}-${{ github.job }}-${{ hashFiles('**/vendor.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.os }}-${{ github.job }}-
|
||||
|
||||
- name: Docker info
|
||||
run: |
|
||||
@@ -225,7 +232,7 @@ jobs:
|
||||
go install github.com/distribution/distribution/v3/cmd/registry@latest
|
||||
|
||||
- name: Checkout BuildKit
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: moby/buildkit
|
||||
ref: master
|
||||
@@ -248,7 +255,7 @@ jobs:
|
||||
cp ${{ env.GOPATH }}\bin\buildctl.exe ${{ env.BIN_OUT }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-windows
|
||||
path: ${{ env.BIN_OUT }}/*
|
||||
@@ -258,7 +265,6 @@ jobs:
|
||||
test-windows:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-windows
|
||||
env:
|
||||
@@ -302,36 +308,31 @@ jobs:
|
||||
disabledFeatures="${disabledFeatures},merge_diff"
|
||||
fi
|
||||
echo "BUILDKIT_TEST_DISABLE_FEATURES=${disabledFeatures}" >> $GITHUB_ENV
|
||||
|
||||
- name: Expose GitHub Runtime
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: moby
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
|
||||
cache-dependency-path: vendor.sum
|
||||
- name: BuildKit ref
|
||||
shell: bash
|
||||
run: |
|
||||
echo "$(./hack/buildkit-ref)" >> $GITHUB_ENV
|
||||
working-directory: moby
|
||||
|
||||
- name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.BUILDKIT_REPO }}
|
||||
ref: ${{ env.BUILDKIT_REF }}
|
||||
path: buildkit
|
||||
|
||||
- name: Download Moby artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-windows
|
||||
path: ${{ env.BIN_OUT }}
|
||||
@@ -357,7 +358,6 @@ jobs:
|
||||
testFlags="${testFlags} --run=TestIntegration/$testSliceOffset.*/worker=${{ matrix.worker }}"
|
||||
fi
|
||||
echo "TESTFLAGS=${testFlags}" >> $GITHUB_ENV
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -67,7 +67,6 @@ jobs:
|
||||
prepare-cross:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
outputs:
|
||||
@@ -75,7 +74,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -90,7 +89,6 @@ jobs:
|
||||
cross:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
- prepare-cross
|
||||
@@ -130,7 +128,6 @@ jobs:
|
||||
govulncheck:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
# Always run security checks, even with 'ci/validate-only' label
|
||||
permissions:
|
||||
# required to write sarif report
|
||||
security-events: write
|
||||
@@ -160,7 +157,6 @@ jobs:
|
||||
|
||||
build-dind:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
steps:
|
||||
|
||||
22
.github/workflows/codeql.yml
vendored
22
.github/workflows/codeql.yml
vendored
@@ -17,7 +17,6 @@ on:
|
||||
- '[0-9]+.x'
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'docker-v*'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ["master"]
|
||||
@@ -33,9 +32,6 @@ on:
|
||||
# * * * * *
|
||||
- cron: '0 9 * * 4'
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -47,14 +43,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
# and is trying to be smart by searching for modules in every directory,
|
||||
# including vendor directories. If no module is found, it's creating one
|
||||
# which is ... not what we want, so let's give it a "go.mod".
|
||||
# see: https://github.com/docker/cli/pull/4944#issuecomment-2002034698
|
||||
- name: Create go.mod
|
||||
run: |
|
||||
ln -s vendor.mod go.mod
|
||||
ln -s vendor.sum go.sum
|
||||
- name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
go-version: "1.24.5"
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
|
||||
18
.github/workflows/labeler.yml
vendored
18
.github/workflows/labeler.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: "Labeler"
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Labels
|
||||
uses: actions/labeler@v6
|
||||
with:
|
||||
sync-labels: false
|
||||
64
.github/workflows/test.yml
vendored
64
.github/workflows/test.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.5"
|
||||
GO_VERSION: "1.24.5"
|
||||
GIT_PAGER: "cat"
|
||||
PAGER: "cat"
|
||||
SETUP_BUILDX_VERSION: edge
|
||||
@@ -44,7 +44,6 @@ jobs:
|
||||
mode:
|
||||
- ""
|
||||
- systemd
|
||||
- firewalld
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
@@ -66,18 +65,10 @@ jobs:
|
||||
targets: dev
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=dev${{ matrix.mode }}
|
||||
*.cache-to=type=gha,scope=dev${{ matrix.mode }}
|
||||
${{ 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
|
||||
*.cache-to=type=gha,scope=dev${{ matrix.mode }},mode=max
|
||||
*.output=type=cacheonly
|
||||
|
||||
test:
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-dev
|
||||
- validate-dco
|
||||
@@ -93,7 +84,6 @@ jobs:
|
||||
storage: ${{ matrix.storage }}
|
||||
|
||||
test-unit:
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- build-dev
|
||||
- validate-dco
|
||||
@@ -110,7 +100,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: scripts
|
||||
@@ -129,12 +119,13 @@ jobs:
|
||||
- validate-prepare
|
||||
- build-dev
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
script: ${{ fromJson(needs.validate-prepare.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
@@ -145,51 +136,23 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.SETUP_BUILDX_VERSION }}
|
||||
driver: docker
|
||||
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Restore dev image
|
||||
uses: actions/cache/restore@v4
|
||||
name: Build dev image
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
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
|
||||
targets: dev
|
||||
set: |
|
||||
dev.cache-from=type=gha,scope=dev
|
||||
-
|
||||
name: Validate
|
||||
run: |
|
||||
make -o build validate-${{ matrix.script }}
|
||||
|
||||
validate-api-swagger:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
defaults:
|
||||
run:
|
||||
working-directory: api
|
||||
needs:
|
||||
- validate-dco
|
||||
name: validate (api-swagger)
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Build api module image
|
||||
run: |
|
||||
make build
|
||||
-
|
||||
name: Validate swagger
|
||||
run: |
|
||||
make validate-swagger
|
||||
make validate-swagger-gen
|
||||
|
||||
smoke-prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- validate-dco
|
||||
outputs:
|
||||
@@ -197,7 +160,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -212,7 +175,6 @@ jobs:
|
||||
smoke:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20 # guardrails timeout for the whole job
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- smoke-prepare
|
||||
strategy:
|
||||
|
||||
28
.github/workflows/validate-pr.yml
vendored
28
.github/workflows/validate-pr.yml
vendored
@@ -14,20 +14,15 @@ on:
|
||||
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||
|
||||
jobs:
|
||||
check-labels:
|
||||
check-area-label:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||
steps:
|
||||
- name: Missing `area/` label
|
||||
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||
run: |
|
||||
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
|
||||
exit 1
|
||||
- name: Missing `kind/` label
|
||||
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'kind/')
|
||||
run: |
|
||||
echo "::error::Every PR with an 'impact/*' label should also have a 'kind/*' label"
|
||||
exit 1
|
||||
- name: OK
|
||||
run: exit 0
|
||||
|
||||
@@ -81,24 +76,13 @@ jobs:
|
||||
- name: Check release branch
|
||||
id: title_branch
|
||||
run: |
|
||||
# If PR targets a different branch than master, the PR title should mention the target branch in square brackets, for example:
|
||||
# [27.1 backport] Some change that needs backporting to 27.1
|
||||
# [27.1] Change directly targeting the 27.1 branch
|
||||
# [docker-29.x] Change directly targeting the docker-29.x branch
|
||||
# [docker-29.x backport] Some change that needs backporting to docker-29.x
|
||||
|
||||
# get the intended major version prefix ("[27.1 backport]" -> "27.") from the PR title.
|
||||
target_branch=$(echo "$PR_TITLE" | sed -nE 's/^\[([^]]+)\].*/\1/p' | sed 's/ backport$//')
|
||||
[[ "$PR_TITLE" =~ ^\[([0-9]*\.)[^]]*\] ]] && branch="${BASH_REMATCH[1]}"
|
||||
|
||||
echo "target_branch: $target_branch"
|
||||
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
|
||||
# get major version prefix from the release branch ("27.x -> "27.")
|
||||
[[ "$GITHUB_BASE_REF" =~ ^([0-9]*\.) ]] && target_branch="${BASH_REMATCH[1]}" || target_branch="$GITHUB_BASE_REF"
|
||||
|
||||
# If the PR is opened against the master branch and the target branch is not specified, exit early.
|
||||
if [[ "$GITHUB_BASE_REF" == "master" && "$target_branch" == "" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$target_branch" != "$GITHUB_BASE_REF" ]]; then
|
||||
if [[ "$target_branch" != "$branch" ]] && ! [[ "$GITHUB_BASE_REF" == "master" && "$branch" == "" ]]; then
|
||||
echo "::error::PR is opened against the $GITHUB_BASE_REF branch, but its title suggests otherwise."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
46
.github/workflows/vm.yml
vendored
46
.github/workflows/vm.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: vm
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
- '[0-9]+.x'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
vm:
|
||||
needs:
|
||||
- validate-dco
|
||||
uses: ./.github/workflows/.vm.yml
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
template:
|
||||
# EL 8 is used for running the tests with cgroup v1.
|
||||
# Do not upgrade this to EL 9 until formally deprecating the cgroup v1 support.
|
||||
#
|
||||
# FIXME: use almalinux-8, then probably no need to keep oraclelinux-8 here.
|
||||
# On almalinux-8, port forwarding tests are failing:
|
||||
# https://github.com/moby/moby/pull/49819#issuecomment-2815676000
|
||||
- template:oraclelinux-8 # Oracle's kernel 5.15
|
||||
# - template:almalinux-8 # kernel 4.18
|
||||
with:
|
||||
template: ${{ matrix.template }}
|
||||
13
.github/workflows/windows-2022.yml
vendored
13
.github/workflows/windows-2022.yml
vendored
@@ -22,17 +22,20 @@ jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
test-prepare:
|
||||
uses: ./.github/workflows/.test-prepare.yml
|
||||
needs:
|
||||
- validate-dco
|
||||
|
||||
run:
|
||||
needs: validate-dco
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- test-prepare
|
||||
uses: ./.github/workflows/.windows.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage:
|
||||
- graphdriver
|
||||
- snapshotter
|
||||
storage: ${{ fromJson(needs.test-prepare.outputs.matrix) }}
|
||||
with:
|
||||
os: windows-2022
|
||||
storage: ${{ matrix.storage }}
|
||||
|
||||
13
.github/workflows/windows-2025.yml
vendored
13
.github/workflows/windows-2025.yml
vendored
@@ -26,17 +26,20 @@ jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
test-prepare:
|
||||
uses: ./.github/workflows/.test-prepare.yml
|
||||
needs:
|
||||
- validate-dco
|
||||
|
||||
run:
|
||||
needs: validate-dco
|
||||
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
|
||||
needs:
|
||||
- test-prepare
|
||||
uses: ./.github/workflows/.windows.yml
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage:
|
||||
- graphdriver
|
||||
- snapshotter
|
||||
storage: ${{ fromJson(needs.test-prepare.outputs.matrix) }}
|
||||
with:
|
||||
os: windows-2025
|
||||
storage: ${{ matrix.storage }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,8 +15,8 @@ thumbs.db
|
||||
|
||||
# build artifacts
|
||||
/bundles/
|
||||
/cmd/dockerd/winresources/winres.json
|
||||
/cmd/dockerd/*.syso
|
||||
/cli/winresources/dockerd/*.syso
|
||||
/cli/winresources/dockerd/winres.json
|
||||
|
||||
# ci artifacts
|
||||
*.exe
|
||||
|
||||
@@ -3,7 +3,8 @@ 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.5"
|
||||
go: "1.24.5"
|
||||
concurrency: 2
|
||||
# Only supported with go modules enabled (build flag -mod=vendor only valid when using modules)
|
||||
# modules-download-mode: vendor
|
||||
|
||||
@@ -68,8 +69,6 @@ linters:
|
||||
desc: Use github.com/moby/sys/userns instead.
|
||||
- pkg: "github.com/tonistiigi/fsutil"
|
||||
desc: The fsutil module does not have a stable API, so we should not have a direct dependency unless necessary.
|
||||
- pkg: "github.com/hashicorp/go-multierror"
|
||||
desc: "Use errors.Join instead"
|
||||
|
||||
dupword:
|
||||
ignore:
|
||||
@@ -104,11 +103,11 @@ linters:
|
||||
msg: Go 1.19 atomic types should be used instead.
|
||||
- pkg: ^regexp$
|
||||
pattern: ^regexp\.MustCompile
|
||||
msg: Use daemon/internal/lazyregexp.New instead.
|
||||
msg: Use internal/lazyregexp.New instead.
|
||||
- pkg: github.com/vishvananda/netlink$
|
||||
pattern: ^netlink\.(Handle\.)?(AddrList|BridgeVlanList|ChainList|ClassList|ConntrackTableList|ConntrackDeleteFilter$|ConntrackDeleteFilters|DevLinkGetDeviceList|DevLinkGetAllPortList|DevlinkGetDeviceParams|FilterList|FouList|GenlFamilyList|GTPPDPList|LinkByName|LinkByAlias|LinkList|LinkSubscribeWithOptions|NeighList$|NeighProxyList|NeighListExecute|NeighSubscribeWithOptions|LinkGetProtinfo|QdiscList|RdmaLinkList|RdmaLinkByName|RdmaLinkDel|RouteList|RouteListFilteredIter|RuleListFiltered$|RouteSubscribeWithOptions|RuleList$|RuleListFiltered|SocketGet|SocketDiagTCPInfo|SocketDiagTCP|SocketDiagUDPInfo|SocketDiagUDP|UnixSocketDiagInfo|UnixSocketDiag|VDPAGetDevConfigList|VDPAGetDevList|VDPAGetMGMTDevList|XfrmPolicyList|XfrmStateList)
|
||||
msg: Use internal nlwrap package for EINTR handling.
|
||||
- pkg: github.com/moby/moby/v2/internal/nlwrap$
|
||||
- pkg: github.com/docker/docker/internal/nlwrap$
|
||||
pattern: ^nlwrap.Handle.(BridgeVlanList|ChainList|ClassList|ConntrackDeleteFilter$|DevLinkGetDeviceList|DevLinkGetAllPortList|DevlinkGetDeviceParams|FilterList|FouList|GenlFamilyList|GTPPDPList|LinkByAlias|LinkSubscribeWithOptions|NeighList$|NeighProxyList|NeighListExecute|NeighSubscribeWithOptions|LinkGetProtinfo|QdiscList|RdmaLinkList|RdmaLinkByName|RdmaLinkDel|RouteListFilteredIter|RuleListFiltered$|RouteSubscribeWithOptions|RuleList$|RuleListFiltered|SocketGet|SocketDiagTCPInfo|SocketDiagTCP|SocketDiagUDPInfo|SocketDiagUDP|UnixSocketDiagInfo|UnixSocketDiag|VDPAGetDevConfigList|VDPAGetDevList|VDPAGetMGMTDevList)
|
||||
msg: Add a wrapper to nlwrap.Handle for EINTR handling and update the list in .golangci.yml.
|
||||
analyze-types: true
|
||||
@@ -174,7 +173,6 @@ linters:
|
||||
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
|
||||
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
|
||||
- G504 # G504: Blocklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386); (only affects go < 1.6.3)
|
||||
- G602 # G602: slice index out of range (TODO: too many false positives; see https://github.com/securego/gosec/issues/1406)
|
||||
|
||||
govet:
|
||||
enable-all: true
|
||||
@@ -194,8 +192,6 @@ linters:
|
||||
alias: c8dimages
|
||||
- pkg: github.com/opencontainers/image-spec/specs-go/v1
|
||||
alias: ocispec
|
||||
- pkg: github.com/moby/docker-image-spec/specs-go/v1
|
||||
alias: dockerspec
|
||||
- pkg: go.etcd.io/bbolt
|
||||
alias: bolt
|
||||
# Enforce that gotest.tools/v3/assert/cmp is always aliased as "is"
|
||||
@@ -208,20 +204,10 @@ linters:
|
||||
max-func-lines: 0
|
||||
|
||||
revive:
|
||||
# Only listed rules are applied
|
||||
# https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
- name: increment-decrement
|
||||
# FIXME make sure all packages have a description. Currently, there's many packages without.
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
- name: redefines-builtin-id
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserve-scope
|
||||
- name: use-any
|
||||
- name: use-errors-new
|
||||
- name: var-declaration
|
||||
|
||||
staticcheck:
|
||||
checks:
|
||||
@@ -269,6 +255,9 @@ linters:
|
||||
http-status-code: true
|
||||
|
||||
exclusions:
|
||||
paths:
|
||||
- volume/drivers/proxy.go # TODO: this is a generated file but with an invalid header, see https://github.com/moby/moby/pull/46274
|
||||
|
||||
rules:
|
||||
# We prefer to use an "linters.exclusions.rules" so that new "default" exclusions are not
|
||||
# automatically inherited. We can decide whether or not to follow upstream
|
||||
@@ -293,10 +282,11 @@ linters:
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
- text: "^G402: " # Look for bad TLS connection settings
|
||||
source: "cmpopts\\.Ignore"
|
||||
# Suppress golint complaining about generated types in api/types/
|
||||
- text: "type name will be used as (container|volume)\\.(Container|Volume).* by other packages, and that stutters; consider calling this"
|
||||
path: "api/types/(volume|container)/"
|
||||
linters:
|
||||
- gosec
|
||||
- revive
|
||||
|
||||
# FIXME: ignoring unused assigns to ctx for now; too many hits in libnetwork/xxx functions that setup traces
|
||||
- text: "assigned to ctx, but never used afterwards"
|
||||
@@ -313,6 +303,11 @@ linters:
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# FIXME(thaJeztah): ignoring these transitional utilities until BuildKit is vendored with https://github.com/moby/moby/pull/49743
|
||||
- text: "SA1019: idtools\\.(ToUserIdentityMapping|FromUserIdentityMapping|IdentityMapping) is deprecated"
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# Ignore "nested context in function literal (fatcontext)" as we intentionally set up tracing on a base-context for tests.
|
||||
# FIXME(thaJeztah): see if there's a more iodiomatic way to do this.
|
||||
- text: 'nested context in function literal'
|
||||
@@ -332,26 +327,13 @@ linters:
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "daemon/internal/lazyregexp"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "internal/testutils"
|
||||
path: "internal/lazyregexp"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "libnetwork/cmd/networkdb-test/dbclient"
|
||||
linters:
|
||||
- forbidigo
|
||||
- text: 'use of `regexp.MustCompile` forbidden'
|
||||
path: "registry/"
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
# These interfaces in the client module are identical by design to allow future expansion.
|
||||
- text: "^identical: interface '(ContainerExportResult|ContainerLogsResult|ImagePullResponse|ImagePushResponse|ImageImportResult|ImageLoadResult|ImageSaveResult|ServiceLogsResult|TaskLogsResult)'"
|
||||
linters:
|
||||
- iface
|
||||
|
||||
# Log a warning if an exclusion rule is unused.
|
||||
# Default: false
|
||||
|
||||
1
.mailmap
1
.mailmap
@@ -97,7 +97,6 @@ Artur Meyster <arthurfbi@yahoo.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com> <55906459+austinvazquez@users.noreply.github.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com> <macedonv@amazon.com>
|
||||
Austin Vazquez <austin.vazquez.dev@gmail.com> <austin.vazquez@docker.com>
|
||||
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
|
||||
Ben Bonnefoy <frenchben@docker.com>
|
||||
Ben Golub <ben.golub@dotcloud.com>
|
||||
|
||||
20
AUTHORS
20
AUTHORS
@@ -23,7 +23,6 @@ Abhishek Chanda <abhishek.becs@gmail.com>
|
||||
Abhishek Sharma <abhishek@asharma.me>
|
||||
Abin Shahab <ashahab@altiscale.com>
|
||||
Abirdcfly <fp544037857@gmail.com>
|
||||
Abubacarr Ceesay <abubacarr671@gmail.com>
|
||||
Ada Mancini <ada@docker.com>
|
||||
Adam Avilla <aavilla@yp.com>
|
||||
Adam Dobrawy <naczelnik@jawnosc.tk>
|
||||
@@ -50,7 +49,6 @@ Adrian Mouat <adrian.mouat@gmail.com>
|
||||
Adrian Oprea <adrian@codesi.nz>
|
||||
Adrien Folie <folie.adrien@gmail.com>
|
||||
Adrien Gallouët <adrien@gallouet.fr>
|
||||
Adrien Pompée <adrien.pompee@atmosphere.aero>
|
||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||
Aidan Feldman <aidan.feldman@gmail.com>
|
||||
@@ -83,7 +81,6 @@ Aleksandrs Fadins <aleks@s-ko.net>
|
||||
Alena Prokharchyk <alena@rancher.com>
|
||||
Alessandro Boch <aboch@tetrationanalytics.com>
|
||||
Alessio Biancalana <dottorblaster@gmail.com>
|
||||
Alessio Perugini <alessio@perugini.xyz>
|
||||
Alex Chan <alex@alexwlchan.net>
|
||||
Alex Chen <alexchenunix@gmail.com>
|
||||
Alex Coventry <alx@empirical.com>
|
||||
@@ -174,7 +171,6 @@ Andrew Po <absourd.noise@gmail.com>
|
||||
Andrew Weiss <andrew.weiss@docker.com>
|
||||
Andrew Williams <williams.andrew@gmail.com>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andrey Epifanov <aepifanov@mirantis.com>
|
||||
Andrey Kolomentsev <andrey.kolomentsev@docker.com>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||
@@ -204,7 +200,6 @@ Anthon van der Neut <anthon@mnt.org>
|
||||
Anthony Baire <Anthony.Baire@irisa.fr>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Anthony Dahanne <anthony.dahanne@gmail.com>
|
||||
Anthony Nandaa <profnandaa@gmail.com>
|
||||
Anthony Sottile <asottile@umich.edu>
|
||||
Anton Löfgren <anton.lofgren@gmail.com>
|
||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||
@@ -350,7 +345,6 @@ Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Carlos Sanchez <carlos@apache.org>
|
||||
Carol Fager-Higgins <carol.fager-higgins@docker.com>
|
||||
carsontham <carsontham@outlook.com>
|
||||
Cary <caryhartline@users.noreply.github.com>
|
||||
Casey Bisson <casey.bisson@joyent.com>
|
||||
Catalin Pirvu <pirvu.catalin94@gmail.com>
|
||||
@@ -877,7 +871,6 @@ haining.cao <haining.cao@daocloud.io>
|
||||
Hakan Özler <hakan.ozler@kodcu.com>
|
||||
Hamish Hutchings <moredhel@aoeu.me>
|
||||
Hannes Ljungberg <hannes@5monkeys.se>
|
||||
Hannes Ortmeier <ortmeier.hannes@gmail.com>
|
||||
Hans Kristian Flaatten <hans@starefossen.com>
|
||||
Hans Rødtang <hansrodtang@gmail.com>
|
||||
Hao Shu Wei <haoshuwei24@gmail.com>
|
||||
@@ -897,7 +890,6 @@ heartlock <21521209@zju.edu.cn>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Helen Xie <chenjg@harmonycloud.cn>
|
||||
Henning Sprang <henning.sprang@gmail.com>
|
||||
Henry Wang <henwang@amazon.com>
|
||||
Hiroshi Hatake <hatake@clear-code.com>
|
||||
Hiroyuki Sasagawa <hs19870702@gmail.com>
|
||||
Hobofan <goisser94@gmail.com>
|
||||
@@ -1097,7 +1089,6 @@ Jintao Zhang <zhangjintao9020@gmail.com>
|
||||
Jiri Appl <jiria@microsoft.com>
|
||||
Jiri Popelka <jpopelka@redhat.com>
|
||||
Jiuyue Ma <majiuyue@huawei.com>
|
||||
Jiří Moravčík <jiri.moravcik@gmail.com>
|
||||
Jiří Župka <jzupka@redhat.com>
|
||||
jjimbo137 <115816493+jjimbo137@users.noreply.github.com>
|
||||
Joakim Roubert <joakim.roubert@axis.com>
|
||||
@@ -1329,7 +1320,6 @@ Leandro Motta Barros <lmb@stackedboxes.org>
|
||||
Leandro Siqueira <leandro.siqueira@gmail.com>
|
||||
Lee Calcote <leecalcote@gmail.com>
|
||||
Lee Chao <932819864@qq.com>
|
||||
Lee Gaines <leetgaines@gmail.com>
|
||||
Lee, Meng-Han <sunrisedm4@gmail.com>
|
||||
Lei Gong <lgong@alauda.io>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
@@ -1417,7 +1407,6 @@ Manuel Meurer <manuel@krautcomputing.com>
|
||||
Manuel Rüger <manuel@rueg.eu>
|
||||
Manuel Woelker <github@manuel.woelker.org>
|
||||
mapk0y <mapk0y@gmail.com>
|
||||
Marat Abrarov <abrarov@gmail.com>
|
||||
Marat Radchenko <marat@slonopotamus.org>
|
||||
Marc Abramowitz <marc@marc-abramowitz.com>
|
||||
Marc Kuo <kuomarc2@gmail.com>
|
||||
@@ -1432,7 +1421,6 @@ Marcus Linke <marcus.linke@gmx.de>
|
||||
Marcus Martins <marcus@docker.com>
|
||||
Marcus Ramberg <marcus@nordaaker.com>
|
||||
Marek Goldmann <marek.goldmann@gmail.com>
|
||||
Maria Glushenok <glushenokm@gmail.com>
|
||||
Marian Marinov <mm@yuhu.biz>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Mario Loriedo <mario.loriedo@gmail.com>
|
||||
@@ -1516,7 +1504,6 @@ Maxime Petazzoni <max@signalfuse.com>
|
||||
Maximiliano Maccanti <maccanti@amazon.com>
|
||||
Maxwell <csuhp007@gmail.com>
|
||||
Meaglith Ma <genedna@gmail.com>
|
||||
Medhy DOHOU <52136144+PowerPixel@users.noreply.github.com>
|
||||
meejah <meejah@meejah.ca>
|
||||
Megan Kostick <mkostick@us.ibm.com>
|
||||
Mehul Kar <mehul.kar@gmail.com>
|
||||
@@ -1617,7 +1604,6 @@ Moysés Borges <moysesb@gmail.com>
|
||||
mrfly <mr.wrfly@gmail.com>
|
||||
Mrunal Patel <mrunalp@gmail.com>
|
||||
Muayyad Alsadi <alsadi@gmail.com>
|
||||
Muhammad Daffa Dinaya <muhammaddaffadinaya@gmail.com>
|
||||
Muhammad Zohaib Aslam <zohaibse011@gmail.com>
|
||||
Mustafa Akın <mustafa91@gmail.com>
|
||||
Muthukumar R <muthur@gmail.com>
|
||||
@@ -2037,14 +2023,12 @@ Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
|
||||
Sergey Evstifeev <sergey.evstifeev@gmail.com>
|
||||
Sergii Kabashniuk <skabashnyuk@codenvy.com>
|
||||
Sergio Lopez <slp@redhat.com>
|
||||
Serhan Tutar <randomnoise@users.noreply.github.com>
|
||||
Serhat Gülçiçek <serhat25@gmail.com>
|
||||
Serhii Nakon <serhii.n@thescimus.com>
|
||||
SeungUkLee <lsy931106@gmail.com>
|
||||
Sevki Hasirci <s@sevki.org>
|
||||
Shane Canon <scanon@lbl.gov>
|
||||
Shane da Silva <shane@dasilva.io>
|
||||
Shang Mu <smu@princeton.edu>
|
||||
Shaun Kaasten <shaunk@gmail.com>
|
||||
Shaun Thompson <shaun.thompson@docker.com>
|
||||
shaunol <shaunol@gmail.com>
|
||||
@@ -2136,7 +2120,6 @@ Stéphane Este-Gracias <sestegra@gmail.com>
|
||||
Stig Larsson <stig@larsson.dev>
|
||||
Su Wang <su.wang@docker.com>
|
||||
Subhajit Ghosh <isubuz.g@gmail.com>
|
||||
Sudheendra Gopinath <sudheendra.gopinath@amd.com>
|
||||
Sujith Haridasan <sujith.h@gmail.com>
|
||||
Sun Gengze <690388648@qq.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||
@@ -2193,7 +2176,6 @@ Thomas Tanaka <thomas.tanaka@oracle.com>
|
||||
Thomas Texier <sharkone@en-mousse.org>
|
||||
Ti Zhou <tizhou1986@gmail.com>
|
||||
Tiago Seabra <tlgs@users.noreply.github.com>
|
||||
Tiago Teixeira <tiago.teixeira@ecorobotix.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tianyi Wang <capkurmagati@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
@@ -2294,7 +2276,6 @@ Valentin Kulesh <valentin.kulesh@virtuozzo.com>
|
||||
vanderliang <lansheng@meili-inc.com>
|
||||
Velko Ivanov <vivanov@deeperplane.com>
|
||||
Veres Lajos <vlajos@gmail.com>
|
||||
Viacheslav Gagara <viacheslavg@gmail.com>
|
||||
Victor Algaze <valgaze@gmail.com>
|
||||
Victor Coisne <victor.coisne@dotcloud.com>
|
||||
Victor Costan <costan@gmail.com>
|
||||
@@ -2511,6 +2492,5 @@ Zunayed Ali <zunayed@gmail.com>
|
||||
徐俊杰 <paco.xu@daocloud.io>
|
||||
慕陶 <jihui.xjh@alibaba-inc.com>
|
||||
搏通 <yufeng.pyf@alibaba-inc.com>
|
||||
纯真 <38834411+chunzhennn@users.noreply.github.com>
|
||||
黄艳红00139573 <huang.yanhong@zte.com.cn>
|
||||
정재영 <jjy600901@gmail.com>
|
||||
|
||||
92
Dockerfile
92
Dockerfile
@@ -1,30 +1,27 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.25.5
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
||||
# XX_VERSION specifies the version of the xx utility to use.
|
||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||
ARG XX_VERSION=1.7.0
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
# VPNKIT_VERSION is the version of the vpnkit binary which is used as a fallback
|
||||
# network driver for rootless.
|
||||
ARG VPNKIT_VERSION=0.6.0
|
||||
|
||||
# DOCKERCLI_VERSION is the version of the CLI to install in the dev-container.
|
||||
ARG DOCKERCLI_VERSION=v29.1.2
|
||||
ARG DOCKERCLI_VERSION=v28.2.2
|
||||
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
|
||||
|
||||
# cli version used for integration-cli tests
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v25.0.5
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v18.06.3-ce
|
||||
|
||||
# BUILDX_VERSION is the version of buildx to install in the dev container.
|
||||
ARG BUILDX_VERSION=0.30.1
|
||||
ARG BUILDX_VERSION=0.24.0
|
||||
|
||||
# COMPOSE_VERSION is the version of compose to install in the dev container.
|
||||
ARG COMPOSE_VERSION=v5.0.0
|
||||
ARG COMPOSE_VERSION=v2.36.2
|
||||
|
||||
ARG SYSTEMD="false"
|
||||
ARG FIREWALLD="false"
|
||||
@@ -37,8 +34,8 @@ ARG DOCKER_STATIC=1
|
||||
ARG REGISTRY_VERSION=3.0.0
|
||||
|
||||
# delve is currently only supported on linux/amd64 and linux/arm64;
|
||||
# https://github.com/go-delve/delve/blob/v1.25.0/pkg/proc/native/support_sentinel.go#L1
|
||||
# https://github.com/go-delve/delve/blob/v1.25.0/pkg/proc/native/support_sentinel_linux.go#L1
|
||||
# https://github.com/go-delve/delve/blob/v1.24.1/pkg/proc/native/support_sentinel.go#L1
|
||||
# https://github.com/go-delve/delve/blob/v1.24.1/pkg/proc/native/support_sentinel_linux.go#L1
|
||||
#
|
||||
# ppc64le support was added in v1.21.1, but is still experimental, and requires
|
||||
# the "-tags exp.linuxppc64le" build-tag to be set:
|
||||
@@ -67,6 +64,7 @@ COPY --from=xx / /
|
||||
RUN go telemetry off && [ "$(go telemetry)" = "off" ] || { echo "Failed to disable Go telemetry"; exit 1; }
|
||||
RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y file
|
||||
ENV GO111MODULE=off
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
FROM base AS criu
|
||||
@@ -83,6 +81,29 @@ RUN --mount=type=cache,sharing=locked,id=moby-criu-aptlib,target=/var/lib/apt \
|
||||
FROM distribution/distribution:$REGISTRY_VERSION AS registry
|
||||
RUN mkdir /build && mv /bin/registry /build/registry
|
||||
|
||||
# go-swagger
|
||||
FROM base AS swagger-src
|
||||
WORKDIR /usr/src/swagger
|
||||
# Currently uses a fork from https://github.com/kolyshkin/go-swagger/tree/golang-1.13-fix
|
||||
# TODO: move to under moby/ or fix upstream go-swagger to work for us.
|
||||
RUN git init . && git remote add origin "https://github.com/kolyshkin/go-swagger.git"
|
||||
# GO_SWAGGER_COMMIT specifies the version of the go-swagger binary to build and
|
||||
# install. Go-swagger is used in CI for validating swagger.yaml in hack/validate/swagger-gen
|
||||
ARG GO_SWAGGER_COMMIT=c56166c036004ba7a3a321e5951ba472b9ae298c
|
||||
RUN git fetch -q --depth 1 origin "${GO_SWAGGER_COMMIT}" && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS swagger
|
||||
WORKDIR /go/src/github.com/go-swagger/go-swagger
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=from=swagger-src,src=/usr/src/swagger,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=swagger-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ <<EOT
|
||||
set -e
|
||||
xx-go build -o /build/swagger ./cmd/swagger
|
||||
xx-verify /build/swagger
|
||||
EOT
|
||||
|
||||
# frozen-images
|
||||
# See also frozenImages in "testutil/environment/protect.go" (which needs to
|
||||
# be updated when adding images to this list)
|
||||
@@ -100,7 +121,7 @@ ARG TARGETVARIANT
|
||||
RUN /download-frozen-image-v2.sh /build \
|
||||
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
|
||||
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
|
||||
debian:trixie-slim@sha256:c85a2732e97694ea77237c61304b3bb410e0e961dd6ee945997a06c788c545bb \
|
||||
debian:bookworm-slim@sha256:2bc5c236e9b262645a323e9088dfa3bb1ecb16cc75811daf40a23a824d665be9 \
|
||||
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 \
|
||||
arm32v7/hello-world:latest@sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1 \
|
||||
hello-world:amd64@sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 \
|
||||
@@ -114,7 +135,7 @@ RUN git init . && git remote add origin "https://github.com/go-delve/delve.git"
|
||||
# from the https://github.com/go-delve/delve repository.
|
||||
# It can be used to run Docker with a possibility of
|
||||
# attaching debugger to it.
|
||||
ARG DELVE_VERSION=v1.25.2
|
||||
ARG DELVE_VERSION=v1.24.1
|
||||
RUN git fetch -q --depth 1 origin "${DELVE_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS delve-supported
|
||||
@@ -124,7 +145,7 @@ RUN --mount=from=delve-src,src=/usr/src/delve,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=delve-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
set -e
|
||||
xx-go build -o /build/dlv ./cmd/dlv
|
||||
GO111MODULE=on xx-go build -o /build/dlv ./cmd/dlv
|
||||
xx-verify /build/dlv
|
||||
EOT
|
||||
|
||||
@@ -136,7 +157,7 @@ FROM base AS gowinres
|
||||
ARG GOWINRES_VERSION=v0.3.1
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build CGO_ENABLED=0 go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
|
||||
&& /build/go-winres --help
|
||||
|
||||
# containerd
|
||||
@@ -146,8 +167,11 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
|
||||
# CONTAINERD_VERSION is used to build containerd binaries, and used for the
|
||||
# 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.2.1
|
||||
# specified here. The containerd golang package is also pinned in vendor.mod.
|
||||
# When updating the binary version you may also need to update the vendor
|
||||
# version to pick up bug fixes or new APIs, however, usually the Go packages
|
||||
# are built from a commit from the master branch.
|
||||
ARG CONTAINERD_VERSION=v1.7.27
|
||||
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS containerd-build
|
||||
@@ -178,31 +202,30 @@ FROM binary-dummy AS containerd-windows
|
||||
FROM containerd-${TARGETOS} AS containerd
|
||||
|
||||
FROM base AS golangci_lint
|
||||
ARG GOLANGCI_LINT_VERSION=v2.7.2
|
||||
ARG GOLANGCI_LINT_VERSION=v2.1.5
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build CGO_ENABLED=0 go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" \
|
||||
&& /build/golangci-lint --version
|
||||
|
||||
FROM base AS gotestsum
|
||||
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
|
||||
ARG GOTESTSUM_VERSION=v1.13.0
|
||||
ARG GOTESTSUM_VERSION=v1.12.0
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build CGO_ENABLED=0 go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
|
||||
&& /build/gotestsum --version
|
||||
|
||||
FROM base AS shfmt
|
||||
ARG SHFMT_VERSION=v3.8.0
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build CGO_ENABLED=0 go install "mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION}" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION}" \
|
||||
&& /build/shfmt --version
|
||||
|
||||
FROM base AS gopls
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build CGO_ENABLED=0 go install "golang.org/x/tools/gopls@latest" \
|
||||
GOBIN=/build/ GO111MODULE=on go install "golang.org/x/tools/gopls@latest" \
|
||||
&& /build/gopls version
|
||||
|
||||
FROM base AS dockercli
|
||||
@@ -234,11 +257,11 @@ RUN --mount=source=hack/dockerfile/cli.sh,target=/download-or-build-cli.sh \
|
||||
FROM base AS runc-src
|
||||
WORKDIR /usr/src/runc
|
||||
RUN git init . && git remote add origin "https://github.com/opencontainers/runc.git"
|
||||
# RUNC_VERSION sets the version of runc to install in the dev-container.
|
||||
# This version should usually match the version that is used by the containerd version
|
||||
# RUNC_VERSION should 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.4
|
||||
# project first, and update both after that is merged. When updating RUNC_VERSION,
|
||||
# consider updating runc in vendor.mod accordingly.
|
||||
ARG RUNC_VERSION=v1.2.6
|
||||
RUN git fetch -q --depth 1 origin "${RUNC_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS runc-build
|
||||
@@ -305,7 +328,8 @@ FROM tini-${TARGETOS} AS tini
|
||||
FROM base AS rootlesskit-src
|
||||
WORKDIR /usr/src/rootlesskit
|
||||
RUN git init . && git remote add origin "https://github.com/rootless-containers/rootlesskit.git"
|
||||
ARG ROOTLESSKIT_VERSION=v2.3.6
|
||||
# When updating, also update vendor.mod and hack/dockerfile/install/rootlesskit.installer accordingly.
|
||||
ARG ROOTLESSKIT_VERSION=v2.3.4
|
||||
RUN git fetch -q --depth 1 origin "${ROOTLESSKIT_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS rootlesskit-build
|
||||
@@ -317,6 +341,7 @@ RUN --mount=type=cache,sharing=locked,id=moby-rootlesskit-aptlib,target=/var/lib
|
||||
gcc \
|
||||
libc6-dev \
|
||||
pkg-config
|
||||
ENV GO111MODULE=on
|
||||
ARG DOCKER_STATIC
|
||||
RUN --mount=from=rootlesskit-src,src=/usr/src/rootlesskit,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
@@ -404,6 +429,7 @@ FROM docker/compose-bin:${COMPOSE_VERSION} AS compose
|
||||
|
||||
FROM base AS dev-systemd-false
|
||||
COPY --link --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --link --from=swagger /build/ /usr/local/bin/
|
||||
COPY --link --from=delve /build/ /usr/local/bin/
|
||||
COPY --link --from=gowinres /build/ /usr/local/bin/
|
||||
COPY --link --from=tini /build/ /usr/local/bin/
|
||||
@@ -476,7 +502,6 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
apparmor \
|
||||
bash-completion \
|
||||
bzip2 \
|
||||
fuse-overlayfs \
|
||||
inetutils-ping \
|
||||
iproute2 \
|
||||
iptables \
|
||||
@@ -484,7 +509,6 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
jq \
|
||||
libcap2-bin \
|
||||
libnet1 \
|
||||
libnftables-dev \
|
||||
libnl-3-200 \
|
||||
libprotobuf-c1 \
|
||||
libyajl2 \
|
||||
@@ -518,21 +542,20 @@ COPY --link --from=dockercli-integration /build/ /usr/local/cli-integration
|
||||
FROM base AS build
|
||||
COPY --from=gowinres /build/ /usr/local/bin/
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV GO111MODULE=off
|
||||
ENV CGO_ENABLED=1
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-build-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-build-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install --no-install-recommends -y \
|
||||
clang \
|
||||
lld \
|
||||
llvm \
|
||||
icoutils
|
||||
llvm
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-build-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-build-aptcache,target=/var/cache/apt \
|
||||
xx-apt-get install --no-install-recommends -y \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
libnftables-dev \
|
||||
libseccomp-dev \
|
||||
libsystemd-dev \
|
||||
pkg-config
|
||||
@@ -556,6 +579,7 @@ RUN <<EOT
|
||||
fi
|
||||
EOT
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=tmpfs,target=cli/winresources/dockerd \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=moby-build-$TARGETPLATFORM <<EOT
|
||||
set -e
|
||||
target=$([ "$DOCKER_STATIC" = "1" ] && echo "binary" || echo "dynbinary")
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
|
||||
# This represents the bare minimum required to build and test Docker.
|
||||
|
||||
ARG GO_VERSION=1.25.5
|
||||
ARG GO_VERSION=1.24.5
|
||||
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
||||
FROM ${GOLANG_IMAGE}
|
||||
ENV GO111MODULE=off
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# Compile and runtime deps
|
||||
# https://github.com/moby/moby/blob/master/project/PACKAGERS.md#build-dependencies
|
||||
# https://github.com/moby/moby/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#build-dependencies
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
@@ -34,26 +35,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
vim-common \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install containerd.io (includes runc), tini, and docker-ce-cli.
|
||||
# The versions of these dependencies differ from the main Dockerfile,
|
||||
# but it should be sufficient for minimal build and test purposes.
|
||||
ADD --chmod=0644 --checksum=sha256:1500c1f56fa9e26b9b8f42452a553675796ade0807cdce11975eb98170b3a570 \
|
||||
https://download.docker.com/linux/debian/gpg /etc/apt/keyrings/docker.asc
|
||||
ARG BASE_DEBIAN_DISTRO
|
||||
ADD <<-EOT /etc/apt/sources.list.d/docker.sources
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/debian
|
||||
Suites: ${BASE_DEBIAN_DISTRO}
|
||||
Components: stable
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOT
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
containerd.io \
|
||||
tini \
|
||||
docker-ce-cli \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -s /usr/bin/tini-static /usr/local/bin/docker-init
|
||||
|
||||
# Install runc, containerd, and tini
|
||||
# Please edit hack/dockerfile/install/<name>.installer to update them.
|
||||
COPY hack/dockerfile/install hack/dockerfile/install
|
||||
RUN set -e; for i in runc containerd tini dockercli; \
|
||||
do hack/dockerfile/install/install.sh $i; \
|
||||
done
|
||||
ENV PATH=/usr/local/cli:$PATH
|
||||
|
||||
ENV AUTO_GOPATH 1
|
||||
|
||||
@@ -161,26 +161,22 @@ 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.5
|
||||
|
||||
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
|
||||
ARG GOTESTSUM_VERSION=v1.13.0
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG GOTESTSUM_VERSION=v1.12.0
|
||||
|
||||
# GOWINRES_VERSION is the version of go-winres to install.
|
||||
ARG GOWINRES_VERSION=v0.3.3
|
||||
|
||||
# TODO: Update containerd version to match Linux version once
|
||||
# https://github.com/microsoft/hcsshim/issues/2488 is resolved.
|
||||
ARG CONTAINERD_VERSION=v2.0.7
|
||||
ARG CONTAINERD_VERSION=v1.7.27
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
# - CONTAINERD_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
# - CONTAINERD_VERSION must be consistent with 'hack/dockerfile/install/containerd.installer' used by Linux.
|
||||
# - FROM_DOCKERFILE is used for detection of building within a container.
|
||||
ENV GO_VERSION=${GO_VERSION} `
|
||||
CONTAINERD_VERSION=${CONTAINERD_VERSION} `
|
||||
GIT_VERSION=2.11.1 `
|
||||
GOPATH=C:\gopath `
|
||||
GO111MODULE=off `
|
||||
GOTOOLCHAIN=local `
|
||||
FROM_DOCKERFILE=1 `
|
||||
GOTESTSUM_VERSION=${GOTESTSUM_VERSION} `
|
||||
@@ -261,11 +257,14 @@ RUN `
|
||||
Remove-Item C:\gitsetup.zip; `
|
||||
`
|
||||
Write-Host INFO: Downloading containerd; `
|
||||
Install-Package -Force 7Zip4PowerShell; `
|
||||
$location='https://github.com/containerd/containerd/releases/download/'+$Env:CONTAINERD_VERSION+'/containerd-'+$Env:CONTAINERD_VERSION.TrimStart('v')+'-windows-amd64.tar.gz'; `
|
||||
Download-File $location C:\containerd.tar.gz; `
|
||||
New-Item -Path C:\containerd -ItemType Directory; `
|
||||
tar -xzf C:\containerd.tar.gz -C C:\containerd; `
|
||||
Expand-7Zip C:\containerd.tar.gz C:\; `
|
||||
Expand-7Zip C:\containerd.tar C:\containerd; `
|
||||
Remove-Item C:\containerd.tar.gz; `
|
||||
Remove-Item C:\containerd.tar; `
|
||||
`
|
||||
# Ensure all directories exist that we will require below....
|
||||
$srcDir = """$Env:GOPATH`\src\github.com\docker\docker\bundles"""; `
|
||||
@@ -277,11 +276,13 @@ RUN `
|
||||
|
||||
RUN `
|
||||
Function Install-GoTestSum() { `
|
||||
$Env:GO111MODULE = 'on'; `
|
||||
$tmpGobin = "${Env:GOBIN_TMP}"; `
|
||||
$Env:GOBIN = """${Env:GOPATH}`\bin"""; `
|
||||
Write-Host "INFO: Installing gotestsum version $Env:GOTESTSUM_VERSION in $Env:GOBIN"; `
|
||||
&go install "gotest.tools/gotestsum@${Env:GOTESTSUM_VERSION}"; `
|
||||
$Env:GOBIN = "${tmpGobin}"; `
|
||||
$Env:GO111MODULE = 'off'; `
|
||||
if ($LASTEXITCODE -ne 0) { `
|
||||
Throw '"gotestsum install failed..."'; `
|
||||
} `
|
||||
@@ -291,11 +292,13 @@ RUN `
|
||||
|
||||
RUN `
|
||||
Function Install-GoWinres() { `
|
||||
$Env:GO111MODULE = 'on'; `
|
||||
$tmpGobin = "${Env:GOBIN_TMP}"; `
|
||||
$Env:GOBIN = """${Env:GOPATH}`\bin"""; `
|
||||
Write-Host "INFO: Installing go-winres version $Env:GOWINRES_VERSION in $Env:GOBIN"; `
|
||||
&go install "github.com/tc-hib/go-winres@${Env:GOWINRES_VERSION}"; `
|
||||
$Env:GOBIN = "${tmpGobin}"; `
|
||||
$Env:GO111MODULE = 'off'; `
|
||||
if ($LASTEXITCODE -ne 0) { `
|
||||
Throw '"go-winres install failed..."'; `
|
||||
} `
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# GitHub ID, Name, Email address, GPG fingerprint
|
||||
"akerouanton","Albin Kerouanton","albinker@gmail.com"
|
||||
"AkihiroSuda","Akihiro Suda","akihiro.suda.cz@hco.ntt.co.jp"
|
||||
"austinvazquez","Austin Vazquez","austin.vazquez.dev@gmail.com"
|
||||
"austinvazquez","Austin Vazquez","macedonv@amazon.com"
|
||||
"corhere","Cory Snider","csnider@mirantis.com"
|
||||
"cpuguy83","Brian Goff","cpuguy83@gmail.com"
|
||||
"robmry","Rob Murray","rob.murray@docker.com"
|
||||
|
||||
30
Makefile
30
Makefile
@@ -27,6 +27,9 @@ DOCKER_ENVS := \
|
||||
-e BUILDFLAGS \
|
||||
-e KEEPBUNDLE \
|
||||
-e DOCKER_BUILD_ARGS \
|
||||
-e DOCKER_BUILD_GOGC \
|
||||
-e DOCKER_BUILD_OPTS \
|
||||
-e DOCKER_BUILD_PKGS \
|
||||
-e DOCKER_BUILDKIT \
|
||||
-e DOCKER_CLI_PATH \
|
||||
-e DOCKERCLI_VERSION \
|
||||
@@ -35,10 +38,8 @@ DOCKER_ENVS := \
|
||||
-e DOCKERCLI_INTEGRATION_REPOSITORY \
|
||||
-e DOCKER_DEBUG \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_FIREWALL_BACKEND \
|
||||
-e DOCKER_GITCOMMIT \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_IGNORE_BR_NETFILTER_ERROR \
|
||||
-e DOCKER_LDFLAGS \
|
||||
-e DOCKER_PORT \
|
||||
-e DOCKER_REMAP_ROOT \
|
||||
@@ -52,7 +53,7 @@ DOCKER_ENVS := \
|
||||
-e GITHUB_ACTIONS \
|
||||
-e TEST_FORCE_VALIDATE \
|
||||
-e TEST_INTEGRATION_DIR \
|
||||
-e TEST_INTEGRATION_USE_GRAPHDRIVER \
|
||||
-e TEST_INTEGRATION_USE_SNAPSHOTTER \
|
||||
-e TEST_INTEGRATION_FAIL_FAST \
|
||||
-e TEST_SKIP_INTEGRATION \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
@@ -115,8 +116,11 @@ DELVE_PORT_FORWARD := $(if $(DELVE_PORT),-p "$(DELVE_PORT)",)
|
||||
|
||||
DOCKER_FLAGS := $(DOCKER) run --rm --privileged $(DOCKER_CONTAINER_NAME) $(DOCKER_ENVS) $(DOCKER_MOUNT) $(DOCKER_PORT_FORWARD) $(DELVE_PORT_FORWARD)
|
||||
|
||||
SWAGGER_DOCS_PORT ?= 9000
|
||||
|
||||
define \n
|
||||
|
||||
|
||||
endef
|
||||
|
||||
# if this session isn't interactive, then we don't want to allocate a
|
||||
@@ -149,7 +153,7 @@ ifdef FIREWALLD
|
||||
DOCKER_BUILD_ARGS += --build-arg=FIREWALLD=true
|
||||
endif
|
||||
|
||||
BUILD_OPTS := ${DOCKER_BUILD_ARGS}
|
||||
BUILD_OPTS := ${DOCKER_BUILD_ARGS} ${DOCKER_BUILD_OPTS}
|
||||
BUILD_CMD := $(BUILDX) build
|
||||
BAKE_CMD := $(BUILDX) bake
|
||||
|
||||
@@ -206,10 +210,6 @@ build: validate-bind-dir bundles
|
||||
shell: build ## start a shell inside the build env
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
.PHONY: dev
|
||||
dev: build ## start a dev mode inside the build env
|
||||
$(DOCKER_RUN_DOCKER) hack/dev.sh
|
||||
|
||||
.PHONY: test
|
||||
test: build test-unit ## run the unit, integration and docker-py tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration test-docker-py
|
||||
@@ -257,12 +257,20 @@ win: bundles ## cross build the binary for windows
|
||||
$(BAKE_CMD) --set *.platform=windows/amd64 binary
|
||||
|
||||
.PHONY: swagger-gen
|
||||
swagger-gen: ## generate swagger API types
|
||||
$(MAKE) -C api swagger-gen
|
||||
swagger-gen:
|
||||
docker run --rm -v $(PWD):/go/src/github.com/docker/docker \
|
||||
-w /go/src/github.com/docker/docker \
|
||||
--entrypoint hack/generate-swagger-api.sh \
|
||||
-e GOPATH=/go \
|
||||
quay.io/goswagger/swagger:0.7.4
|
||||
|
||||
.PHONY: swagger-docs
|
||||
swagger-docs: ## preview the API documentation
|
||||
$(MAKE) -C api swagger-docs
|
||||
@echo "API docs preview will be running at http://localhost:$(SWAGGER_DOCS_PORT)"
|
||||
@docker run --rm -v $(PWD)/api/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
|
||||
-e 'REDOC_OPTIONS=hide-hostname="true" lazy-rendering' \
|
||||
-p $(SWAGGER_DOCS_PORT):80 \
|
||||
bfirsh/redoc:1.14.0
|
||||
|
||||
.PHONY: generate-files
|
||||
generate-files:
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
The Moby Project
|
||||
================
|
||||
|
||||
[](https://pkg.go.dev/github.com/moby/moby/v2)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/moby/moby/v2)
|
||||
[](https://pkg.go.dev/github.com/docker/docker)
|
||||
[](https://goreportcard.com/report/github.com/docker/docker)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
|
||||
[](https://www.bestpractices.dev/projects/10989)
|
||||
|
||||
|
||||

|
||||
|
||||
@@ -113,5 +113,5 @@ We see gRPC as the natural communication layer between decoupled components.
|
||||
|
||||
In addition to pushing out large components into other projects, much of the
|
||||
internal code structure, and in particular the
|
||||
["Daemon"](https://pkg.go.dev/github.com/moby/moby/v2/daemon#Daemon) object,
|
||||
["Daemon"](https://godoc.org/github.com/docker/docker/daemon#Daemon) object,
|
||||
should be split into smaller, more manageable, and more testable components.
|
||||
|
||||
35
TESTING.md
35
TESTING.md
@@ -8,11 +8,11 @@ questions you may have as an aspiring Moby contributor.
|
||||
Moby has two test suites (and one legacy test suite):
|
||||
|
||||
* Unit tests - use standard `go test` and
|
||||
[gotest.tools/v3/assert](https://pkg.go.dev/gotest.tools/v3/assert) assertions. They are located in
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
the package they test. Unit tests should be fast and test only their own
|
||||
package.
|
||||
* API integration tests - use standard `go test` and
|
||||
[gotest.tools/v3/assert](https://pkg.go.dev/gotest.tools/v3/assert) assertions. They are located in
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
`./integration/<component>` directories, where `component` is: container,
|
||||
image, volume, etc. These tests perform HTTP requests to an API endpoint and
|
||||
check the HTTP response and daemon state after the call.
|
||||
@@ -57,28 +57,17 @@ Instead, implement new tests under `integration/`.
|
||||
### Integration tests environment considerations
|
||||
|
||||
When adding new tests or modifying existing tests under `integration/`, testing
|
||||
environment should be properly considered. [`skip.If`](https://pkg.go.dev/gotest.tools/v3/skip#If) from
|
||||
[gotest.tools/v3/skip](https://pkg.go.dev/gotest.tools/v3/skip) can be used to make the
|
||||
environment should be properly considered. `skip.If` from
|
||||
[gotest.tools/skip](https://godoc.org/gotest.tools/skip) can be used to make the
|
||||
test run conditionally. Full testing environment conditions can be found at
|
||||
[environment.go](https://github.com/moby/moby/blob/311b2c87e125c6d4198014369e313135cf928a8a/testutil/environment/environment.go)
|
||||
[environment.go](https://github.com/moby/moby/blob/6b6eeed03b963a27085ea670f40cd5ff8a61f32e/testutil/environment/environment.go)
|
||||
|
||||
Here is a quick example. If the test needs to interact with a docker daemon on
|
||||
the same host, the following condition should be checked within the test code
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func TestSomething(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRemoteDaemon(), "test requires a local daemon")
|
||||
|
||||
// your integration test code
|
||||
}
|
||||
skip.If(t, testEnv.IsRemoteDaemon())
|
||||
// your integration test code
|
||||
```
|
||||
|
||||
If a remote daemon is detected, the test will be skipped.
|
||||
@@ -89,11 +78,11 @@ If a remote daemon is detected, the test will be skipped.
|
||||
|
||||
To run the unit test suite:
|
||||
|
||||
```bash
|
||||
```
|
||||
make test-unit
|
||||
```
|
||||
|
||||
or `hack/test/unit` from inside a `make shell` container or properly
|
||||
or `hack/test/unit` from inside a `BINDDIR=. make shell` container or properly
|
||||
configured environment.
|
||||
|
||||
The following environment variables may be used to run a subset of tests:
|
||||
@@ -106,7 +95,7 @@ The following environment variables may be used to run a subset of tests:
|
||||
|
||||
To run the integration test suite:
|
||||
|
||||
```bash
|
||||
```
|
||||
make test-integration
|
||||
```
|
||||
|
||||
@@ -132,6 +121,6 @@ automatically set the other above mentioned environment variables accordingly.
|
||||
You can change a version of golang used for building stuff that is being tested
|
||||
by setting `GO_VERSION` variable, for example:
|
||||
|
||||
```bash
|
||||
make GO_VERSION=1.24.8 test
|
||||
```
|
||||
make GO_VERSION=1.12.8 test
|
||||
```
|
||||
|
||||
46
VENDORING.md
Normal file
46
VENDORING.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Vendoring policies
|
||||
|
||||
This document outlines recommended Vendoring policies for Docker repositories.
|
||||
(Example, libnetwork is a Docker repo and logrus is not.)
|
||||
|
||||
## Vendoring using tags
|
||||
|
||||
Commit ID based vendoring provides little/no information about the updates
|
||||
vendored. To fix this, vendors will now require that repositories use annotated
|
||||
tags along with commit ids to snapshot commits. Annotated tags by themselves
|
||||
are not sufficient, since the same tag can be force updated to reference
|
||||
different commits.
|
||||
|
||||
Each tag should:
|
||||
- Follow Semantic Versioning rules (refer to section on "Semantic Versioning")
|
||||
- Have a corresponding entry in the change tracking document.
|
||||
|
||||
Each repo should:
|
||||
- Have a change tracking document between tags/releases. Ex: CHANGELOG.md,
|
||||
github releases file.
|
||||
|
||||
The goal here is for consuming repos to be able to use the tag version and
|
||||
changelog updates to determine whether the vendoring will cause any breaking or
|
||||
backward incompatible changes. This also means that repos can specify having
|
||||
dependency on a package of a specific version or greater up to the next major
|
||||
release, without encountering breaking changes.
|
||||
|
||||
## Semantic Versioning
|
||||
Annotated version tags should follow [Semantic Versioning](http://semver.org) policies:
|
||||
|
||||
"Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
1. MAJOR version when you make incompatible API changes,
|
||||
2. MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
3. PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions
|
||||
to the MAJOR.MINOR.PATCH format."
|
||||
|
||||
## Vendoring cadence
|
||||
In order to avoid huge vendoring changes, it is recommended to have a regular
|
||||
cadence for vendoring updates. e.g. monthly.
|
||||
|
||||
## Pre-merge vendoring tests
|
||||
All related repos will be vendored into docker/docker.
|
||||
CI on docker/docker should catch any breaking changes involving multiple repos.
|
||||
@@ -1,25 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.25.5
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine AS base
|
||||
RUN apk add --no-cache bash make yamllint
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
# go-swagger
|
||||
FROM base AS swagger
|
||||
WORKDIR /go/src/github.com/go-swagger/go-swagger
|
||||
# GO_SWAGGER_VERSION specifies the version of the go-swagger binary to install.
|
||||
# Go-swagger is used in CI for generating types from swagger.yaml in
|
||||
# api/scripts/generate-swagger-api.sh
|
||||
ARG GO_SWAGGER_VERSION=v0.33.1
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build,id=swagger-build-$TARGETPLATFORM \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
CGO_ENABLED=0 go install "github.com/go-swagger/go-swagger/cmd/swagger@${GO_SWAGGER_VERSION}" && \
|
||||
/go/bin/swagger version
|
||||
|
||||
# dev is a dev-environment to work with the api module.
|
||||
FROM base AS dev
|
||||
COPY --from=swagger /go/bin/swagger /usr/local/bin/swagger
|
||||
WORKDIR /go/src/github.com/moby/moby/api
|
||||
64
api/Makefile
64
api/Makefile
@@ -1,64 +0,0 @@
|
||||
# API Module Makefile
|
||||
# This Makefile provides targets for the swagger generation and validation
|
||||
# which are specific to the API module.
|
||||
|
||||
DOCKER ?= docker
|
||||
BUILDX ?= $(DOCKER) buildx
|
||||
|
||||
API_DIR := $(CURDIR)
|
||||
PROJECT_PATH := /go/src/github.com/moby/moby
|
||||
|
||||
DOCKER_MOUNT := -v "$(API_DIR):$(PROJECT_PATH)/api"
|
||||
|
||||
DOCKER_IMAGE := docker-api-dev
|
||||
|
||||
DOCKER_WORKDIR := -w $(PROJECT_PATH)/api
|
||||
|
||||
DOCKER_FLAGS := $(DOCKER) run --rm $(DOCKER_MOUNT) $(DOCKER_WORKDIR)
|
||||
DOCKER_RUN := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
|
||||
DOCKER_CONTAINER_NAME := $(if $(CONTAINER_NAME),--name $(CONTAINER_NAME),)
|
||||
|
||||
DOCKER_BUILD_ARGS += --build-arg=GO_VERSION
|
||||
DOCKER_BUILD_ARGS += --build-arg=SWAGGER_VERSION
|
||||
|
||||
BUILD_CMD := $(BUILDX) build
|
||||
|
||||
SWAGGER_DOCS_PORT ?= 9000
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
$(BUILD_CMD) $(DOCKER_BUILD_ARGS) \
|
||||
--target dev \
|
||||
--load \
|
||||
-t "$(DOCKER_IMAGE)" \
|
||||
-f Dockerfile \
|
||||
.
|
||||
|
||||
.PHONY: swagger-gen
|
||||
swagger-gen: build ## generate swagger API types
|
||||
$(DOCKER_RUN) ./scripts/generate-swagger-api.sh
|
||||
|
||||
.PHONY: swagger-docs
|
||||
swagger-docs: ## preview the API documentation
|
||||
@echo "API docs preview will be running at http://localhost:$(SWAGGER_DOCS_PORT)"
|
||||
@docker run --rm \
|
||||
-v ./:/usr/share/nginx/html/swagger/ \
|
||||
-e 'REDOC_OPTIONS=hide-hostname="true" lazy-rendering' \
|
||||
-e SPEC_URL="swagger/swagger.yaml" \
|
||||
-p $(SWAGGER_DOCS_PORT):80 \
|
||||
redocly/redoc:v2.5.1
|
||||
|
||||
.PHONY: validate-swagger
|
||||
validate-swagger: build ## validate the swagger.yaml file
|
||||
$(DOCKER_RUN) ./scripts/validate-swagger.sh
|
||||
|
||||
.PHONY: validate-swagger-gen
|
||||
validate-swagger-gen: build ## validate generated types are up-to-date
|
||||
$(DOCKER_RUN) ./scripts/validate-swagger-gen.sh
|
||||
|
||||
.PHONY: help
|
||||
help: ## display this help message
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@@ -1,18 +1,12 @@
|
||||
# Engine API
|
||||
|
||||
[](https://pkg.go.dev/github.com/moby/moby/api)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/moby/moby/api)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
|
||||
[](https://www.bestpractices.dev/projects/10989)
|
||||
|
||||
# Working on the Engine API
|
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
|
||||
|
||||
It consists of various components in this repository:
|
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/moby/moby/issues/27919) for progress on this.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
|
||||
- `cli/` The command-line client.
|
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
|
||||
- `daemon/` The daemon, which serves the API.
|
||||
|
||||
@@ -27,7 +21,6 @@ The API is defined by the [Swagger](http://swagger.io/specification/) definition
|
||||
## Updating the API documentation
|
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation.
|
||||
Documentation for each API version can be found in the [docs directory](docs/README.md), which also provides a [CHANGELOG.md](docs/CHANGELOG.md).
|
||||
|
||||
The file is split into two main sections:
|
||||
|
||||
@@ -36,7 +29,7 @@ The file is split into two main sections:
|
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
|
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/moby/moby/issues/27919).
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919).
|
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing.
|
||||
|
||||
@@ -46,4 +39,4 @@ When you make edits to `swagger.yaml`, you may want to check the generated API d
|
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost:9000`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
|
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docs](https://github.com/docker/docs).
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).
|
||||
|
||||
20
api/common.go
Normal file
20
api/common.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package api
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of the current REST API.
|
||||
DefaultVersion = "1.51"
|
||||
|
||||
// MinSupportedAPIVersion is the minimum API version that can be supported
|
||||
// by the API server, specified as "major.minor". Note that the daemon
|
||||
// may be configured with a different minimum API version, as returned
|
||||
// in [github.com/docker/docker/api/types.Version.MinAPIVersion].
|
||||
//
|
||||
// API requests for API versions lower than the configured version produce
|
||||
// an error.
|
||||
MinSupportedAPIVersion = "1.24"
|
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
NoBaseImageSpecifier = "scratch"
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
package api
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
# API Documentation
|
||||
|
||||
This directory contains versioned documents for each version of the API
|
||||
specification supported by this module. While this module provides support
|
||||
for older API versions, support should be considered "best-effort", especially
|
||||
for very old versions. Users are recommended to use the latest API versions,
|
||||
and only rely on older API versions for compatibility with older clients.
|
||||
|
||||
Newer API versions tend to be backward-compatible with older versions,
|
||||
with some exceptions where features were deprecated. For an overview
|
||||
of changes for each version, refer to [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
The latest version of the API specification can be found [at the root directory
|
||||
of this module](../swagger.yaml) which may contain unreleased changes.
|
||||
|
||||
For API version v1.24, documentation is only available in markdown
|
||||
format, for later versions [Swagger (OpenAPI) v2.0](https://swagger.io/specification/v2/)
|
||||
specifications can be found in this directory. The Moby project itself
|
||||
primarily uses these swagger files to produce the API documentation;
|
||||
while we attempt to make these files match the actual implementation,
|
||||
the OpenAPI 2.0 specification has limitations that prevent us from
|
||||
expressing all options provided. There may be discrepancies (for which
|
||||
we welcome contributions). If you find bugs, or discrepancies, please
|
||||
open a ticket (or pull request).
|
||||
|
||||
|
||||
13586
api/docs/v1.52.yaml
13586
api/docs/v1.52.yaml
File diff suppressed because it is too large
Load Diff
14
api/go.mod
14
api/go.mod
@@ -1,14 +0,0 @@
|
||||
module github.com/moby/moby/api
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
pgregory.net/rapid v1.2.0
|
||||
)
|
||||
|
||||
require github.com/google/go-cmp v0.7.0 // indirect
|
||||
14
api/go.sum
14
api/go.sum
@@ -1,14 +0,0 @@
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
@@ -1,92 +0,0 @@
|
||||
package authconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
)
|
||||
|
||||
// Encode serializes the auth configuration as a base64url encoded
|
||||
// ([RFC4648, section 5]) JSON string for sending through the X-Registry-Auth header.
|
||||
//
|
||||
// [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5
|
||||
func Encode(authConfig registry.AuthConfig) (string, error) {
|
||||
// Older daemons (or registries) may not handle an empty string,
|
||||
// which resulted in an "io.EOF" when unmarshaling or decoding.
|
||||
//
|
||||
// FIXME(thaJeztah): find exactly what code-paths are impacted by this.
|
||||
// if authConfig == (AuthConfig{}) { return "", nil }
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", errInvalidParameter{err}
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// Decode decodes base64url encoded ([RFC4648, section 5]) JSON
|
||||
// authentication information as sent through the X-Registry-Auth header.
|
||||
//
|
||||
// This function always returns an [AuthConfig], even if an error occurs. It is up
|
||||
// to the caller to decide if authentication is required, and if the error can
|
||||
// be ignored.
|
||||
//
|
||||
// [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5
|
||||
func Decode(authEncoded string) (*registry.AuthConfig, error) {
|
||||
if authEncoded == "" {
|
||||
return ®istry.AuthConfig{}, nil
|
||||
}
|
||||
|
||||
decoded, err := base64.URLEncoding.DecodeString(authEncoded)
|
||||
if err != nil {
|
||||
var e base64.CorruptInputError
|
||||
if errors.As(err, &e) {
|
||||
return ®istry.AuthConfig{}, invalid(errors.New("must be a valid base64url-encoded string"))
|
||||
}
|
||||
return ®istry.AuthConfig{}, invalid(err)
|
||||
}
|
||||
|
||||
if bytes.Equal(decoded, []byte("{}")) {
|
||||
return ®istry.AuthConfig{}, nil
|
||||
}
|
||||
|
||||
return decode(bytes.NewReader(decoded))
|
||||
}
|
||||
|
||||
// DecodeRequestBody decodes authentication information as sent as JSON in the
|
||||
// body of a request. This function is to provide backward compatibility with old
|
||||
// clients and API versions. Current clients and API versions expect authentication
|
||||
// to be provided through the X-Registry-Auth header.
|
||||
//
|
||||
// Like [Decode], this function always returns an [AuthConfig], even if an
|
||||
// error occurs. It is up to the caller to decide if authentication is required,
|
||||
// and if the error can be ignored.
|
||||
func DecodeRequestBody(r io.ReadCloser) (*registry.AuthConfig, error) {
|
||||
return decode(r)
|
||||
}
|
||||
|
||||
func decode(r io.Reader) (*registry.AuthConfig, error) {
|
||||
authConfig := ®istry.AuthConfig{}
|
||||
if err := json.NewDecoder(r).Decode(authConfig); err != nil {
|
||||
// always return an (empty) AuthConfig to increase compatibility with
|
||||
// the existing API.
|
||||
return ®istry.AuthConfig{}, invalid(fmt.Errorf("invalid JSON: %w", err))
|
||||
}
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func invalid(err error) error {
|
||||
return errInvalidParameter{fmt.Errorf("invalid X-Registry-Auth header: %w", err)}
|
||||
}
|
||||
|
||||
type errInvalidParameter struct{ error }
|
||||
|
||||
func (errInvalidParameter) InvalidParameter() {}
|
||||
|
||||
func (e errInvalidParameter) Cause() error { return e.error }
|
||||
|
||||
func (e errInvalidParameter) Unwrap() error { return e.error }
|
||||
@@ -1,191 +0,0 @@
|
||||
package authconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestDecodeAuthConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input string
|
||||
inputBase64 string
|
||||
expected registry.AuthConfig
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "empty",
|
||||
input: ``,
|
||||
inputBase64: ``,
|
||||
expected: registry.AuthConfig{},
|
||||
},
|
||||
{
|
||||
doc: "empty JSON",
|
||||
input: `{}`,
|
||||
inputBase64: `e30=`,
|
||||
expected: registry.AuthConfig{},
|
||||
},
|
||||
{
|
||||
doc: "malformed JSON",
|
||||
input: `{`,
|
||||
inputBase64: `ew==`,
|
||||
expected: registry.AuthConfig{},
|
||||
expectedErr: `invalid X-Registry-Auth header: invalid JSON: unexpected EOF`,
|
||||
},
|
||||
{
|
||||
doc: "test authConfig",
|
||||
input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`,
|
||||
inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ==`,
|
||||
expected: registry.AuthConfig{
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
ServerAddress: "example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
// FIXME(thaJeztah): we should not accept multiple JSON documents.
|
||||
doc: "multiple authConfig",
|
||||
input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}{"username":"testuser2","password":"testpassword2","serveraddress":"example.org"}`,
|
||||
inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifXsidXNlcm5hbWUiOiJ0ZXN0dXNlcjIiLCJwYXNzd29yZCI6InRlc3RwYXNzd29yZDIiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5vcmcifQ==`,
|
||||
expected: registry.AuthConfig{
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
ServerAddress: "example.com",
|
||||
},
|
||||
},
|
||||
// We currently only support base64url encoding with padding, so
|
||||
// un-padded should produce an error.
|
||||
//
|
||||
// RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
|
||||
// RFC4648, section 3.2: https://tools.ietf.org/html/rfc4648#section-3.2
|
||||
{
|
||||
doc: "empty JSON no padding",
|
||||
input: `{}`,
|
||||
inputBase64: `e30`,
|
||||
expected: registry.AuthConfig{},
|
||||
expectedErr: `invalid X-Registry-Auth header: must be a valid base64url-encoded string`,
|
||||
},
|
||||
{
|
||||
doc: "test authConfig",
|
||||
input: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`,
|
||||
inputBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ`,
|
||||
expected: registry.AuthConfig{},
|
||||
expectedErr: `invalid X-Registry-Auth header: must be a valid base64url-encoded string`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
if tc.inputBase64 != "" {
|
||||
// Sanity check to make sure our fixtures are correct.
|
||||
b64 := base64.URLEncoding.EncodeToString([]byte(tc.input))
|
||||
if !strings.HasSuffix(tc.inputBase64, "=") {
|
||||
b64 = strings.TrimRight(b64, "=")
|
||||
}
|
||||
assert.Check(t, is.Equal(b64, tc.inputBase64))
|
||||
}
|
||||
|
||||
out, err := Decode(tc.inputBase64)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.ErrorType(err, errInvalidParameter{}))
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *out, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeAuthConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input registry.AuthConfig
|
||||
outBase64 string
|
||||
outPlain string
|
||||
}{
|
||||
{
|
||||
// Older daemons (or registries) may not handle an empty string,
|
||||
// which resulted in an "io.EOF" when unmarshaling or decoding.
|
||||
//
|
||||
// FIXME(thaJeztah): find exactly what code-paths are impacted by this.
|
||||
doc: "empty",
|
||||
input: registry.AuthConfig{},
|
||||
outBase64: `e30=`,
|
||||
outPlain: `{}`,
|
||||
},
|
||||
{
|
||||
doc: "test authConfig",
|
||||
input: registry.AuthConfig{
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
ServerAddress: "example.com",
|
||||
},
|
||||
outBase64: `eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwicGFzc3dvcmQiOiJ0ZXN0cGFzc3dvcmQiLCJzZXJ2ZXJhZGRyZXNzIjoiZXhhbXBsZS5jb20ifQ==`,
|
||||
outPlain: `{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
// Sanity check to make sure our fixtures are correct.
|
||||
b64 := base64.URLEncoding.EncodeToString([]byte(tc.outPlain))
|
||||
assert.Check(t, is.Equal(b64, tc.outBase64))
|
||||
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
out, err := Encode(tc.input)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, out, tc.outBase64)
|
||||
|
||||
authJSON, err := base64.URLEncoding.DecodeString(out)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(authJSON), tc.outPlain)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeAuthConfig(b *testing.B) {
|
||||
cases := []struct {
|
||||
doc string
|
||||
inputBase64 string
|
||||
invalid bool
|
||||
}{
|
||||
{
|
||||
doc: "empty",
|
||||
inputBase64: ``,
|
||||
},
|
||||
{
|
||||
doc: "empty JSON",
|
||||
inputBase64: `e30=`,
|
||||
},
|
||||
{
|
||||
doc: "valid",
|
||||
inputBase64: base64.URLEncoding.EncodeToString([]byte(`{"username":"testuser","password":"testpassword","serveraddress":"example.com"}`)),
|
||||
},
|
||||
{
|
||||
doc: "invalid base64",
|
||||
inputBase64: "not-base64",
|
||||
invalid: true,
|
||||
},
|
||||
{
|
||||
doc: "malformed JSON",
|
||||
inputBase64: `ew==`,
|
||||
invalid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.doc, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Decode(tc.inputBase64)
|
||||
if !tc.invalid && err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package stdcopy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// StdType is the type of standard stream
|
||||
// a writer can multiplex to.
|
||||
type StdType byte
|
||||
|
||||
const (
|
||||
Stdin StdType = 0 // Stdin represents standard input stream. It is present for completeness and should NOT be used. When reading the stream with [StdCopy] it is output on [Stdout].
|
||||
Stdout StdType = 1 // Stdout represents standard output stream.
|
||||
Stderr StdType = 2 // Stderr represents standard error steam.
|
||||
Systemerr StdType = 3 // Systemerr represents errors originating from the system. When reading the stream with [StdCopy] it is returned as an error.
|
||||
)
|
||||
|
||||
const (
|
||||
stdWriterPrefixLen = 8
|
||||
stdWriterFdIndex = 0
|
||||
stdWriterSizeIndex = 4
|
||||
|
||||
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
|
||||
)
|
||||
|
||||
// StdCopy is a modified version of [io.Copy] to de-multiplex messages
|
||||
// from "multiplexedSource" and copy them to destination streams
|
||||
// "destOut" and "destErr".
|
||||
//
|
||||
// StdCopy demultiplexes "multiplexedSource", assuming that it contains
|
||||
// two streams, previously multiplexed using a writer created with
|
||||
// [NewStdWriter].
|
||||
//
|
||||
// As it reads from "multiplexedSource", StdCopy writes [Stdout] messages
|
||||
// to "destOut", and [Stderr] message to "destErr]. For backward-compatibility,
|
||||
// [Stdin] messages are output to "destOut". The [Systemerr] stream provides
|
||||
// errors produced by the daemon. It is returned as an error, and terminates
|
||||
// processing the stream.
|
||||
//
|
||||
// StdCopy it reads until it hits [io.EOF] on "multiplexedSource", after
|
||||
// which it returns a nil error. In other words: any error returned indicates
|
||||
// a real underlying error, which may be when an unknown [StdType] stream
|
||||
// is received.
|
||||
//
|
||||
// The "written" return holds the total number of bytes written to "destOut"
|
||||
// and "destErr" combined.
|
||||
func StdCopy(destOut, destErr io.Writer, multiplexedSource io.Reader) (written int64, _ error) {
|
||||
var (
|
||||
buf = make([]byte, startingBufLen)
|
||||
bufLen = len(buf)
|
||||
nr, nw int
|
||||
err error
|
||||
out io.Writer
|
||||
frameSize int
|
||||
)
|
||||
|
||||
for {
|
||||
// Make sure we have at least a full header
|
||||
for nr < stdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, err = multiplexedSource.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr < stdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check the first byte to know where to write
|
||||
stream := StdType(buf[stdWriterFdIndex])
|
||||
switch stream {
|
||||
case Stdin:
|
||||
fallthrough
|
||||
case Stdout:
|
||||
// Write on stdout
|
||||
out = destOut
|
||||
case Stderr:
|
||||
// Write on stderr
|
||||
out = destErr
|
||||
case Systemerr:
|
||||
// If we're on Systemerr, we won't write anywhere.
|
||||
// NB: if this code changes later, make sure you don't try to write
|
||||
// to outstream if Systemerr is the stream
|
||||
out = nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unrecognized stream: %d", stream)
|
||||
}
|
||||
|
||||
// Retrieve the size of the frame
|
||||
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
|
||||
|
||||
// Check if the buffer is big enough to read the frame.
|
||||
// Extend it if necessary.
|
||||
if frameSize+stdWriterPrefixLen > bufLen {
|
||||
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
|
||||
bufLen = len(buf)
|
||||
}
|
||||
|
||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||
for nr < frameSize+stdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, err = multiplexedSource.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr < frameSize+stdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// we might have an error from the source mixed up in our multiplexed
|
||||
// stream. if we do, return it.
|
||||
if stream == Systemerr {
|
||||
return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen]))
|
||||
}
|
||||
|
||||
// Write the retrieved frame (without header)
|
||||
nw, err = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If the frame has not been fully written: error
|
||||
if nw != frameSize {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
written += int64(nw)
|
||||
|
||||
// Move the rest of the buffer to the beginning
|
||||
copy(buf, buf[frameSize+stdWriterPrefixLen:])
|
||||
// Move the index
|
||||
nr -= frameSize + stdWriterPrefixLen
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# commit to be tagged for new release
|
||||
commit = "HEAD"
|
||||
|
||||
project_name = "moby"
|
||||
github_repo = "moby/moby"
|
||||
sub_path = "api"
|
||||
ignore_deps = [ "github.com/moby/moby" ]
|
||||
|
||||
# previous release
|
||||
previous = "v28.2.2"
|
||||
|
||||
pre_release = true
|
||||
|
||||
preface = """\
|
||||
The first dedicated release for the Moby API. This release continues the 1.x
|
||||
line of API compatibility with the 52nd minor release of the 1.x API.
|
||||
"""
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
# vim: set noexpandtab:
|
||||
# -*- indent-tabs-mode: t -*-
|
||||
set -eu
|
||||
|
||||
API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
generate_model() {
|
||||
local package="$1"
|
||||
shift
|
||||
mapfile
|
||||
swagger generate model --spec="${API_DIR}/swagger.yaml" \
|
||||
--target="${API_DIR}" --model-package="$package" \
|
||||
--config-file="${API_DIR}/swagger-gen.yaml" \
|
||||
--template-dir="${API_DIR}/templates" --allow-template-override \
|
||||
"$@" \
|
||||
$(printf -- '--name=%s ' "${MAPFILE[@]}")
|
||||
}
|
||||
|
||||
# /==================================================================\
|
||||
# | |
|
||||
# | ATTENTION: |
|
||||
# | |
|
||||
# | Sort model package stanzas and model/operation names |
|
||||
# | *** ALPHABETICALLY *** |
|
||||
# | to reduce the likelihood of merge conflicts. |
|
||||
# | |
|
||||
# \==================================================================/
|
||||
|
||||
#region -------- Models --------
|
||||
|
||||
generate_model types/build <<- 'EOT'
|
||||
BuildCacheDiskUsage
|
||||
EOT
|
||||
|
||||
generate_model types/common <<- 'EOT'
|
||||
ErrorResponse
|
||||
IDResponse
|
||||
EOT
|
||||
|
||||
generate_model types/container <<- 'EOT'
|
||||
ChangeType
|
||||
ContainerCreateResponse
|
||||
ContainerTopResponse
|
||||
ContainerUpdateResponse
|
||||
ContainerWaitExitError
|
||||
ContainerWaitResponse
|
||||
ContainersDiskUsage
|
||||
FilesystemChange
|
||||
PortSummary
|
||||
EOT
|
||||
|
||||
generate_model types/image <<- 'EOT'
|
||||
ImageDeleteResponseItem
|
||||
ImagesDiskUsage
|
||||
ImageHistoryResponseItem
|
||||
EOT
|
||||
# ImageSummary
|
||||
# TODO: Restore when go-swagger is updated
|
||||
# See https://github.com/moby/moby/pull/47526#discussion_r1551800022
|
||||
|
||||
generate_model types/network --keep-spec-order --additional-initialism=IPAM <<- 'EOT'
|
||||
ConfigReference
|
||||
EndpointResource
|
||||
IPAMStatus
|
||||
Network
|
||||
NetworkConnectRequest
|
||||
NetworkCreateResponse
|
||||
NetworkDisconnectRequest
|
||||
NetworkInspect
|
||||
NetworkStatus
|
||||
NetworkSummary
|
||||
NetworkTaskInfo
|
||||
PeerInfo
|
||||
ServiceInfo
|
||||
SubnetStatus
|
||||
EOT
|
||||
|
||||
generate_model types/plugin <<- 'EOT'
|
||||
Plugin
|
||||
PluginDevice
|
||||
PluginEnv
|
||||
PluginMount
|
||||
EOT
|
||||
|
||||
generate_model types/registry <<- 'EOT'
|
||||
AuthResponse
|
||||
EOT
|
||||
|
||||
generate_model types/storage <<- 'EOT'
|
||||
DriverData
|
||||
RootFSStorage
|
||||
RootFSStorageSnapshot
|
||||
Storage
|
||||
EOT
|
||||
|
||||
generate_model types/swarm <<- 'EOT'
|
||||
ServiceCreateResponse
|
||||
ServiceUpdateResponse
|
||||
EOT
|
||||
|
||||
generate_model types/volume <<- 'EOT'
|
||||
Volume
|
||||
VolumeCreateRequest
|
||||
VolumeListResponse
|
||||
VolumesDiskUsage
|
||||
EOT
|
||||
|
||||
#endregion
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
API_DIR="${SCRIPT_DIR}/.."
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap "rm -rf ${TMP_DIR}" EXIT
|
||||
GEN_FILES=()
|
||||
|
||||
echo "Validating generated code..."
|
||||
echo "Separating generated files from handwritten files..."
|
||||
while IFS= read -r file; do
|
||||
GEN_FILES+=("$file")
|
||||
done < <(grep -rl "// Code generated" "${API_DIR}/types" || true)
|
||||
|
||||
echo "Copying generated files into temporary folder..."
|
||||
for f in "${GEN_FILES[@]}"; do
|
||||
mkdir -p "${TMP_DIR}/$(dirname "${f#${API_DIR}/}")"
|
||||
cp "$f" "${TMP_DIR}/${f#${API_DIR}/}"
|
||||
done
|
||||
|
||||
cp "${API_DIR}/swagger.yaml" "${TMP_DIR}/"
|
||||
cp "${API_DIR}/swagger-gen.yaml" "${TMP_DIR}/"
|
||||
cp -r "${API_DIR}/templates" "${TMP_DIR}/" 2> /dev/null || true
|
||||
|
||||
echo "Generating swagger types in temporary folder..."
|
||||
(
|
||||
cd "${TMP_DIR}"
|
||||
"${SCRIPT_DIR}/generate-swagger-api.sh" > /dev/null 2>&1
|
||||
)
|
||||
|
||||
echo "Run diff for all generated files..."
|
||||
DIFF_FOUND=false
|
||||
for f in "${GEN_FILES[@]}"; do
|
||||
REL="${f#${API_DIR}/}"
|
||||
if ! diff -q "${TMP_DIR}/${REL}" "${API_DIR}/${REL}" > /dev/null 2>&1; then
|
||||
echo "Difference found in ${REL}"
|
||||
diff -u "${TMP_DIR}/${REL}" "${API_DIR}/${REL}" || true
|
||||
DIFF_FOUND=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$DIFF_FOUND" = true ]; then
|
||||
echo
|
||||
echo "Swagger validation failed. Please run:"
|
||||
echo " ./scripts/generate-swagger-api.sh"
|
||||
echo "and commit updated generated files."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Swagger file is up to date."
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Expected to be in api directory
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
echo "Validating swagger.yaml..."
|
||||
|
||||
yamllint -f parsable -c validate/yamllint.yaml swagger.yaml
|
||||
|
||||
if out=$(swagger validate swagger.yaml); then
|
||||
echo "Validation done! ${out}"
|
||||
else
|
||||
echo "${out}" >&2
|
||||
false
|
||||
fi
|
||||
128
api/server/backend/build/backend.go
Normal file
128
api/server/backend/build/backend.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/builder"
|
||||
buildkit "github.com/docker/docker/builder/builder-next"
|
||||
daemonevents "github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// ImageComponent provides an interface for working with images
|
||||
type ImageComponent interface {
|
||||
SquashImage(from string, to string) (string, error)
|
||||
TagImage(context.Context, image.ID, reference.Named) error
|
||||
}
|
||||
|
||||
// Builder defines interface for running a build
|
||||
type Builder interface {
|
||||
Build(context.Context, backend.BuildConfig) (*builder.Result, error)
|
||||
}
|
||||
|
||||
// Backend provides build functionality to the API router
|
||||
type Backend struct {
|
||||
builder Builder
|
||||
imageComponent ImageComponent
|
||||
buildkit *buildkit.Builder
|
||||
eventsService *daemonevents.Events
|
||||
}
|
||||
|
||||
// NewBackend creates a new build backend from components
|
||||
func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
|
||||
return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
|
||||
}
|
||||
|
||||
// RegisterGRPC registers buildkit controller to the grpc server.
|
||||
func (b *Backend) RegisterGRPC(s *grpc.Server) {
|
||||
if b.buildkit != nil {
|
||||
b.buildkit.RegisterGRPC(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds an image from a Source
|
||||
func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
|
||||
options := config.Options
|
||||
useBuildKit := options.Version == build.BuilderBuildKit
|
||||
|
||||
tags, err := sanitizeRepoAndTags(options.Tags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buildResult *builder.Result
|
||||
if useBuildKit {
|
||||
buildResult, err = b.buildkit.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
buildResult, err = b.builder.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if buildResult == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
imageID := buildResult.ImageID
|
||||
if options.Squash {
|
||||
if imageID, err = squashBuild(buildResult, b.imageComponent); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config.ProgressWriter.AuxFormatter != nil {
|
||||
if err = config.ProgressWriter.AuxFormatter.Emit("moby.image.id", build.Result{ID: imageID}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if imageID != "" && !useBuildKit {
|
||||
stdout := config.ProgressWriter.StdoutFormatter
|
||||
_, _ = fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
|
||||
err = tagImages(ctx, b.imageComponent, config.ProgressWriter.StdoutFormatter, image.ID(imageID), tags)
|
||||
}
|
||||
return imageID, err
|
||||
}
|
||||
|
||||
// PruneCache removes all cached build sources
|
||||
func (b *Backend) PruneCache(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
|
||||
buildCacheSize, cacheIDs, err := b.buildkit.Prune(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to prune build cache")
|
||||
}
|
||||
b.eventsService.Log(events.ActionPrune, events.BuilderEventType, events.Actor{
|
||||
Attributes: map[string]string{
|
||||
"reclaimed": strconv.FormatInt(buildCacheSize, 10),
|
||||
},
|
||||
})
|
||||
return &build.CachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
|
||||
}
|
||||
|
||||
// Cancel cancels the build by ID
|
||||
func (b *Backend) Cancel(ctx context.Context, id string) error {
|
||||
return b.buildkit.Cancel(ctx, id)
|
||||
}
|
||||
|
||||
func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
|
||||
var fromID string
|
||||
if build.FromImage != nil {
|
||||
fromID = build.FromImage.ImageID()
|
||||
}
|
||||
imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error squashing image")
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/v2/daemon/internal/image"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
15
api/server/httputils/decoder.go
Normal file
15
api/server/httputils/decoder.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// ContainerDecoder specifies how
|
||||
// to translate an io.Reader into
|
||||
// container configuration.
|
||||
type ContainerDecoder interface {
|
||||
DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -91,7 +91,7 @@ func RepoTagReference(repo, tag string) (reference.NamedTagged, error) {
|
||||
}
|
||||
|
||||
if _, isDigested := ref.(reference.Digested); isDigested {
|
||||
return nil, errors.New("cannot import digest reference")
|
||||
return nil, fmt.Errorf("cannot import digest reference")
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
@@ -114,6 +114,16 @@ type ArchiveOptions struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type badParameterError struct {
|
||||
param string
|
||||
}
|
||||
|
||||
func (e badParameterError) Error() string {
|
||||
return "bad parameter: " + e.param + "cannot be empty"
|
||||
}
|
||||
|
||||
func (e badParameterError) InvalidParameter() {}
|
||||
|
||||
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
||||
// It fails if the archive name and path are not in the request.
|
||||
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
|
||||
@@ -123,11 +133,11 @@ func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions,
|
||||
|
||||
name := vars["name"]
|
||||
if name == "" {
|
||||
return ArchiveOptions{}, errdefs.InvalidParameter(errors.New("bad parameter: name cannot be empty"))
|
||||
return ArchiveOptions{}, badParameterError{"name"}
|
||||
}
|
||||
path := r.Form.Get("path")
|
||||
if path == "" {
|
||||
return ArchiveOptions{}, errdefs.InvalidParameter(errors.New("bad parameter: path cannot be empty"))
|
||||
return ArchiveOptions{}, badParameterError{"path"}
|
||||
}
|
||||
return ArchiveOptions{name, path}, nil
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
}
|
||||
|
||||
// CloseStreams ensures that a list for http streams are properly closed.
|
||||
func CloseStreams(streams ...any) {
|
||||
func CloseStreams(streams ...interface{}) {
|
||||
for _, stream := range streams {
|
||||
if tcpc, ok := stream.(interface {
|
||||
CloseWrite() error
|
||||
@@ -59,7 +59,7 @@ func CheckForJSON(r *http.Request) error {
|
||||
|
||||
// ReadJSON validates the request to have the correct content-type, and decodes
|
||||
// the request's Body into out.
|
||||
func ReadJSON(r *http.Request, out any) error {
|
||||
func ReadJSON(r *http.Request, out interface{}) error {
|
||||
err := CheckForJSON(r)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -87,7 +87,7 @@ func ReadJSON(r *http.Request, out any) error {
|
||||
}
|
||||
|
||||
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||
func WriteJSON(w http.ResponseWriter, code int, v any) error {
|
||||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
enc := json.NewEncoder(w)
|
||||
89
api/server/httputils/write_log_stream.go
Normal file
89
api/server/httputils/write_log_stream.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
// WriteLogStream writes an encoded byte stream of log messages from the
|
||||
// messages channel, multiplexing them with a stdcopy.Writer if mux is true
|
||||
func WriteLogStream(_ context.Context, w http.ResponseWriter, msgs <-chan *backend.LogMessage, config *container.LogsOptions, mux bool) {
|
||||
// See https://github.com/moby/moby/issues/47448
|
||||
// Trigger headers to be written immediately.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
wf := ioutils.NewWriteFlusher(w)
|
||||
defer wf.Close()
|
||||
|
||||
wf.Flush()
|
||||
|
||||
outStream := io.Writer(wf)
|
||||
errStream := outStream
|
||||
sysErrStream := errStream
|
||||
if mux {
|
||||
sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr)
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
}
|
||||
|
||||
for {
|
||||
msg, ok := <-msgs
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// check if the message contains an error. if so, write that error
|
||||
// and exit
|
||||
if msg.Err != nil {
|
||||
fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err)
|
||||
continue
|
||||
}
|
||||
logLine := msg.Line
|
||||
if config.Details {
|
||||
logLine = append(attrsByteSlice(msg.Attrs), ' ')
|
||||
logLine = append(logLine, msg.Line...)
|
||||
}
|
||||
if config.Timestamps {
|
||||
logLine = append([]byte(msg.Timestamp.Format(jsonmessage.RFC3339NanoFixed)+" "), logLine...)
|
||||
}
|
||||
if msg.Source == "stdout" && config.ShowStdout {
|
||||
_, _ = outStream.Write(logLine)
|
||||
}
|
||||
if msg.Source == "stderr" && config.ShowStderr {
|
||||
_, _ = errStream.Write(logLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byKey []backend.LogAttr
|
||||
|
||||
func (b byKey) Len() int { return len(b) }
|
||||
func (b byKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
|
||||
func (b byKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
|
||||
func attrsByteSlice(a []backend.LogAttr) []byte {
|
||||
// Note this sorts "a" in-place. That is fine here - nothing else is
|
||||
// going to use Attrs or care about the order.
|
||||
sort.Sort(byKey(a))
|
||||
|
||||
var ret []byte
|
||||
for i, pair := range a {
|
||||
k, v := url.QueryEscape(pair.Key), url.QueryEscape(pair.Value)
|
||||
ret = append(ret, []byte(k)...)
|
||||
ret = append(ret, '=')
|
||||
ret = append(ret, []byte(v)...)
|
||||
if i != len(a)-1 {
|
||||
ret = append(ret, ',')
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/middleware"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/middleware"
|
||||
)
|
||||
|
||||
// handlerWithGlobalMiddlewares wraps the handler function for a request with
|
||||
@@ -16,7 +16,7 @@ func (s *Server) handlerWithGlobalMiddlewares(handler httputils.APIFunc) httputi
|
||||
next = m.WrapHandler(next)
|
||||
}
|
||||
|
||||
if log.GetLevel() >= log.DebugLevel {
|
||||
if log.GetLevel() == log.DebugLevel {
|
||||
next = middleware.DebugRequestMiddleware(next)
|
||||
}
|
||||
|
||||
116
api/server/middleware/debug.go
Normal file
116
api/server/middleware/debug.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httpstatus"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// DebugRequestMiddleware dumps the request to logger
|
||||
func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
logger := log.G(ctx)
|
||||
|
||||
// Use a variable for fields to prevent overhead of repeatedly
|
||||
// calling WithFields.
|
||||
fields := log.Fields{
|
||||
"module": "api",
|
||||
"method": r.Method,
|
||||
"request-url": r.RequestURI,
|
||||
"vars": vars,
|
||||
}
|
||||
handleWithLogs := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
logger.WithFields(fields).Debugf("handling %s request", r.Method)
|
||||
err := handler(ctx, w, r, vars)
|
||||
if err != nil {
|
||||
// TODO(thaJeztah): unify this with Server.makeHTTPHandler, which also logs internal server errors as error-log. See https://github.com/moby/moby/pull/48740#discussion_r1816675574
|
||||
fields["error-response"] = err
|
||||
fields["status"] = httpstatus.FromError(err)
|
||||
logger.WithFields(fields).Debugf("error response for %s request", r.Method)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
return handleWithLogs(ctx, w, r, vars)
|
||||
}
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return handleWithLogs(ctx, w, r, vars)
|
||||
}
|
||||
maxBodySize := 4096 // 4KB
|
||||
if r.ContentLength > int64(maxBodySize) {
|
||||
return handleWithLogs(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
body := r.Body
|
||||
bufReader := bufio.NewReaderSize(body, maxBodySize)
|
||||
r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
|
||||
|
||||
b, err := bufReader.Peek(maxBodySize)
|
||||
if err != io.EOF {
|
||||
// either there was an error reading, or the buffer is full (in which case the request is too large)
|
||||
return handleWithLogs(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
var postForm map[string]interface{}
|
||||
if err := json.Unmarshal(b, &postForm); err == nil {
|
||||
maskSecretKeys(postForm)
|
||||
// TODO(thaJeztah): is there a better way to detect if we're using JSON-formatted logs?
|
||||
if _, ok := logger.Logger.Formatter.(*logrus.JSONFormatter); ok {
|
||||
fields["form-data"] = postForm
|
||||
} else {
|
||||
if data, err := json.Marshal(postForm); err != nil {
|
||||
fields["form-data"] = postForm
|
||||
} else {
|
||||
fields["form-data"] = string(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleWithLogs(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
func maskSecretKeys(inp interface{}) {
|
||||
if arr, ok := inp.([]interface{}); ok {
|
||||
for _, f := range arr {
|
||||
maskSecretKeys(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if form, ok := inp.(map[string]interface{}); ok {
|
||||
scrub := []string{
|
||||
// Note: The Data field contains the base64-encoded secret in 'secret'
|
||||
// and 'config' create and update requests. Currently, no other POST
|
||||
// API endpoints use a data field, so we scrub this field unconditionally.
|
||||
// Change this handling to be conditional if a new endpoint is added
|
||||
// in future where this field should not be scrubbed.
|
||||
"data",
|
||||
"jointoken",
|
||||
"password",
|
||||
"secret",
|
||||
"signingcakey",
|
||||
"unlockkey",
|
||||
}
|
||||
loop0:
|
||||
for k, v := range form {
|
||||
for _, m := range scrub {
|
||||
if strings.EqualFold(m, k) {
|
||||
form[k] = "*****"
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
maskSecretKeys(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
75
api/server/middleware/debug_test.go
Normal file
75
api/server/middleware/debug_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMaskSecretKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
doc: "secret/config create and update requests",
|
||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
},
|
||||
{
|
||||
doc: "masking other fields (recursively)",
|
||||
input: map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
"other": map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "case insensitive field matching",
|
||||
input: map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range tests {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
maskSecretKeys(testcase.input)
|
||||
assert.Check(t, is.DeepEqual(testcase.expected, testcase.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
86
api/server/middleware/version.go
Normal file
86
api/server/middleware/version.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
)
|
||||
|
||||
// VersionMiddleware is a middleware that
|
||||
// validates the client and server versions.
|
||||
type VersionMiddleware struct {
|
||||
serverVersion string
|
||||
|
||||
// defaultAPIVersion is the default API version provided by the API server,
|
||||
// specified as "major.minor". It is usually configured to the latest API
|
||||
// version [github.com/docker/docker/api.DefaultVersion].
|
||||
//
|
||||
// API requests for API versions greater than this version are rejected by
|
||||
// the server and produce a [versionUnsupportedError].
|
||||
defaultAPIVersion string
|
||||
|
||||
// minAPIVersion is the minimum API version provided by the API server,
|
||||
// specified as "major.minor".
|
||||
//
|
||||
// API requests for API versions lower than this version are rejected by
|
||||
// the server and produce a [versionUnsupportedError].
|
||||
minAPIVersion string
|
||||
}
|
||||
|
||||
// NewVersionMiddleware creates a VersionMiddleware with the given versions.
|
||||
func NewVersionMiddleware(serverVersion, defaultAPIVersion, minAPIVersion string) (*VersionMiddleware, error) {
|
||||
if versions.LessThan(defaultAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(defaultAPIVersion, api.DefaultVersion) {
|
||||
return nil, fmt.Errorf("invalid default API version (%s): must be between %s and %s", defaultAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
|
||||
}
|
||||
if versions.LessThan(minAPIVersion, api.MinSupportedAPIVersion) || versions.GreaterThan(minAPIVersion, api.DefaultVersion) {
|
||||
return nil, fmt.Errorf("invalid minimum API version (%s): must be between %s and %s", minAPIVersion, api.MinSupportedAPIVersion, api.DefaultVersion)
|
||||
}
|
||||
if versions.GreaterThan(minAPIVersion, defaultAPIVersion) {
|
||||
return nil, fmt.Errorf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", minAPIVersion, defaultAPIVersion)
|
||||
}
|
||||
return &VersionMiddleware{
|
||||
serverVersion: serverVersion,
|
||||
defaultAPIVersion: defaultAPIVersion,
|
||||
minAPIVersion: minAPIVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type versionUnsupportedError struct {
|
||||
version, minVersion, maxVersion string
|
||||
}
|
||||
|
||||
func (e versionUnsupportedError) Error() string {
|
||||
if e.minVersion != "" {
|
||||
return fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", e.version, e.minVersion)
|
||||
}
|
||||
return fmt.Sprintf("client version %s is too new. Maximum supported API version is %s", e.version, e.maxVersion)
|
||||
}
|
||||
|
||||
func (e versionUnsupportedError) InvalidParameter() {}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.Header().Set("Server", fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS))
|
||||
w.Header().Set("Api-Version", v.defaultAPIVersion)
|
||||
w.Header().Set("Ostype", runtime.GOOS)
|
||||
|
||||
apiVersion := vars["version"]
|
||||
if apiVersion == "" {
|
||||
apiVersion = v.defaultAPIVersion
|
||||
}
|
||||
if versions.LessThan(apiVersion, v.minAPIVersion) {
|
||||
return versionUnsupportedError{version: apiVersion, minVersion: v.minAPIVersion}
|
||||
}
|
||||
if versions.GreaterThan(apiVersion, v.defaultAPIVersion) {
|
||||
return versionUnsupportedError{version: apiVersion, maxVersion: v.defaultAPIVersion}
|
||||
}
|
||||
ctx = context.WithValue(ctx, httputils.APIVersionKey{}, apiVersion)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
145
api/server/middleware/version_test.go
Normal file
145
api/server/middleware/version_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestNewVersionMiddlewareValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc, defaultVersion, minVersion, expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "defaults",
|
||||
defaultVersion: api.DefaultVersion,
|
||||
minVersion: api.MinSupportedAPIVersion,
|
||||
},
|
||||
{
|
||||
doc: "invalid default lower than min",
|
||||
defaultVersion: api.MinSupportedAPIVersion,
|
||||
minVersion: api.DefaultVersion,
|
||||
expectedErr: fmt.Sprintf("invalid API version: the minimum API version (%s) is higher than the default version (%s)", api.DefaultVersion, api.MinSupportedAPIVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid default too low",
|
||||
defaultVersion: "0.1",
|
||||
minVersion: api.MinSupportedAPIVersion,
|
||||
expectedErr: fmt.Sprintf("invalid default API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid default too high",
|
||||
defaultVersion: "9999.9999",
|
||||
minVersion: api.DefaultVersion,
|
||||
expectedErr: fmt.Sprintf("invalid default API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid minimum too low",
|
||||
defaultVersion: api.MinSupportedAPIVersion,
|
||||
minVersion: "0.1",
|
||||
expectedErr: fmt.Sprintf("invalid minimum API version (0.1): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
{
|
||||
doc: "invalid minimum too high",
|
||||
defaultVersion: api.DefaultVersion,
|
||||
minVersion: "9999.9999",
|
||||
expectedErr: fmt.Sprintf("invalid minimum API version (9999.9999): must be between %s and %s", api.MinSupportedAPIVersion, api.DefaultVersion),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
_, err := NewVersionMiddleware("1.2.3", tc.defaultVersion, tc.minVersion)
|
||||
if tc.expectedErr == "" {
|
||||
assert.Check(t, err)
|
||||
} else {
|
||||
assert.Check(t, is.Error(err, tc.expectedErr))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionMiddlewareVersion(t *testing.T) {
|
||||
expectedVersion := "<not set>"
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v := httputils.VersionFromContext(ctx)
|
||||
assert.Check(t, is.Equal(expectedVersion, v))
|
||||
return nil
|
||||
}
|
||||
|
||||
m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
|
||||
assert.NilError(t, err)
|
||||
h := m.WrapHandler(handler)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/containers/json", http.NoBody)
|
||||
resp := httptest.NewRecorder()
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
reqVersion string
|
||||
expectedVersion string
|
||||
errString string
|
||||
}{
|
||||
{
|
||||
expectedVersion: api.DefaultVersion,
|
||||
},
|
||||
{
|
||||
reqVersion: api.MinSupportedAPIVersion,
|
||||
expectedVersion: api.MinSupportedAPIVersion,
|
||||
},
|
||||
{
|
||||
reqVersion: "0.1",
|
||||
errString: fmt.Sprintf("client version 0.1 is too old. Minimum supported API version is %s, please upgrade your client to a newer version", api.MinSupportedAPIVersion),
|
||||
},
|
||||
{
|
||||
reqVersion: "9999.9999",
|
||||
errString: fmt.Sprintf("client version 9999.9999 is too new. Maximum supported API version is %s", api.DefaultVersion),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expectedVersion = test.expectedVersion
|
||||
|
||||
err := h(ctx, resp, req, map[string]string{"version": test.reqVersion})
|
||||
|
||||
if test.errString != "" {
|
||||
assert.Check(t, is.Error(err, test.errString))
|
||||
} else {
|
||||
assert.Check(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionMiddlewareWithErrorsReturnsHeaders(t *testing.T) {
|
||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v := httputils.VersionFromContext(ctx)
|
||||
assert.Check(t, v != "")
|
||||
return nil
|
||||
}
|
||||
|
||||
m, err := NewVersionMiddleware("1.2.3", api.DefaultVersion, api.MinSupportedAPIVersion)
|
||||
assert.NilError(t, err)
|
||||
h := m.WrapHandler(handler)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/containers/json", http.NoBody)
|
||||
resp := httptest.NewRecorder()
|
||||
ctx := context.Background()
|
||||
|
||||
vars := map[string]string{"version": "0.1"}
|
||||
err = h(ctx, resp, req, vars)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
hdr := resp.Result().Header
|
||||
assert.Check(t, is.Contains(hdr.Get("Server"), "Docker/1.2.3"))
|
||||
assert.Check(t, is.Contains(hdr.Get("Server"), runtime.GOOS))
|
||||
assert.Check(t, is.Equal(hdr.Get("Api-Version"), api.DefaultVersion))
|
||||
assert.Check(t, is.Equal(hdr.Get("Ostype"), runtime.GOOS))
|
||||
}
|
||||
23
api/server/router/build/backend.go
Normal file
23
api/server/router/build/backend.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
)
|
||||
|
||||
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
|
||||
type Backend interface {
|
||||
// Build a Docker image returning the id of the image
|
||||
// TODO: make this return a reference instead of string
|
||||
Build(context.Context, backend.BuildConfig) (string, error)
|
||||
|
||||
// PruneCache prunes the build cache.
|
||||
PruneCache(context.Context, build.CachePruneOptions) (*build.CachePruneReport, error)
|
||||
Cancel(context.Context, string) error
|
||||
}
|
||||
|
||||
type experimentalProvider interface {
|
||||
HasExperimental() bool
|
||||
}
|
||||
67
api/server/router/build/build.go
Normal file
67
api/server/router/build/build.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
)
|
||||
|
||||
// buildRouter is a router to talk with the build controller
|
||||
type buildRouter struct {
|
||||
backend Backend
|
||||
daemon experimentalProvider
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new build router
|
||||
func NewRouter(b Backend, d experimentalProvider) router.Router {
|
||||
r := &buildRouter{
|
||||
backend: b,
|
||||
daemon: d,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routers to the build controller
|
||||
func (br *buildRouter) Routes() []router.Route {
|
||||
return br.routes
|
||||
}
|
||||
|
||||
func (br *buildRouter) initRoutes() {
|
||||
br.routes = []router.Route{
|
||||
router.NewPostRoute("/build", br.postBuild),
|
||||
router.NewPostRoute("/build/prune", br.postPrune),
|
||||
router.NewPostRoute("/build/cancel", br.postCancel),
|
||||
}
|
||||
}
|
||||
|
||||
// BuilderVersion derives the default docker builder version from the config.
|
||||
//
|
||||
// The default on Linux is version "2" (BuildKit), but the daemon can be
|
||||
// configured to recommend version "1" (classic Builder). Windows does not
|
||||
// yet support BuildKit for native Windows images, and uses "1" (classic builder)
|
||||
// as a default.
|
||||
//
|
||||
// This value is only a recommendation as advertised by the daemon, and it is
|
||||
// up to the client to choose which builder to use.
|
||||
func BuilderVersion(features map[string]bool) build.BuilderVersion {
|
||||
// TODO(thaJeztah) move the default to daemon/config
|
||||
bv := build.BuilderBuildKit
|
||||
if runtime.GOOS == "windows" {
|
||||
// BuildKit is not yet the default on Windows.
|
||||
bv = build.BuilderV1
|
||||
}
|
||||
|
||||
// Allow the features field in the daemon config to override the
|
||||
// default builder to advertise.
|
||||
if enable, ok := features["buildkit"]; ok {
|
||||
if enable {
|
||||
bv = build.BuilderBuildKit
|
||||
} else {
|
||||
bv = build.BuilderV1
|
||||
}
|
||||
}
|
||||
return bv
|
||||
}
|
||||
@@ -16,16 +16,16 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/progress"
|
||||
"github.com/moby/moby/v2/daemon/internal/streamformatter"
|
||||
"github.com/moby/moby/v2/daemon/internal/versions"
|
||||
"github.com/moby/moby/v2/daemon/server/buildbackend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -35,8 +35,8 @@ type invalidParam struct {
|
||||
|
||||
func (e invalidParam) InvalidParameter() {}
|
||||
|
||||
func newImageBuildOptions(ctx context.Context, r *http.Request) (*buildbackend.BuildOptions, error) {
|
||||
options := &buildbackend.BuildOptions{
|
||||
func newImageBuildOptions(ctx context.Context, r *http.Request) (*build.ImageBuildOptions, error) {
|
||||
options := &build.ImageBuildOptions{
|
||||
Version: build.BuilderV1, // Builder V1 is the default, but can be overridden
|
||||
Dockerfile: r.FormValue("dockerfile"),
|
||||
SuppressOutput: httputils.BoolValue(r, "q"),
|
||||
@@ -81,7 +81,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*buildbackend.B
|
||||
if versions.GreaterThanOrEqualTo(version, "1.40") {
|
||||
outputsJSON := r.FormValue("outputs")
|
||||
if outputsJSON != "" {
|
||||
var outputs []buildbackend.BuildOutput
|
||||
var outputs []build.ImageBuildOutput
|
||||
if err := json.Unmarshal([]byte(outputsJSON), &outputs); err != nil {
|
||||
return nil, invalidParam{errors.Wrap(err, "invalid outputs specified")}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *
|
||||
return err
|
||||
}
|
||||
|
||||
opts := buildbackend.CachePruneOptions{
|
||||
opts := build.CachePruneOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Filters: fltrs,
|
||||
}
|
||||
@@ -313,7 +313,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
|
||||
|
||||
wantAux := versions.GreaterThanOrEqualTo(version, "1.30")
|
||||
|
||||
imgID, err := br.backend.Build(ctx, buildbackend.BuildConfig{
|
||||
imgID, err := br.backend.Build(ctx, backend.BuildConfig{
|
||||
Source: body,
|
||||
Options: buildOptions,
|
||||
ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader),
|
||||
@@ -359,7 +359,7 @@ func (s *syncWriter) Write(b []byte) (int, error) {
|
||||
return s.w.Write(b)
|
||||
}
|
||||
|
||||
func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) buildbackend.ProgressWriter {
|
||||
func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter {
|
||||
// see https://github.com/moby/moby/pull/21406
|
||||
out = &syncWriter{w: out}
|
||||
|
||||
@@ -368,7 +368,7 @@ func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(
|
||||
aux = &streamformatter.AuxFormatter{Writer: out}
|
||||
}
|
||||
|
||||
return buildbackend.ProgressWriter{
|
||||
return backend.ProgressWriter{
|
||||
Output: out,
|
||||
StdoutFormatter: streamformatter.NewStdoutWriter(out),
|
||||
StderrFormatter: streamformatter.NewStderrWriter(out),
|
||||
10
api/server/router/checkpoint/backend.go
Normal file
10
api/server/router/checkpoint/backend.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package checkpoint
|
||||
|
||||
import "github.com/docker/docker/api/types/checkpoint"
|
||||
|
||||
// Backend for Checkpoint
|
||||
type Backend interface {
|
||||
CheckpointCreate(container string, config checkpoint.CreateOptions) error
|
||||
CheckpointDelete(container string, config checkpoint.DeleteOptions) error
|
||||
CheckpointList(container string, config checkpoint.ListOptions) ([]checkpoint.Summary, error)
|
||||
}
|
||||
36
api/server/router/checkpoint/checkpoint.go
Normal file
36
api/server/router/checkpoint/checkpoint.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// checkpointRouter is a router to talk with the checkpoint controller
|
||||
type checkpointRouter struct {
|
||||
backend Backend
|
||||
decoder httputils.ContainerDecoder
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new checkpoint router
|
||||
func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router {
|
||||
r := &checkpointRouter{
|
||||
backend: b,
|
||||
decoder: decoder,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routers to the checkpoint controller
|
||||
func (cr *checkpointRouter) Routes() []router.Route {
|
||||
return cr.routes
|
||||
}
|
||||
|
||||
func (cr *checkpointRouter) initRoutes() {
|
||||
cr.routes = []router.Route{
|
||||
router.NewGetRoute("/containers/{name:.*}/checkpoints", cr.getContainerCheckpoints, router.Experimental),
|
||||
router.NewPostRoute("/containers/{name:.*}/checkpoints", cr.postContainerCheckpoint, router.Experimental),
|
||||
router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", cr.deleteContainerCheckpoint, router.Experimental),
|
||||
}
|
||||
}
|
||||
60
api/server/router/checkpoint/checkpoint_routes.go
Normal file
60
api/server/router/checkpoint/checkpoint_routes.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/checkpoint"
|
||||
)
|
||||
|
||||
func (cr *checkpointRouter) postContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var options checkpoint.CreateOptions
|
||||
if err := httputils.ReadJSON(r, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := cr.backend.CheckpointCreate(vars["name"], options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *checkpointRouter) getContainerCheckpoints(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkpoints, err := cr.backend.CheckpointList(vars["name"], checkpoint.ListOptions{
|
||||
CheckpointDir: r.Form.Get("dir"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, checkpoints)
|
||||
}
|
||||
|
||||
func (cr *checkpointRouter) deleteContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := cr.backend.CheckpointDelete(vars["name"], checkpoint.DeleteOptions{
|
||||
CheckpointDir: r.Form.Get("dir"),
|
||||
CheckpointID: vars["checkpoint"],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
79
api/server/router/container/backend.go
Normal file
79
api/server/router/container/backend.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/moby/go-archive"
|
||||
)
|
||||
|
||||
// execBackend includes functions to implement to provide exec functionality.
|
||||
type execBackend interface {
|
||||
ContainerExecCreate(name string, options *container.ExecOptions) (string, error)
|
||||
ContainerExecInspect(id string) (*backend.ExecInspect, error)
|
||||
ContainerExecResize(ctx context.Context, name string, height, width uint32) error
|
||||
ContainerExecStart(ctx context.Context, name string, options backend.ExecStartConfig) error
|
||||
ExecExists(name string) (bool, error)
|
||||
}
|
||||
|
||||
// copyBackend includes functions to implement to provide container copy functionality.
|
||||
type copyBackend interface {
|
||||
ContainerArchivePath(name string, path string) (content io.ReadCloser, stat *container.PathStat, err error)
|
||||
ContainerExport(ctx context.Context, name string, out io.Writer) error
|
||||
ContainerExtractToDir(name, path string, copyUIDGID, noOverwriteDirNonDir bool, content io.Reader) error
|
||||
ContainerStatPath(name string, path string) (stat *container.PathStat, err error)
|
||||
}
|
||||
|
||||
// stateBackend includes functions to implement to provide container state lifecycle functionality.
|
||||
type stateBackend interface {
|
||||
ContainerCreate(ctx context.Context, config backend.ContainerCreateConfig) (container.CreateResponse, error)
|
||||
ContainerKill(name string, signal string) error
|
||||
ContainerPause(name string) error
|
||||
ContainerRename(oldName, newName string) error
|
||||
ContainerResize(ctx context.Context, name string, height, width uint32) error
|
||||
ContainerRestart(ctx context.Context, name string, options container.StopOptions) error
|
||||
ContainerRm(name string, config *backend.ContainerRmConfig) error
|
||||
ContainerStart(ctx context.Context, name string, checkpoint string, checkpointDir string) error
|
||||
ContainerStop(ctx context.Context, name string, options container.StopOptions) error
|
||||
ContainerUnpause(name string) error
|
||||
ContainerUpdate(name string, hostConfig *container.HostConfig) (container.UpdateResponse, error)
|
||||
ContainerWait(ctx context.Context, name string, condition container.WaitCondition) (<-chan container.StateStatus, error)
|
||||
}
|
||||
|
||||
// monitorBackend includes functions to implement to provide containers monitoring functionality.
|
||||
type monitorBackend interface {
|
||||
ContainerChanges(ctx context.Context, name string) ([]archive.Change, error)
|
||||
ContainerInspect(ctx context.Context, name string, options backend.ContainerInspectOptions) (*container.InspectResponse, error)
|
||||
ContainerLogs(ctx context.Context, name string, config *container.LogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
|
||||
ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error
|
||||
ContainerTop(name string, psArgs string) (*container.TopResponse, error)
|
||||
Containers(ctx context.Context, config *container.ListOptions) ([]*container.Summary, error)
|
||||
}
|
||||
|
||||
// attachBackend includes function to implement to provide container attaching functionality.
|
||||
type attachBackend interface {
|
||||
ContainerAttach(name string, c *backend.ContainerAttachConfig) error
|
||||
}
|
||||
|
||||
// systemBackend includes functions to implement to provide system wide containers functionality
|
||||
type systemBackend interface {
|
||||
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*container.PruneReport, error)
|
||||
}
|
||||
|
||||
type commitBackend interface {
|
||||
CreateImageFromContainer(ctx context.Context, name string, config *backend.CreateImageConfig) (imageID string, err error)
|
||||
}
|
||||
|
||||
// Backend is all the methods that need to be implemented to provide container specific functionality.
|
||||
type Backend interface {
|
||||
commitBackend
|
||||
execBackend
|
||||
copyBackend
|
||||
stateBackend
|
||||
monitorBackend
|
||||
attachBackend
|
||||
systemBackend
|
||||
}
|
||||
71
api/server/router/container/container.go
Normal file
71
api/server/router/container/container.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// containerRouter is a router to talk with the container controller
|
||||
type containerRouter struct {
|
||||
backend Backend
|
||||
decoder httputils.ContainerDecoder
|
||||
routes []router.Route
|
||||
cgroup2 bool
|
||||
}
|
||||
|
||||
// NewRouter initializes a new container router
|
||||
func NewRouter(b Backend, decoder httputils.ContainerDecoder, cgroup2 bool) router.Router {
|
||||
r := &containerRouter{
|
||||
backend: b,
|
||||
decoder: decoder,
|
||||
cgroup2: cgroup2,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the container controller
|
||||
func (c *containerRouter) Routes() []router.Route {
|
||||
return c.routes
|
||||
}
|
||||
|
||||
// initRoutes initializes the routes in container router
|
||||
func (c *containerRouter) initRoutes() {
|
||||
c.routes = []router.Route{
|
||||
// HEAD
|
||||
router.NewHeadRoute("/containers/{name:.*}/archive", c.headContainersArchive),
|
||||
// GET
|
||||
router.NewGetRoute("/containers/json", c.getContainersJSON),
|
||||
router.NewGetRoute("/containers/{name:.*}/export", c.getContainersExport),
|
||||
router.NewGetRoute("/containers/{name:.*}/changes", c.getContainersChanges),
|
||||
router.NewGetRoute("/containers/{name:.*}/json", c.getContainersByName),
|
||||
router.NewGetRoute("/containers/{name:.*}/top", c.getContainersTop),
|
||||
router.NewGetRoute("/containers/{name:.*}/logs", c.getContainersLogs),
|
||||
router.NewGetRoute("/containers/{name:.*}/stats", c.getContainersStats),
|
||||
router.NewGetRoute("/containers/{name:.*}/attach/ws", c.wsContainersAttach),
|
||||
router.NewGetRoute("/exec/{id:.*}/json", c.getExecByID),
|
||||
router.NewGetRoute("/containers/{name:.*}/archive", c.getContainersArchive),
|
||||
// POST
|
||||
router.NewPostRoute("/containers/create", c.postContainersCreate),
|
||||
router.NewPostRoute("/containers/{name:.*}/kill", c.postContainersKill),
|
||||
router.NewPostRoute("/containers/{name:.*}/pause", c.postContainersPause),
|
||||
router.NewPostRoute("/containers/{name:.*}/unpause", c.postContainersUnpause),
|
||||
router.NewPostRoute("/containers/{name:.*}/restart", c.postContainersRestart),
|
||||
router.NewPostRoute("/containers/{name:.*}/start", c.postContainersStart),
|
||||
router.NewPostRoute("/containers/{name:.*}/stop", c.postContainersStop),
|
||||
router.NewPostRoute("/containers/{name:.*}/wait", c.postContainersWait),
|
||||
router.NewPostRoute("/containers/{name:.*}/resize", c.postContainersResize),
|
||||
router.NewPostRoute("/containers/{name:.*}/attach", c.postContainersAttach),
|
||||
router.NewPostRoute("/containers/{name:.*}/exec", c.postContainerExecCreate),
|
||||
router.NewPostRoute("/exec/{name:.*}/start", c.postContainerExecStart),
|
||||
router.NewPostRoute("/exec/{name:.*}/resize", c.postContainerExecResize),
|
||||
router.NewPostRoute("/containers/{name:.*}/rename", c.postContainerRename),
|
||||
router.NewPostRoute("/containers/{name:.*}/update", c.postContainerUpdate),
|
||||
router.NewPostRoute("/containers/prune", c.postContainersPrune),
|
||||
router.NewPostRoute("/commit", c.postCommit),
|
||||
// PUT
|
||||
router.NewPutRoute("/containers/{name:.*}/archive", c.putContainersArchive),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/containers/{name:.*}", c.deleteContainers),
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,55 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/runconfig"
|
||||
"github.com/moby/moby/v2/daemon/internal/versions"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
|
||||
networkSettings "github.com/moby/moby/v2/daemon/network"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httpstatus"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/moby/moby/v2/pkg/ioutils"
|
||||
"github.com/docker/docker/api/server/httpstatus"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
networkSettings "github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/runconfig"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
// commitRequest may contain an optional [container.Config].
|
||||
type commitRequest struct {
|
||||
*container.Config
|
||||
}
|
||||
|
||||
// decodeCommitRequest decodes the request body and returns a [container.Config]
|
||||
// if present, or nil otherwise. It returns an error when failing to decode
|
||||
// due to invalid JSON, or if the request contains multiple JSON documents.
|
||||
//
|
||||
// The client posts a bare [*container.Config] (see [Client.ContainerCommit]
|
||||
// and [container.CommitOptions]), but it may be empty / nil, in which case
|
||||
// it must be ignored, and no overrides to be applied.
|
||||
//
|
||||
// [Client.ContainerCommit]: https://github.com/moby/moby/blob/c4afa7715715a1020e50b19ad60728c4479fb0a5/client/container_commit.go#L52
|
||||
// [container.CommitOptions]: https://github.com/moby/moby/blob/c4afa7715715a1020e50b19ad60728c4479fb0a5/api/types/container/options.go#L30
|
||||
func decodeCommitRequest(r *http.Request) (*container.Config, error) {
|
||||
var w commitRequest
|
||||
if err := httputils.ReadJSON(r, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.Config, nil
|
||||
}
|
||||
|
||||
func (c *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := decodeCommitRequest(r)
|
||||
if err != nil {
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME(thaJeztah): change this to unmarshal just [container.Config]:
|
||||
// The commit endpoint accepts a [container.Config], but the decoder uses a
|
||||
// [container.CreateRequest], which is a superset, and also contains
|
||||
// [container.HostConfig] and [network.NetworkConfig]. Those structs
|
||||
// are discarded here, but decoder.DecodeConfig also performs validation,
|
||||
// so a request containing those additional fields would result in a
|
||||
// validation error.
|
||||
config, _, _, err := c.decoder.DecodeConfig(r.Body)
|
||||
if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -72,13 +58,8 @@ func (c *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter,
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
var noPause bool
|
||||
if r.Form.Has("pause") && !httputils.BoolValue(r, "pause") {
|
||||
noPause = true
|
||||
}
|
||||
|
||||
imgID, err := c.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{
|
||||
NoPause: noPause,
|
||||
Pause: httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend
|
||||
Tag: ref,
|
||||
Author: r.Form.Get("author"),
|
||||
Comment: r.Form.Get("comment"),
|
||||
@@ -101,23 +82,23 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
|
||||
return err
|
||||
}
|
||||
|
||||
var limit int
|
||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
||||
val, err := strconv.Atoi(tmpLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
limit = val
|
||||
}
|
||||
|
||||
containers, err := c.backend.Containers(ctx, &backend.ContainerListOptions{
|
||||
config := &container.ListOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Size: httputils.BoolValue(r, "size"),
|
||||
Since: r.Form.Get("since"),
|
||||
Before: r.Form.Get("before"),
|
||||
Limit: limit,
|
||||
Filters: filter,
|
||||
})
|
||||
}
|
||||
|
||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
||||
limit, err := strconv.Atoi(tmpLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Limit = limit
|
||||
}
|
||||
|
||||
containers, err := c.backend.Containers(ctx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,22 +106,16 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
if versions.LessThan(version, "1.46") {
|
||||
for i := range containers {
|
||||
for _, c := range containers {
|
||||
// Ignore HostConfig.Annotations because it was added in API v1.46.
|
||||
containers[i].HostConfig.Annotations = nil
|
||||
c.HostConfig.Annotations = nil
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.48") {
|
||||
// ImageManifestDescriptor information was added in API 1.48
|
||||
for i := range containers {
|
||||
containers[i].ImageManifestDescriptor = nil
|
||||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.52") {
|
||||
for i := range containers {
|
||||
containers[i].Health = nil
|
||||
for _, c := range containers {
|
||||
c.ImageManifestDescriptor = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +168,7 @@ func (c *containerRouter) getContainersLogs(ctx context.Context, w http.Response
|
||||
}
|
||||
|
||||
containerName := vars["name"]
|
||||
logsConfig := &backend.ContainerLogsOptions{
|
||||
logsConfig := &container.LogsOptions{
|
||||
Follow: httputils.BoolValue(r, "follow"),
|
||||
Timestamps: httputils.BoolValue(r, "timestamps"),
|
||||
Since: r.Form.Get("since"),
|
||||
@@ -261,7 +236,7 @@ func (c *containerRouter) postContainersStop(ctx context.Context, w http.Respons
|
||||
}
|
||||
|
||||
var (
|
||||
options backend.ContainerStopOptions
|
||||
options container.StopOptions
|
||||
version = httputils.VersionFromContext(ctx)
|
||||
)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
@@ -303,7 +278,7 @@ func (c *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
|
||||
}
|
||||
|
||||
var (
|
||||
options backend.ContainerStopOptions
|
||||
options container.StopOptions
|
||||
version = httputils.VersionFromContext(ctx)
|
||||
)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
@@ -467,6 +442,11 @@ func (c *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
|
||||
updateConfig.PidsLimit = nil
|
||||
}
|
||||
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
// Ignore KernelMemory removed in API 1.42.
|
||||
updateConfig.KernelMemory = 0
|
||||
}
|
||||
|
||||
if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
|
||||
// Both `0` and `-1` are accepted to set "unlimited" when updating.
|
||||
// Historically, any negative value was accepted, so treat them as
|
||||
@@ -499,18 +479,26 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
|
||||
name := r.Form.Get("name")
|
||||
|
||||
// Use a tee-reader to allow reading the body for legacy fields.
|
||||
var requestBody bytes.Buffer
|
||||
rdr := io.TeeReader(r.Body, &requestBody)
|
||||
|
||||
// TODO(thaJeztah): do we prefer [backend.ContainerCreateConfig] here?
|
||||
req, err := runconfig.DecodeCreateRequest(rdr, c.backend.RawSysInfo())
|
||||
config, hostConfig, networkingConfig, err := c.decoder.DecodeConfig(r.Body)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
|
||||
}
|
||||
return err
|
||||
}
|
||||
// TODO(thaJeztah): update code below to take [container.CreateRequest] or [backend.ContainerCreateConfig] directly.
|
||||
config, hostConfig, networkingConfig := req.Config, req.HostConfig, req.NetworkingConfig
|
||||
|
||||
if config == nil {
|
||||
return errdefs.InvalidParameter(runconfig.ErrEmptyConfig)
|
||||
}
|
||||
if hostConfig == nil {
|
||||
hostConfig = &container.HostConfig{}
|
||||
}
|
||||
if networkingConfig == nil {
|
||||
networkingConfig = &network.NetworkingConfig{}
|
||||
}
|
||||
if networkingConfig.EndpointsConfig == nil {
|
||||
networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings)
|
||||
}
|
||||
// The NetworkMode "default" is used as a way to express a container should
|
||||
// be attached to the OS-dependant default network, in an OS-independent
|
||||
// way. Doing this conversion as soon as possible ensures we have less
|
||||
@@ -529,6 +517,11 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
// When using API 1.24 and under, the client is responsible for removing the container
|
||||
if versions.LessThan(version, "1.25") {
|
||||
hostConfig.AutoRemove = false
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.40") {
|
||||
// Ignore BindOptions.NonRecursive because it was added in API 1.40.
|
||||
for _, m := range hostConfig.Mounts {
|
||||
@@ -537,6 +530,9 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore KernelMemoryTCP because it was added in API 1.40.
|
||||
hostConfig.KernelMemoryTCP = 0
|
||||
|
||||
// Older clients (API < 1.40) expects the default to be shareable, make them happy
|
||||
if hostConfig.IpcMode.IsEmpty() {
|
||||
hostConfig.IpcMode = container.IPCModeShareable
|
||||
@@ -545,7 +541,7 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
|
||||
if versions.LessThan(version, "1.41") {
|
||||
// Older clients expect the default to be "host" on cgroup v1 hosts
|
||||
if hostConfig.CgroupnsMode.IsEmpty() && !c.backend.RawSysInfo().CgroupUnified {
|
||||
if !c.cgroup2 && hostConfig.CgroupnsMode.IsEmpty() {
|
||||
hostConfig.CgroupnsMode = container.CgroupnsModeHost
|
||||
}
|
||||
}
|
||||
@@ -589,6 +585,8 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
}
|
||||
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
// Ignore KernelMemory removed in API 1.42.
|
||||
hostConfig.KernelMemory = 0
|
||||
for _, m := range hostConfig.Mounts {
|
||||
if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume {
|
||||
return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type))
|
||||
@@ -668,28 +666,15 @@ func (c *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
if warn := handleVolumeDriverBC(version, hostConfig); warn != "" {
|
||||
warnings = append(warnings, warn)
|
||||
}
|
||||
if versions.LessThan(version, "1.52") {
|
||||
var legacyConfig struct {
|
||||
// Mac Address of the container.
|
||||
//
|
||||
// MacAddress field is deprecated since API v1.44. Use EndpointSettings.MacAddress instead.
|
||||
MacAddress network.HardwareAddr `json:",omitempty"`
|
||||
}
|
||||
_ = json.Unmarshal(requestBody.Bytes(), &legacyConfig)
|
||||
if warn, err := handleMACAddressBC(hostConfig, networkingConfig, version, legacyConfig.MacAddress); err != nil {
|
||||
return err
|
||||
} else if warn != "" {
|
||||
warnings = append(warnings, warn)
|
||||
}
|
||||
}
|
||||
|
||||
if warn, err := handleSysctlBC(hostConfig, networkingConfig, version); err != nil {
|
||||
if warn, err := handleMACAddressBC(config, hostConfig, networkingConfig, version); err != nil {
|
||||
return err
|
||||
} else if warn != "" {
|
||||
warnings = append(warnings, warn)
|
||||
}
|
||||
|
||||
if warn := handlePortBindingsBC(hostConfig, version); warn != "" {
|
||||
if warn, err := handleSysctlBC(hostConfig, networkingConfig, version); err != nil {
|
||||
return err
|
||||
} else if warn != "" {
|
||||
warnings = append(warnings, warn)
|
||||
}
|
||||
|
||||
@@ -746,19 +731,21 @@ func handleVolumeDriverBC(version string, hostConfig *container.HostConfig) (war
|
||||
// handleMACAddressBC takes care of backward-compatibility for the container-wide MAC address by mutating the
|
||||
// networkingConfig to set the endpoint-specific MACAddress field introduced in API v1.44. It returns a warning message
|
||||
// or an error if the container-wide field was specified for API >= v1.44.
|
||||
func handleMACAddressBC(hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, version string, deprecatedMacAddress network.HardwareAddr) (string, error) {
|
||||
func handleMACAddressBC(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, version string) (string, error) {
|
||||
deprecatedMacAddress := config.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
|
||||
// For older versions of the API, migrate the container-wide MAC address to EndpointsConfig.
|
||||
if versions.LessThan(version, "1.44") {
|
||||
if len(deprecatedMacAddress) == 0 {
|
||||
if deprecatedMacAddress == "" {
|
||||
// If a MAC address is supplied in EndpointsConfig, discard it because the old API
|
||||
// would have ignored it.
|
||||
for _, ep := range networkingConfig.EndpointsConfig {
|
||||
ep.MacAddress = nil
|
||||
ep.MacAddress = ""
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
if !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() {
|
||||
return "", errdefs.InvalidParameter(errors.New("conflicting options: mac-address and the network mode"))
|
||||
return "", runconfig.ErrConflictContainerNetworkAndMac
|
||||
}
|
||||
|
||||
epConfig, err := epConfigForNetMode(version, hostConfig.NetworkMode, networkingConfig)
|
||||
@@ -770,14 +757,9 @@ func handleMACAddressBC(hostConfig *container.HostConfig, networkingConfig *netw
|
||||
}
|
||||
|
||||
// The container-wide MacAddress parameter is deprecated and should now be specified in EndpointsConfig.
|
||||
if len(deprecatedMacAddress) == 0 {
|
||||
if deprecatedMacAddress == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if versions.GreaterThanOrEqualTo(version, "1.52") {
|
||||
return "", errdefs.InvalidParameter(errors.New("container-wide MAC address no longer supported; use endpoint-specific MAC address instead"))
|
||||
}
|
||||
|
||||
var warning string
|
||||
if hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() {
|
||||
ep, err := epConfigForNetMode(version, hostConfig.NetworkMode, networkingConfig)
|
||||
@@ -786,13 +768,14 @@ func handleMACAddressBC(hostConfig *container.HostConfig, networkingConfig *netw
|
||||
}
|
||||
// ep is the endpoint that needs the container-wide MAC address; migrate the address
|
||||
// to it, or bail out if there's a mismatch.
|
||||
if len(ep.MacAddress) == 0 {
|
||||
if ep.MacAddress == "" {
|
||||
ep.MacAddress = deprecatedMacAddress
|
||||
} else if !slices.Equal(ep.MacAddress, deprecatedMacAddress) {
|
||||
} else if ep.MacAddress != deprecatedMacAddress {
|
||||
return "", errdefs.InvalidParameter(errors.New("the container-wide MAC address must match the endpoint-specific MAC address for the main network, or be left empty"))
|
||||
}
|
||||
}
|
||||
warning = "The container-wide MacAddress field is now deprecated. It should be specified in EndpointsConfig instead."
|
||||
config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
|
||||
return warning, nil
|
||||
}
|
||||
@@ -885,56 +868,6 @@ func handleSysctlBC(
|
||||
return warning, nil
|
||||
}
|
||||
|
||||
// handlePortBindingsBC handles backward-compatibility for empty port bindings.
|
||||
//
|
||||
// Before Engine v29.0, an empty list of port bindings for a container port was
|
||||
// treated as if a PortBinding with an unspecified IP address and HostPort was
|
||||
// provided. The daemon was doing this backfilling on ContainerStart.
|
||||
//
|
||||
// Preserve this behavior for older API versions but emit a warning for API
|
||||
// v1.52 and drop that behavior for newer API versions.
|
||||
//
|
||||
// See https://github.com/moby/moby/pull/50710#discussion_r2315840899 for more
|
||||
// context.
|
||||
func handlePortBindingsBC(hostConfig *container.HostConfig, version string) string {
|
||||
var emptyPBs []string
|
||||
|
||||
for port, bindings := range hostConfig.PortBindings {
|
||||
if len(bindings) > 0 {
|
||||
continue
|
||||
}
|
||||
/*
|
||||
API 1.53 shipped in a minor release. We cannot introduce a breaking change there, so
|
||||
we must still backfill empty port bindings. This change can be re-introduced for the
|
||||
API version that ships in 30.x. Note that some networking tests will need fixing.
|
||||
See https://github.com/moby/moby/issues/51727
|
||||
|
||||
if versions.GreaterThan(version, "1.52") && len(bindings) == 0 {
|
||||
// Starting with API 1.53, no backfilling is done. An empty slice
|
||||
// of port bindings is treated as "no port bindings" by the daemon,
|
||||
// but it still needs to backfill empty slices when loading the
|
||||
// on-disk state for containers created by older versions of the
|
||||
// Engine. Drop the PortBindings entry to ensure that no backfilling
|
||||
// will happen when restarting the daemon.
|
||||
delete(hostConfig.PortBindings, port)
|
||||
continue
|
||||
}
|
||||
*/
|
||||
|
||||
if versions.GreaterThanOrEqualTo(version, "1.52") {
|
||||
emptyPBs = append(emptyPBs, port.String())
|
||||
}
|
||||
|
||||
hostConfig.PortBindings[port] = []network.PortBinding{{}}
|
||||
}
|
||||
|
||||
if len(emptyPBs) > 0 {
|
||||
return fmt.Sprintf("Following container port(s) have an empty list of port-bindings: %s. Such bindings will be discarded in a future version.", strings.Join(emptyPBs, ", "))
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// epConfigForNetMode finds, or creates, an entry in netConfig.EndpointsConfig
|
||||
// corresponding to nwMode.
|
||||
//
|
||||
@@ -1167,7 +1100,7 @@ func (c *containerRouter) postContainersPrune(ctx context.Context, w http.Respon
|
||||
return err
|
||||
}
|
||||
|
||||
pruneReport, err := c.backend.ContainerPrune(ctx, pruneFilters)
|
||||
pruneReport, err := c.backend.ContainersPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@@ -17,119 +15,114 @@ func TestHandleMACAddressBC(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
apiVersion string
|
||||
ctrWideMAC network.HardwareAddr
|
||||
ctrWideMAC string
|
||||
networkMode container.NetworkMode
|
||||
epConfig map[string]*network.EndpointSettings
|
||||
expEpWithCtrWideMAC string
|
||||
expEpWithNoMAC string
|
||||
expCtrWideMAC network.HardwareAddr
|
||||
expCtrWideMAC string
|
||||
expWarning string
|
||||
expError string
|
||||
}{
|
||||
{
|
||||
name: "old api ctr-wide mac mix id and name",
|
||||
apiVersion: "1.43",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api clear ep mac",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}},
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "11:22:33:44:55:66"}},
|
||||
expEpWithNoMAC: "aNetName",
|
||||
},
|
||||
{
|
||||
name: "old api no-network ctr-wide mac",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "none",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
expError: "conflicting options: mac-address and the network mode",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api create ep",
|
||||
apiVersion: "1.43",
|
||||
networkMode: "aNetId",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
epConfig: map[string]*network.EndpointSettings{},
|
||||
expEpWithCtrWideMAC: "aNetId",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "old api migrate ctr-wide mac",
|
||||
apiVersion: "1.43",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "api 1.44 no macs",
|
||||
name: "new api no macs",
|
||||
apiVersion: "1.44",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
},
|
||||
{
|
||||
name: "api 1.44 ep specific mac",
|
||||
name: "new api ep specific mac",
|
||||
apiVersion: "1.44",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}},
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "11:22:33:44:55:66"}},
|
||||
},
|
||||
{
|
||||
name: "api 1.44 migrate ctr-wide mac to new ep",
|
||||
name: "new api migrate ctr-wide mac to new ep",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expWarning: "The container-wide MacAddress field is now deprecated",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "",
|
||||
},
|
||||
{
|
||||
name: "api 1.44 migrate ctr-wide mac to existing ep",
|
||||
name: "new api migrate ctr-wide mac to existing ep",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expEpWithCtrWideMAC: "aNetName",
|
||||
expWarning: "The container-wide MacAddress field is now deprecated",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "",
|
||||
},
|
||||
{
|
||||
name: "api 1.44 mode vs name mismatch",
|
||||
name: "new api mode vs name mismatch",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetId",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {}},
|
||||
expError: "unable to migrate container-wide MAC address to a specific network: HostConfig.NetworkMode must match the identity of a network in NetworkSettings.Networks",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
{
|
||||
name: "api 1.44 mac mismatch",
|
||||
name: "new api mac mismatch",
|
||||
apiVersion: "1.44",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
ctrWideMAC: "11:22:33:44:55:66",
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: network.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}},
|
||||
epConfig: map[string]*network.EndpointSettings{"aNetName": {MacAddress: "00:11:22:33:44:55"}},
|
||||
expError: "the container-wide MAC address must match the endpoint-specific MAC address",
|
||||
expCtrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
},
|
||||
{
|
||||
name: "api 1.52 reject ctr-wide mac",
|
||||
apiVersion: "1.52",
|
||||
ctrWideMAC: network.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
networkMode: "aNetName",
|
||||
epConfig: map[string]*network.EndpointSettings{},
|
||||
expError: "container-wide MAC address no longer supported; use endpoint-specific MAC address instead",
|
||||
expCtrWideMAC: "11:22:33:44:55:66",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
MacAddress: tc.ctrWideMAC, //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
NetworkMode: tc.networkMode,
|
||||
}
|
||||
@@ -142,7 +135,7 @@ func TestHandleMACAddressBC(t *testing.T) {
|
||||
EndpointsConfig: epConfig,
|
||||
}
|
||||
|
||||
warning, err := handleMACAddressBC(hostCfg, netCfg, tc.apiVersion, tc.ctrWideMAC)
|
||||
warning, err := handleMACAddressBC(cfg, hostCfg, netCfg, tc.apiVersion)
|
||||
|
||||
if tc.expError == "" {
|
||||
assert.Check(t, err)
|
||||
@@ -156,12 +149,14 @@ func TestHandleMACAddressBC(t *testing.T) {
|
||||
}
|
||||
if tc.expEpWithCtrWideMAC != "" {
|
||||
got := netCfg.EndpointsConfig[tc.expEpWithCtrWideMAC].MacAddress
|
||||
assert.Check(t, is.DeepEqual(got, tc.expCtrWideMAC, cmpopts.EquateEmpty()))
|
||||
assert.Check(t, is.Equal(got, tc.ctrWideMAC))
|
||||
}
|
||||
if tc.expEpWithNoMAC != "" {
|
||||
got := netCfg.EndpointsConfig[tc.expEpWithNoMAC].MacAddress
|
||||
assert.Check(t, is.DeepEqual(got, network.HardwareAddr{}, cmpopts.EquateEmpty()))
|
||||
assert.Check(t, is.Equal(got, ""))
|
||||
}
|
||||
gotCtrWideMAC := cfg.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
|
||||
assert.Check(t, is.Equal(gotCtrWideMAC, tc.expCtrWideMAC))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -316,7 +311,9 @@ func TestHandleSysctlBC(t *testing.T) {
|
||||
NetworkMode: container.NetworkMode(tc.networkMode),
|
||||
Sysctls: map[string]string{},
|
||||
}
|
||||
maps.Copy(hostCfg.Sysctls, tc.sysctls)
|
||||
for k, v := range tc.sysctls {
|
||||
hostCfg.Sysctls[k] = v
|
||||
}
|
||||
netCfg := &network.NetworkingConfig{
|
||||
EndpointsConfig: tc.epConfig,
|
||||
}
|
||||
99
api/server/router/container/copy.go
Normal file
99
api/server/router/container/copy.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
gddohttputil "github.com/golang/gddo/httputil"
|
||||
)
|
||||
|
||||
// setContainerPathStatHeader encodes the stat to JSON, base64 encode, and place in a header.
|
||||
func setContainerPathStatHeader(stat *container.PathStat, header http.Header) error {
|
||||
statJSON, err := json.Marshal(stat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Set(
|
||||
"X-Docker-Container-Path-Stat",
|
||||
base64.StdEncoding.EncodeToString(statJSON),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *containerRouter) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := c.backend.ContainerStatPath(v.Name, v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setContainerPathStatHeader(stat, w.Header())
|
||||
}
|
||||
|
||||
func writeCompressedResponse(w http.ResponseWriter, r *http.Request, body io.Reader) error {
|
||||
var cw io.Writer
|
||||
switch gddohttputil.NegotiateContentEncoding(r, []string{"gzip", "deflate"}) {
|
||||
case "gzip":
|
||||
gw := gzip.NewWriter(w)
|
||||
defer gw.Close()
|
||||
cw = gw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
case "deflate":
|
||||
fw, err := flate.NewWriter(w, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fw.Close()
|
||||
cw = fw
|
||||
w.Header().Set("Content-Encoding", "deflate")
|
||||
default:
|
||||
cw = w
|
||||
}
|
||||
_, err := io.Copy(cw, body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *containerRouter) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarArchive, stat, err := c.backend.ContainerArchivePath(v.Name, v.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tarArchive.Close()
|
||||
|
||||
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
return writeCompressedResponse(w, r, tarArchive)
|
||||
}
|
||||
|
||||
func (c *containerRouter) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir")
|
||||
copyUIDGID := httputils.BoolValue(r, "copyUIDGID")
|
||||
|
||||
return c.backend.ContainerExtractToDir(v.Name, v.Path, copyUIDGID, noOverwriteDirNonDir, r.Body)
|
||||
}
|
||||
171
api/server/router/container/exec.go
Normal file
171
api/server/router/container/exec.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *containerRouter) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
eConfig, err := c.backend.ContainerExecInspect(vars["id"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, eConfig)
|
||||
}
|
||||
|
||||
type execCommandError struct{}
|
||||
|
||||
func (execCommandError) Error() string {
|
||||
return "No exec command specified"
|
||||
}
|
||||
|
||||
func (execCommandError) InvalidParameter() {}
|
||||
|
||||
func (c *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execConfig := &container.ExecOptions{}
|
||||
if err := httputils.ReadJSON(r, execConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(execConfig.Cmd) == 0 {
|
||||
return execCommandError{}
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.42") {
|
||||
// Not supported by API versions before 1.42
|
||||
execConfig.ConsoleSize = nil
|
||||
}
|
||||
|
||||
// Register an instance of Exec in container.
|
||||
id, err := c.backend.ContainerExecCreate(vars["name"], execConfig)
|
||||
if err != nil {
|
||||
log.G(ctx).Errorf("Error setting up exec command in container %s: %v", vars["name"], err)
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusCreated, &container.ExecCreateResponse{
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
||||
func (c *containerRouter) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
execName = vars["name"]
|
||||
stdin, inStream io.ReadCloser
|
||||
stdout, stderr, outStream io.Writer
|
||||
)
|
||||
|
||||
options := &container.ExecStartOptions{}
|
||||
if err := httputils.ReadJSON(r, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists, err := c.backend.ExecExists(execName); !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.ConsoleSize != nil {
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
|
||||
// Not supported before 1.42
|
||||
if versions.LessThan(version, "1.42") {
|
||||
options.ConsoleSize = nil
|
||||
}
|
||||
|
||||
// No console without tty
|
||||
if !options.Tty {
|
||||
options.ConsoleSize = nil
|
||||
}
|
||||
}
|
||||
|
||||
if !options.Detach {
|
||||
var err error
|
||||
// Setting up the streaming http interface.
|
||||
inStream, outStream, err = httputils.HijackConnection(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer httputils.CloseStreams(inStream, outStream)
|
||||
|
||||
if _, ok := r.Header["Upgrade"]; ok {
|
||||
contentType := types.MediaTypeRawStream
|
||||
if !options.Tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") {
|
||||
contentType = types.MediaTypeMultiplexedStream
|
||||
}
|
||||
_, _ = fmt.Fprint(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n")
|
||||
} else {
|
||||
_, _ = fmt.Fprint(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n")
|
||||
}
|
||||
|
||||
// copy headers that were removed as part of hijack
|
||||
if err := w.Header().WriteSubset(outStream, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprint(outStream, "\r\n")
|
||||
|
||||
stdin = inStream
|
||||
if options.Tty {
|
||||
stdout = outStream
|
||||
} else {
|
||||
stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
// Now run the user process in container.
|
||||
//
|
||||
// TODO: Maybe we should we pass ctx here if we're not detaching?
|
||||
err := c.backend.ContainerExecStart(context.Background(), execName, backend.ExecStartConfig{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
ConsoleSize: options.ConsoleSize,
|
||||
})
|
||||
if err != nil {
|
||||
if options.Detach {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(stdout, "%v\r\n", err)
|
||||
log.G(ctx).Errorf("Error running exec %s in container: %v", execName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *containerRouter) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
height, err := httputils.Uint32Value(r, "h")
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(errors.Wrapf(err, "invalid resize height %q", r.Form.Get("h")))
|
||||
}
|
||||
width, err := httputils.Uint32Value(r, "w")
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(errors.Wrapf(err, "invalid resize width %q", r.Form.Get("w")))
|
||||
}
|
||||
|
||||
return c.backend.ContainerExecResize(ctx, vars["name"], height, width)
|
||||
}
|
||||
41
api/server/router/container/inspect.go
Normal file
41
api/server/router/container/inspect.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.23
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/internal/sliceutil"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
// getContainersByName inspects container's configuration and serializes it as json.
|
||||
func (c *containerRouter) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
ctr, err := c.backend.ContainerInspect(ctx, vars["name"], backend.ContainerInspectOptions{
|
||||
Size: httputils.BoolValue(r, "size"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.45") {
|
||||
shortCID := stringid.TruncateID(ctr.ID)
|
||||
for nwName, ep := range ctr.NetworkSettings.Networks {
|
||||
if container.NetworkMode(nwName).IsUserDefined() {
|
||||
ep.Aliases = sliceutil.Dedup(append(ep.Aliases, shortCID, ctr.Config.Hostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
if versions.LessThan(version, "1.48") {
|
||||
ctr.ImageManifestDescriptor = nil
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, ctr)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/moby/moby/v2/daemon/internal/unix_noeintr"
|
||||
"github.com/docker/docker/internal/unix_noeintr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
53
api/server/router/debug/debug.go
Normal file
53
api/server/router/debug/debug.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"expvar"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// NewRouter creates a new debug router
|
||||
// The debug router holds endpoints for debug the daemon, such as those for pprof.
|
||||
func NewRouter() router.Router {
|
||||
r := &debugRouter{}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
type debugRouter struct {
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
func (r *debugRouter) initRoutes() {
|
||||
r.routes = []router.Route{
|
||||
router.NewGetRoute("/debug/vars", frameworkAdaptHandler(expvar.Handler())),
|
||||
router.NewGetRoute("/debug/pprof/", frameworkAdaptHandlerFunc(pprof.Index)),
|
||||
router.NewGetRoute("/debug/pprof/cmdline", frameworkAdaptHandlerFunc(pprof.Cmdline)),
|
||||
router.NewGetRoute("/debug/pprof/profile", frameworkAdaptHandlerFunc(pprof.Profile)),
|
||||
router.NewGetRoute("/debug/pprof/symbol", frameworkAdaptHandlerFunc(pprof.Symbol)),
|
||||
router.NewGetRoute("/debug/pprof/trace", frameworkAdaptHandlerFunc(pprof.Trace)),
|
||||
router.NewGetRoute("/debug/pprof/{name}", handlePprof),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *debugRouter) Routes() []router.Route {
|
||||
return r.routes
|
||||
}
|
||||
|
||||
func frameworkAdaptHandler(handler http.Handler) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
handler.ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func frameworkAdaptHandlerFunc(handler http.HandlerFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
handler(w, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
15
api/server/router/distribution/backend.go
Normal file
15
api/server/router/distribution/backend.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package distribution
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide image specific functionality.
|
||||
type Backend interface {
|
||||
GetRepositories(context.Context, reference.Named, *registry.AuthConfig) ([]distribution.Repository, error)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package distribution
|
||||
|
||||
import "github.com/moby/moby/v2/daemon/server/router"
|
||||
import "github.com/docker/docker/api/server/router"
|
||||
|
||||
// distributionRouter is a router to talk with the registry
|
||||
type distributionRouter struct {
|
||||
@@ -26,6 +26,6 @@ func (dr *distributionRouter) Routes() []router.Route {
|
||||
func (dr *distributionRouter) initRoutes() {
|
||||
dr.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/distribution/{name:.*}/json", dr.getDistributionInfo, router.WithMinimumAPIVersion("1.30")),
|
||||
router.NewGetRoute("/distribution/{name:.*}/json", dr.getDistributionInfo),
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/moby/moby/api/pkg/authconfig"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
distributionpkg "github.com/moby/moby/v2/daemon/internal/distribution"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
distributionpkg "github.com/docker/docker/distribution"
|
||||
"github.com/docker/docker/errdefs"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -43,7 +43,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
|
||||
// For a search it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := authconfig.Decode(r.Header.Get(registry.AuthHeader))
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
repos, err := dr.backend.GetRepositories(ctx, namedRef, authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -65,7 +65,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L76-L152
|
||||
var lastErr error
|
||||
for _, repo := range repos {
|
||||
distributionInspect, err := fetchManifest(ctx, repo, namedRef)
|
||||
distributionInspect, err := dr.fetchManifest(ctx, repo, namedRef)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
@@ -75,7 +75,7 @@ func (dr *distributionRouter) getDistributionInfo(ctx context.Context, w http.Re
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
|
||||
func (dr *distributionRouter) fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
|
||||
var distributionInspect registry.DistributionInspect
|
||||
if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
|
||||
namedRef = reference.TagNameOnly(namedRef)
|
||||
@@ -125,11 +125,6 @@ func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedR
|
||||
if err != nil {
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
switch mediaType {
|
||||
case distributionpkg.MediaTypeDockerSchema1Manifest, distributionpkg.MediaTypeDockerSchema1SignedManifest:
|
||||
return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef)
|
||||
}
|
||||
|
||||
// update MediaType because registry might return something incorrect
|
||||
distributionInspect.Descriptor.MediaType = mediaType
|
||||
if distributionInspect.Descriptor.Size == 0 {
|
||||
@@ -158,6 +153,10 @@ func fetchManifest(ctx context.Context, distrepo distribution.Repository, namedR
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(thaJeztah); we only use this to produce a nice error, but as a result, we can't remove libtrust as dependency - see if we can reduce the dependencies, but still able to detect it's a deprecated manifest
|
||||
case *schema1.SignedManifest:
|
||||
return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef)
|
||||
}
|
||||
return distributionInspect, nil
|
||||
}
|
||||
68
api/server/router/experimental.go
Normal file
68
api/server/router/experimental.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
)
|
||||
|
||||
// ExperimentalRoute defines an experimental API route that can be enabled or disabled.
|
||||
type ExperimentalRoute interface {
|
||||
Route
|
||||
|
||||
Enable()
|
||||
Disable()
|
||||
}
|
||||
|
||||
// experimentalRoute defines an experimental API route that can be enabled or disabled.
|
||||
// It implements ExperimentalRoute
|
||||
type experimentalRoute struct {
|
||||
local Route
|
||||
handler httputils.APIFunc
|
||||
}
|
||||
|
||||
// Enable enables this experimental route
|
||||
func (r *experimentalRoute) Enable() {
|
||||
r.handler = r.local.Handler()
|
||||
}
|
||||
|
||||
// Disable disables the experimental route
|
||||
func (r *experimentalRoute) Disable() {
|
||||
r.handler = experimentalHandler
|
||||
}
|
||||
|
||||
type notImplementedError struct{}
|
||||
|
||||
func (notImplementedError) Error() string {
|
||||
return "This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it."
|
||||
}
|
||||
|
||||
func (notImplementedError) NotImplemented() {}
|
||||
|
||||
func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return notImplementedError{}
|
||||
}
|
||||
|
||||
// Handler returns the APIFunc to let the server wrap it in middlewares.
|
||||
func (r *experimentalRoute) Handler() httputils.APIFunc {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
// Method returns the http method that the route responds to.
|
||||
func (r *experimentalRoute) Method() string {
|
||||
return r.local.Method()
|
||||
}
|
||||
|
||||
// Path returns the subpath where the route responds to.
|
||||
func (r *experimentalRoute) Path() string {
|
||||
return r.local.Path()
|
||||
}
|
||||
|
||||
// Experimental will mark a route as experimental.
|
||||
func Experimental(r Route) Route {
|
||||
return &experimentalRoute{
|
||||
local: r,
|
||||
handler: experimentalHandler,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// Package grpc provides the router for the /grpc endpoint.
|
||||
//
|
||||
// Deprecated: The /grpc endpoint is deprecated and will be removed in the next
|
||||
// major version. The Engine now properly supports HTTP/2 and h2c requests and can
|
||||
// serve gRPC without this endpoint. Clients should establish gRPC connections
|
||||
// directly over HTTP/2.
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.23
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
@@ -14,11 +11,11 @@ import (
|
||||
|
||||
"github.com/containerd/containerd/v2/defaults"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/internal/otelutil"
|
||||
"github.com/moby/buildkit/util/grpcerrors"
|
||||
"github.com/moby/buildkit/util/stack"
|
||||
"github.com/moby/buildkit/util/tracing"
|
||||
"github.com/moby/moby/v2/daemon/internal/otelutil"
|
||||
"github.com/moby/moby/v2/daemon/server/router"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
@@ -31,9 +28,6 @@ type grpcRouter struct {
|
||||
}
|
||||
|
||||
// NewRouter initializes a new grpc http router
|
||||
//
|
||||
// Deprecated: The /grpc endpoint is deprecated and will be removed in the next
|
||||
// major version. The Engine now properly supports HTTP/2 and h2c requests.
|
||||
func NewRouter(backends ...Backend) router.Router {
|
||||
tp, _ := otelutil.NewTracerProvider(context.Background(), false)
|
||||
opts := []grpc.ServerOption{
|
||||
47
api/server/router/image/backend.go
Normal file
47
api/server/router/image/backend.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide image specific functionality.
|
||||
type Backend interface {
|
||||
imageBackend
|
||||
importExportBackend
|
||||
registryBackend
|
||||
}
|
||||
|
||||
type imageBackend interface {
|
||||
ImageDelete(ctx context.Context, imageRef string, options image.RemoveOptions) ([]image.DeleteResponse, error)
|
||||
ImageHistory(ctx context.Context, imageName string, platform *ocispec.Platform) ([]*image.HistoryResponseItem, error)
|
||||
Images(ctx context.Context, opts image.ListOptions) ([]*image.Summary, error)
|
||||
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*dockerimage.Image, error)
|
||||
ImageInspect(ctx context.Context, refOrID string, options backend.ImageInspectOpts) (*image.InspectResponse, error)
|
||||
TagImage(ctx context.Context, id dockerimage.ID, newRef reference.Named) error
|
||||
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*image.PruneReport, error)
|
||||
}
|
||||
|
||||
type importExportBackend interface {
|
||||
LoadImage(ctx context.Context, inTar io.ReadCloser, platform *ocispec.Platform, outStream io.Writer, quiet bool) error
|
||||
ImportImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (dockerimage.ID, error)
|
||||
ExportImage(ctx context.Context, names []string, platform *ocispec.Platform, outStream io.Writer) error
|
||||
}
|
||||
|
||||
type registryBackend interface {
|
||||
PullImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PushImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
}
|
||||
|
||||
type Searcher interface {
|
||||
Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error)
|
||||
}
|
||||
48
api/server/router/image/image.go
Normal file
48
api/server/router/image/image.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// imageRouter is a router to talk with the image controller
|
||||
type imageRouter struct {
|
||||
backend Backend
|
||||
searcher Searcher
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new image router
|
||||
func NewRouter(backend Backend, searcher Searcher) router.Router {
|
||||
ir := &imageRouter{
|
||||
backend: backend,
|
||||
searcher: searcher,
|
||||
}
|
||||
ir.initRoutes()
|
||||
return ir
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the image controller
|
||||
func (ir *imageRouter) Routes() []router.Route {
|
||||
return ir.routes
|
||||
}
|
||||
|
||||
// initRoutes initializes the routes in the image router
|
||||
func (ir *imageRouter) initRoutes() {
|
||||
ir.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/images/json", ir.getImagesJSON),
|
||||
router.NewGetRoute("/images/search", ir.getImagesSearch),
|
||||
router.NewGetRoute("/images/get", ir.getImagesGet),
|
||||
router.NewGetRoute("/images/{name:.*}/get", ir.getImagesGet),
|
||||
router.NewGetRoute("/images/{name:.*}/history", ir.getImagesHistory),
|
||||
router.NewGetRoute("/images/{name:.*}/json", ir.getImagesByName),
|
||||
// POST
|
||||
router.NewPostRoute("/images/load", ir.postImagesLoad),
|
||||
router.NewPostRoute("/images/create", ir.postImagesCreate),
|
||||
router.NewPostRoute("/images/{name:.*}/push", ir.postImagesPush),
|
||||
router.NewPostRoute("/images/{name:.*}/tag", ir.postImagesTag),
|
||||
router.NewPostRoute("/images/prune", ir.postImagesPrune),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/images/{name:.*}", ir.deleteImages),
|
||||
}
|
||||
}
|
||||
599
api/server/router/image/image_routes.go
Normal file
599
api/server/router/image/image_routes.go
Normal file
@@ -0,0 +1,599 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/builder/remotecontext"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Creates an image from Pull or from Import
|
||||
func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
img = r.Form.Get("fromImage")
|
||||
repo = r.Form.Get("repo")
|
||||
tag = r.Form.Get("tag")
|
||||
comment = r.Form.Get("message")
|
||||
progressErr error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
platform *ocispec.Platform
|
||||
)
|
||||
defer output.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.32") {
|
||||
if p := r.FormValue("platform"); p != "" {
|
||||
sp, err := platforms.Parse(p)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
platform = &sp
|
||||
}
|
||||
}
|
||||
|
||||
if img != "" { // pull
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: "pull -a" may send an image name with a
|
||||
// trailing :. This is ugly, but let's not break API
|
||||
// compatibility.
|
||||
imgName := strings.TrimSuffix(img, ":")
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(imgName)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
// TODO(thaJeztah) this could use a WithTagOrDigest() utility
|
||||
if tag != "" {
|
||||
// The "tag" could actually be a digest.
|
||||
var dgst digest.Digest
|
||||
dgst, err = digest.Parse(tag)
|
||||
if err == nil {
|
||||
ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst)
|
||||
} else {
|
||||
ref, err = reference.WithTag(ref, tag)
|
||||
}
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateRepoName(ref); err != nil {
|
||||
return errdefs.Forbidden(err)
|
||||
}
|
||||
|
||||
// For a pull it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
//
|
||||
// TODO(thaJeztah): accept empty values but return an error when failing to decode.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
progressErr = ir.backend.PullImage(ctx, ref, platform, metaHeaders, authConfig, output)
|
||||
} else { // import
|
||||
src := r.Form.Get("fromSrc")
|
||||
|
||||
tagRef, err := httputils.RepoTagReference(repo, tag)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
comment = "Imported from " + src
|
||||
}
|
||||
|
||||
var layerReader io.ReadCloser
|
||||
defer r.Body.Close()
|
||||
if src == "-" {
|
||||
layerReader = r.Body
|
||||
} else {
|
||||
if len(strings.Split(src, "://")) == 1 {
|
||||
src = "http://" + src
|
||||
}
|
||||
u, err := url.Parse(src)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
resp, err := remotecontext.GetWithStatusError(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output.Write(streamformatter.FormatStatus("", "Downloading from %s", u))
|
||||
progressOutput := streamformatter.NewJSONProgressOutput(output, true)
|
||||
layerReader = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing")
|
||||
defer layerReader.Close()
|
||||
}
|
||||
|
||||
var id image.ID
|
||||
id, progressErr = ir.backend.ImportImage(ctx, tagRef, platform, comment, layerReader, r.Form["changes"])
|
||||
|
||||
if progressErr == nil {
|
||||
_, _ = output.Write(streamformatter.FormatStatus("", "%v", id.String()))
|
||||
}
|
||||
}
|
||||
if progressErr != nil {
|
||||
if !output.Flushed() {
|
||||
return progressErr
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(progressErr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
metaHeaders := map[string][]string{}
|
||||
for k, v := range r.Header {
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
metaHeaders[k] = v
|
||||
}
|
||||
}
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var authConfig *registry.AuthConfig
|
||||
if authEncoded := r.Header.Get(registry.AuthHeader); authEncoded != "" {
|
||||
// Handle the authConfig as a header, but ignore invalid AuthConfig
|
||||
// to increase compatibility with the existing API.
|
||||
//
|
||||
// TODO(thaJeztah): accept empty values but return an error when failing to decode.
|
||||
authConfig, _ = registry.DecodeAuthConfig(authEncoded)
|
||||
}
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
img := vars["name"]
|
||||
tag := r.Form.Get("tag")
|
||||
|
||||
var ref reference.Named
|
||||
|
||||
// Tag is empty only in case PushOptions.All is true.
|
||||
if tag != "" {
|
||||
r, err := httputils.RepoTagReference(img, tag)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
ref = r
|
||||
} else {
|
||||
r, err := reference.ParseNormalizedNamed(img)
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
ref = r
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
// Platform is optional, and only supported in API version 1.46 and later.
|
||||
// However the PushOptions struct previously was an alias for the PullOptions struct
|
||||
// which also contained a Platform field.
|
||||
// This means that older clients may be sending a platform field, even
|
||||
// though it wasn't really supported by the server.
|
||||
// Don't break these clients and just ignore the platform field on older APIs.
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.46") {
|
||||
if formPlatform := r.Form.Get("platform"); formPlatform != "" {
|
||||
p, err := httputils.DecodePlatform(formPlatform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platform = p
|
||||
}
|
||||
}
|
||||
|
||||
if err := ir.backend.PushImage(ctx, ref, platform, metaHeaders, authConfig, output); err != nil {
|
||||
if !output.Flushed() {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
var names []string
|
||||
if name, ok := vars["name"]; ok {
|
||||
names = []string{name}
|
||||
} else {
|
||||
names = r.Form["names"]
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") {
|
||||
if formPlatforms := r.Form["platform"]; len(formPlatforms) > 1 {
|
||||
// TODO(thaJeztah): remove once we support multiple platforms: see https://github.com/moby/moby/issues/48759
|
||||
return errdefs.InvalidParameter(errors.New("multiple platform parameters not supported"))
|
||||
}
|
||||
if formPlatform := r.Form.Get("platform"); formPlatform != "" {
|
||||
p, err := httputils.DecodePlatform(formPlatform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platform = p
|
||||
}
|
||||
}
|
||||
|
||||
if err := ir.backend.ExportImage(ctx, names, platform, output); err != nil {
|
||||
if !output.Flushed() {
|
||||
return err
|
||||
}
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") {
|
||||
if formPlatforms := r.Form["platform"]; len(formPlatforms) > 1 {
|
||||
// TODO(thaJeztah): remove once we support multiple platforms: see https://github.com/moby/moby/issues/48759
|
||||
return errdefs.InvalidParameter(errors.New("multiple platform parameters not supported"))
|
||||
}
|
||||
if formPlatform := r.Form.Get("platform"); formPlatform != "" {
|
||||
p, err := httputils.DecodePlatform(formPlatform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platform = p
|
||||
}
|
||||
}
|
||||
quiet := httputils.BoolValueOrDefault(r, "quiet", true)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
output := ioutils.NewWriteFlusher(w)
|
||||
defer output.Close()
|
||||
if err := ir.backend.LoadImage(ctx, r.Body, platform, output, quiet); err != nil {
|
||||
_, _ = output.Write(streamformatter.FormatError(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type missingImageError struct{}
|
||||
|
||||
func (missingImageError) Error() string {
|
||||
return "image name cannot be blank"
|
||||
}
|
||||
|
||||
func (missingImageError) InvalidParameter() {}
|
||||
|
||||
func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return missingImageError{}
|
||||
}
|
||||
|
||||
force := httputils.BoolValue(r, "force")
|
||||
prune := !httputils.BoolValue(r, "noprune")
|
||||
|
||||
var platforms []ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.50") {
|
||||
p, err := httputils.DecodePlatforms(r.Form["platforms"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platforms = p
|
||||
}
|
||||
|
||||
list, err := ir.backend.ImageDelete(ctx, name, imagetypes.RemoveOptions{
|
||||
Force: force,
|
||||
PruneChildren: prune,
|
||||
Platforms: platforms,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var manifests bool
|
||||
if r.Form.Get("manifests") != "" && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") {
|
||||
manifests = httputils.BoolValue(r, "manifests")
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
if r.Form.Get("platform") != "" && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.49") {
|
||||
p, err := httputils.DecodePlatform(r.Form.Get("platform"))
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
platform = p
|
||||
}
|
||||
|
||||
if manifests && platform != nil {
|
||||
return errdefs.InvalidParameter(errors.New("conflicting options: manifests and platform options cannot both be set"))
|
||||
}
|
||||
|
||||
resp, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{
|
||||
Manifests: manifests,
|
||||
Platform: platform,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// inspectResponse preserves fields in the response that have an
|
||||
// "omitempty" in the OCI spec, but didn't omit such fields in
|
||||
// legacy responses before API v1.50.
|
||||
imageInspect := &inspectCompatResponse{
|
||||
InspectResponse: resp,
|
||||
legacyConfig: legacyConfigFields["current"],
|
||||
}
|
||||
|
||||
// Make sure we output empty arrays instead of nil. While Go nil slice is functionally equivalent to an empty slice,
|
||||
// it matters for the JSON representation.
|
||||
if imageInspect.RepoTags == nil {
|
||||
imageInspect.RepoTags = []string{}
|
||||
}
|
||||
if imageInspect.RepoDigests == nil {
|
||||
imageInspect.RepoDigests = []string{}
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.44") {
|
||||
imageInspect.VirtualSize = imageInspect.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
|
||||
if imageInspect.Created == "" {
|
||||
// backwards compatibility for Created not existing returning "0001-01-01T00:00:00Z"
|
||||
// https://github.com/moby/moby/issues/47368
|
||||
imageInspect.Created = time.Time{}.Format(time.RFC3339Nano)
|
||||
}
|
||||
}
|
||||
if versions.GreaterThanOrEqualTo(version, "1.45") {
|
||||
imageInspect.Container = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
imageInspect.ContainerConfig = nil //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
}
|
||||
if versions.LessThan(version, "1.48") {
|
||||
imageInspect.Descriptor = nil
|
||||
}
|
||||
if versions.LessThan(version, "1.50") {
|
||||
imageInspect.legacyConfig = legacyConfigFields["v1.49"]
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.LessThan(version, "1.41") {
|
||||
// NOTE: filter is a shell glob string applied to repository names.
|
||||
filterParam := r.Form.Get("filter")
|
||||
if filterParam != "" {
|
||||
imageFilters.Add("reference", filterParam)
|
||||
}
|
||||
}
|
||||
|
||||
var sharedSize bool
|
||||
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
||||
// NOTE: Support for the "shared-size" parameter was added in API 1.42.
|
||||
sharedSize = httputils.BoolValue(r, "shared-size")
|
||||
}
|
||||
|
||||
var manifests bool
|
||||
if versions.GreaterThanOrEqualTo(version, "1.47") {
|
||||
manifests = httputils.BoolValue(r, "manifests")
|
||||
}
|
||||
|
||||
images, err := ir.backend.Images(ctx, imagetypes.ListOptions{
|
||||
All: httputils.BoolValue(r, "all"),
|
||||
Filters: imageFilters,
|
||||
SharedSize: sharedSize,
|
||||
Manifests: manifests,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
useNone := versions.LessThan(version, "1.43")
|
||||
withVirtualSize := versions.LessThan(version, "1.44")
|
||||
noDescriptor := versions.LessThan(version, "1.48")
|
||||
noContainers := versions.LessThan(version, "1.51")
|
||||
for _, img := range images {
|
||||
if useNone {
|
||||
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
|
||||
img.RepoTags = append(img.RepoTags, "<none>:<none>")
|
||||
img.RepoDigests = append(img.RepoDigests, "<none>@<none>")
|
||||
}
|
||||
} else {
|
||||
if img.RepoTags == nil {
|
||||
img.RepoTags = []string{}
|
||||
}
|
||||
if img.RepoDigests == nil {
|
||||
img.RepoDigests = []string{}
|
||||
}
|
||||
}
|
||||
if withVirtualSize {
|
||||
img.VirtualSize = img.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
|
||||
}
|
||||
if noDescriptor {
|
||||
img.Descriptor = nil
|
||||
}
|
||||
if noContainers {
|
||||
img.Containers = -1
|
||||
}
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, images)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var platform *ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") {
|
||||
if formPlatform := r.Form.Get("platform"); formPlatform != "" {
|
||||
p, err := httputils.DecodePlatform(formPlatform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platform = p
|
||||
}
|
||||
}
|
||||
history, err := ir.backend.ImageHistory(ctx, vars["name"], platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, history)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
|
||||
if ref == nil || err != nil {
|
||||
return errdefs.InvalidParameter(err)
|
||||
}
|
||||
|
||||
refName := reference.FamiliarName(ref)
|
||||
if refName == string(digest.Canonical) {
|
||||
return errdefs.InvalidParameter(errors.New("refusing to create an ambiguous tag using digest algorithm as name"))
|
||||
}
|
||||
|
||||
img, err := ir.backend.GetImage(ctx, vars["name"], backend.GetImageOpts{})
|
||||
if err != nil {
|
||||
return errdefs.NotFound(err)
|
||||
}
|
||||
|
||||
if err := ir.backend.TagImage(ctx, img.ID(), ref); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var limit int
|
||||
if r.Form.Get("limit") != "" {
|
||||
var err error
|
||||
limit, err = strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil || limit < 0 {
|
||||
return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified"))
|
||||
}
|
||||
}
|
||||
searchFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For a search it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
|
||||
headers := http.Header{}
|
||||
for k, v := range r.Header {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
if strings.HasPrefix(k, "X-Meta-") {
|
||||
headers[k] = v
|
||||
}
|
||||
}
|
||||
headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx))
|
||||
res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, res)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pruneReport, err := ir.backend.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
|
||||
}
|
||||
|
||||
// validateRepoName validates the name of a repository.
|
||||
func validateRepoName(name reference.Named) error {
|
||||
familiarName := reference.FamiliarName(name)
|
||||
if familiarName == api.NoBaseImageSpecifier {
|
||||
return fmt.Errorf("'%s' is a reserved name", familiarName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
88
api/server/router/image/inspect_response.go
Normal file
88
api/server/router/image/inspect_response.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.23
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
)
|
||||
|
||||
// legacyConfigFields defines legacy image-config fields to include in
|
||||
// API responses on older API versions.
|
||||
var legacyConfigFields = map[string]map[string]any{
|
||||
// Legacy fields for API v1.49 and lower. These fields are deprecated
|
||||
// and omitted in newer API versions; see https://github.com/moby/moby/pull/48457
|
||||
"v1.49": {
|
||||
"AttachStderr": false,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"Cmd": nil,
|
||||
"Domainname": "",
|
||||
"Entrypoint": nil,
|
||||
"Env": nil,
|
||||
"Hostname": "",
|
||||
"Image": "",
|
||||
"Labels": nil,
|
||||
"OnBuild": nil,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Tty": false,
|
||||
"User": "",
|
||||
"Volumes": nil,
|
||||
"WorkingDir": "",
|
||||
},
|
||||
// Legacy fields for current API versions (v1.50 and up). These fields
|
||||
// did not have an "omitempty" and were always included in the response,
|
||||
// even if not set; see https://github.com/moby/moby/issues/50134
|
||||
"current": {
|
||||
"Cmd": nil,
|
||||
"Entrypoint": nil,
|
||||
"Env": nil,
|
||||
"Labels": nil,
|
||||
"OnBuild": nil,
|
||||
"User": "",
|
||||
"Volumes": nil,
|
||||
"WorkingDir": "",
|
||||
},
|
||||
}
|
||||
|
||||
// inspectCompatResponse is a wrapper around [image.InspectResponse] with a
|
||||
// custom marshal function for legacy [api/types/container.Config} fields
|
||||
// that have been removed, or did not have omitempty.
|
||||
type inspectCompatResponse struct {
|
||||
*image.InspectResponse
|
||||
legacyConfig map[string]any
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom marshaler to include legacy fields
|
||||
// in API responses.
|
||||
func (ir *inspectCompatResponse) MarshalJSON() ([]byte, error) {
|
||||
type tmp *image.InspectResponse
|
||||
base, err := json.Marshal((tmp)(ir.InspectResponse))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ir.legacyConfig) == 0 {
|
||||
return base, nil
|
||||
}
|
||||
|
||||
type resp struct {
|
||||
*image.InspectResponse
|
||||
Config map[string]any
|
||||
}
|
||||
|
||||
var merged resp
|
||||
err = json.Unmarshal(base, &merged)
|
||||
if err != nil {
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// prevent mutating legacyConfigFields.
|
||||
cfg := maps.Clone(ir.legacyConfig)
|
||||
maps.Copy(cfg, merged.Config)
|
||||
merged.Config = cfg
|
||||
return json.Marshal(merged)
|
||||
}
|
||||
@@ -4,9 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/v2/daemon/internal/compat"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -41,12 +40,12 @@ func TestInspectResponse(t *testing.T) {
|
||||
expected: `{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh"],"Domainname":"","Entrypoint":null,"Env":null,"Hostname":"","Image":"","Labels":null,"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"StopSignal":"SIGQUIT","Tty":false,"User":"","Volumes":null,"WorkingDir":""}`,
|
||||
},
|
||||
{
|
||||
doc: "api v1.50 - v1.51",
|
||||
doc: "api >= v1.50",
|
||||
cfg: &ocispec.ImageConfig{
|
||||
Cmd: []string{"/bin/sh"},
|
||||
StopSignal: "SIGQUIT",
|
||||
},
|
||||
legacyConfig: legacyConfigFields["v1.50-v1.51"],
|
||||
legacyConfig: legacyConfigFields["current"],
|
||||
expected: `{"Cmd":["/bin/sh"],"Entrypoint":null,"Env":null,"Labels":null,"OnBuild":null,"StopSignal":"SIGQUIT","User":"","Volumes":null,"WorkingDir":""}`,
|
||||
},
|
||||
}
|
||||
@@ -60,11 +59,10 @@ func TestInspectResponse(t *testing.T) {
|
||||
ImageConfig: *tc.cfg,
|
||||
}
|
||||
}
|
||||
legacyConfigResponse := compat.Wrap(imgInspect, compat.WithExtraFields(map[string]any{
|
||||
"Config": tc.legacyConfig,
|
||||
}))
|
||||
|
||||
out, err := json.Marshal(&legacyConfigResponse)
|
||||
out, err := json.Marshal(&inspectCompatResponse{
|
||||
InspectResponse: imgInspect,
|
||||
legacyConfig: tc.legacyConfig,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
var outMap struct{ Config json.RawMessage }
|
||||
73
api/server/router/local.go
Normal file
73
api/server/router/local.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
)
|
||||
|
||||
// RouteWrapper wraps a route with extra functionality.
|
||||
// It is passed in when creating a new route.
|
||||
type RouteWrapper func(r Route) Route
|
||||
|
||||
// localRoute defines an individual API route to connect
|
||||
// with the docker daemon. It implements Route.
|
||||
type localRoute struct {
|
||||
method string
|
||||
path string
|
||||
handler httputils.APIFunc
|
||||
}
|
||||
|
||||
// Handler returns the APIFunc to let the server wrap it in middlewares.
|
||||
func (l localRoute) Handler() httputils.APIFunc {
|
||||
return l.handler
|
||||
}
|
||||
|
||||
// Method returns the http method that the route responds to.
|
||||
func (l localRoute) Method() string {
|
||||
return l.method
|
||||
}
|
||||
|
||||
// Path returns the subpath where the route responds to.
|
||||
func (l localRoute) Path() string {
|
||||
return l.path
|
||||
}
|
||||
|
||||
// NewRoute initializes a new local route for the router.
|
||||
func NewRoute(method, path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
var r Route = localRoute{method, path, handler}
|
||||
for _, o := range opts {
|
||||
r = o(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewGetRoute initializes a new route with the http method GET.
|
||||
func NewGetRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodGet, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewPostRoute initializes a new route with the http method POST.
|
||||
func NewPostRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodPost, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewPutRoute initializes a new route with the http method PUT.
|
||||
func NewPutRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodPut, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewDeleteRoute initializes a new route with the http method DELETE.
|
||||
func NewDeleteRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodDelete, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewOptionsRoute initializes a new route with the http method OPTIONS.
|
||||
func NewOptionsRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodOptions, path, handler, opts...)
|
||||
}
|
||||
|
||||
// NewHeadRoute initializes a new route with the http method HEAD.
|
||||
func NewHeadRoute(path string, handler httputils.APIFunc, opts ...RouteWrapper) Route {
|
||||
return NewRoute(http.MethodHead, path, handler, opts...)
|
||||
}
|
||||
30
api/server/router/network/backend.go
Normal file
30
api/server/router/network/backend.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
// to provide network specific functionality.
|
||||
type Backend interface {
|
||||
GetNetworks(filters.Args, backend.NetworkListConfig) ([]network.Inspect, error)
|
||||
CreateNetwork(ctx context.Context, nc network.CreateRequest) (*network.CreateResponse, error)
|
||||
ConnectContainerToNetwork(ctx context.Context, containerName, networkName string, endpointConfig *network.EndpointSettings) error
|
||||
DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
|
||||
DeleteNetwork(networkID string) error
|
||||
NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*network.PruneReport, error)
|
||||
}
|
||||
|
||||
// ClusterBackend is all the methods that need to be implemented
|
||||
// to provide cluster network specific functionality.
|
||||
type ClusterBackend interface {
|
||||
GetNetworks(filters.Args) ([]network.Inspect, error)
|
||||
GetNetwork(name string) (network.Inspect, error)
|
||||
GetNetworksByName(name string) ([]network.Inspect, error)
|
||||
CreateNetwork(nc network.CreateRequest) (string, error)
|
||||
RemoveNetwork(name string) error
|
||||
}
|
||||
43
api/server/router/network/network.go
Normal file
43
api/server/router/network/network.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/server/router"
|
||||
)
|
||||
|
||||
// networkRouter is a router to talk with the network controller
|
||||
type networkRouter struct {
|
||||
backend Backend
|
||||
cluster ClusterBackend
|
||||
routes []router.Route
|
||||
}
|
||||
|
||||
// NewRouter initializes a new network router
|
||||
func NewRouter(b Backend, c ClusterBackend) router.Router {
|
||||
r := &networkRouter{
|
||||
backend: b,
|
||||
cluster: c,
|
||||
}
|
||||
r.initRoutes()
|
||||
return r
|
||||
}
|
||||
|
||||
// Routes returns the available routes to the network controller
|
||||
func (n *networkRouter) Routes() []router.Route {
|
||||
return n.routes
|
||||
}
|
||||
|
||||
func (n *networkRouter) initRoutes() {
|
||||
n.routes = []router.Route{
|
||||
// GET
|
||||
router.NewGetRoute("/networks", n.getNetworksList),
|
||||
router.NewGetRoute("/networks/", n.getNetworksList),
|
||||
router.NewGetRoute("/networks/{id:.+}", n.getNetwork),
|
||||
// POST
|
||||
router.NewPostRoute("/networks/create", n.postNetworkCreate),
|
||||
router.NewPostRoute("/networks/{id:.*}/connect", n.postNetworkConnect),
|
||||
router.NewPostRoute("/networks/{id:.*}/disconnect", n.postNetworkDisconnect),
|
||||
router.NewPostRoute("/networks/prune", n.postNetworksPrune),
|
||||
// DELETE
|
||||
router.NewDeleteRoute("/networks/{id:.*}", n.deleteNetwork),
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/internal/filters"
|
||||
"github.com/moby/moby/v2/daemon/internal/versions"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
||||
dnetwork "github.com/moby/moby/v2/daemon/network"
|
||||
"github.com/moby/moby/v2/daemon/server/backend"
|
||||
"github.com/moby/moby/v2/daemon/server/httputils"
|
||||
"github.com/moby/moby/v2/daemon/server/networkbackend"
|
||||
"github.com/moby/moby/v2/errdefs"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/libnetwork"
|
||||
"github.com/docker/docker/libnetwork/scope"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -24,42 +22,28 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
|
||||
return err
|
||||
}
|
||||
|
||||
filterArgs, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
filter, err := filters.FromJSON(r.Form.Get("filters"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter, err := dnetwork.NewFilter(filterArgs)
|
||||
if err := network.ValidateFilters(filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var list []network.Summary
|
||||
nr, err := n.cluster.GetNetworks(filter)
|
||||
if err == nil {
|
||||
list = nr
|
||||
}
|
||||
|
||||
// Combine the network list returned by Docker daemon if it is not already
|
||||
// returned by the cluster manager
|
||||
localNetworks, err := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: versions.LessThan(httputils.VersionFromContext(ctx), "1.28")})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if versions.LessThan(httputils.VersionFromContext(ctx), "1.28") {
|
||||
list, _ := n.cluster.GetNetworks(filter, false)
|
||||
var idx map[string]bool
|
||||
if len(list) > 0 {
|
||||
idx = make(map[string]bool, len(list))
|
||||
for _, n := range list {
|
||||
idx[n.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
localNetworks, err := n.backend.GetNetworks(filter, backend.NetworkListConfig{WithServices: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range localNetworks {
|
||||
if !idx[n.ID] {
|
||||
list = append(list, n)
|
||||
}
|
||||
}
|
||||
if list == nil {
|
||||
list = []network.Inspect{}
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
list, _ := n.cluster.GetNetworkSummaries(filter)
|
||||
var idx map[string]bool
|
||||
if len(list) > 0 {
|
||||
idx = make(map[string]bool, len(list))
|
||||
@@ -67,18 +51,11 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit
|
||||
idx[n.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the network list returned by Docker daemon if it is not already
|
||||
// returned by the cluster manager
|
||||
localNetworks, err := n.backend.GetNetworkSummaries(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, n := range localNetworks {
|
||||
if !idx[n.ID] {
|
||||
list = append(list, n)
|
||||
if idx[n.ID] {
|
||||
continue
|
||||
}
|
||||
list = append(list, n)
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
@@ -136,21 +113,11 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
|
||||
// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
|
||||
// Instead there should be a backend function to just get one network.
|
||||
filterArgs := filters.NewArgs(filters.Arg("id", term))
|
||||
filter := filters.NewArgs(filters.Arg("idOrName", term))
|
||||
if networkScope != "" {
|
||||
filterArgs.Add("scope", networkScope)
|
||||
filter.Add("scope", networkScope)
|
||||
}
|
||||
filter, err := dnetwork.NewFilter(filterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.IDAlsoMatchesName = true
|
||||
|
||||
withStatus := versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.52")
|
||||
networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{
|
||||
WithServices: verbose,
|
||||
WithStatus: withStatus,
|
||||
})
|
||||
networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true, Verbose: verbose})
|
||||
for _, nw := range networks {
|
||||
if nw.ID == term {
|
||||
return httputils.WriteJSON(w, http.StatusOK, nw)
|
||||
@@ -167,28 +134,25 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
|
||||
nwk, err := n.cluster.GetNetwork(term, withStatus)
|
||||
nwk, err := n.cluster.GetNetwork(term)
|
||||
if err == nil {
|
||||
// If the get network is passed with a specific network ID / partial network ID
|
||||
// or if the get network was passed with a network name and scope as swarm
|
||||
// return the network. Skipped using isMatchingScope because it is true if the scope
|
||||
// is not set which would be case if the client API v1.30
|
||||
if strings.HasPrefix(nwk.ID, term) || networkScope == scope.Swarm {
|
||||
// If we have a previous match "backend", return it
|
||||
// along with the Status from the Swarm leader.
|
||||
// If we have a previous match "backend", return it, we need verbose when enabled
|
||||
// ex: overlay/partial_ID or name/swarm_scope
|
||||
if nwv, ok := listByPartialID[nwk.ID]; ok {
|
||||
nwv.Status = nwk.Status
|
||||
nwk = nwv
|
||||
} else if nwv, ok = listByFullName[nwk.ID]; ok {
|
||||
nwv.Status = nwk.Status
|
||||
nwk = nwv
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, nwk)
|
||||
}
|
||||
}
|
||||
|
||||
networks, _ = n.cluster.GetNetworks(filter, withStatus)
|
||||
networks, _ = n.cluster.GetNetworks(filter)
|
||||
for _, nw := range networks {
|
||||
if nw.ID == term {
|
||||
return httputils.WriteJSON(w, http.StatusOK, nw)
|
||||
@@ -197,10 +161,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
// Check the ID collision as we are in swarm scope here, and
|
||||
// the map (of the listByFullName) may have already had a
|
||||
// network with the same ID (from local scope previously)
|
||||
if nwk, ok := listByFullName[nw.ID]; ok {
|
||||
nwk.Status = nw.Status
|
||||
listByFullName[nw.ID] = nwk
|
||||
} else {
|
||||
if _, ok := listByFullName[nw.ID]; !ok {
|
||||
listByFullName[nw.ID] = nw
|
||||
}
|
||||
}
|
||||
@@ -208,10 +169,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
|
||||
// Check the ID collision as we are in swarm scope here, and
|
||||
// the map (of the listByPartialID) may have already had a
|
||||
// network with the same ID (from local scope previously)
|
||||
if nwk, ok := listByPartialID[nw.ID]; ok {
|
||||
nwk.Status = nw.Status
|
||||
listByPartialID[nw.ID] = nwk
|
||||
} else {
|
||||
if _, ok := listByPartialID[nw.ID]; !ok {
|
||||
listByPartialID[nw.ID] = nw
|
||||
}
|
||||
}
|
||||
@@ -287,7 +245,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
|
||||
return err
|
||||
}
|
||||
|
||||
var connect networkbackend.ConnectRequest
|
||||
var connect network.ConnectOptions
|
||||
if err := httputils.ReadJSON(r, &connect); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -304,7 +262,7 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
|
||||
return err
|
||||
}
|
||||
|
||||
var disconnect networkbackend.DisconnectRequest
|
||||
var disconnect network.DisconnectOptions
|
||||
if err := httputils.ReadJSON(r, &disconnect); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -334,7 +292,7 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *networkRouter) postNetworkPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := httputils.ParseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -344,7 +302,7 @@ func (n *networkRouter) postNetworkPrune(ctx context.Context, w http.ResponseWri
|
||||
return err
|
||||
}
|
||||
|
||||
pruneReport, err := n.backend.NetworkPrune(ctx, pruneFilters)
|
||||
pruneReport, err := n.backend.NetworksPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,13 +321,8 @@ func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error)
|
||||
listByFullName := map[string]network.Inspect{}
|
||||
listByPartialID := map[string]network.Inspect{}
|
||||
|
||||
filter, err := dnetwork.NewFilter(filters.NewArgs(filters.Arg("id", term)))
|
||||
if err != nil {
|
||||
return network.Inspect{}, err
|
||||
}
|
||||
filter.IDAlsoMatchesName = true
|
||||
|
||||
networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{})
|
||||
filter := filters.NewArgs(filters.Arg("idOrName", term))
|
||||
networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true})
|
||||
for _, nw := range networks {
|
||||
if nw.ID == term {
|
||||
return nw, nil
|
||||
@@ -386,7 +339,7 @@ func (n *networkRouter) findUniqueNetwork(term string) (network.Inspect, error)
|
||||
}
|
||||
}
|
||||
|
||||
networks, _ = n.cluster.GetNetworks(filter, false)
|
||||
networks, _ = n.cluster.GetNetworks(filter)
|
||||
for _, nw := range networks {
|
||||
if nw.ID == term {
|
||||
return nw, nil
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user