Compare commits

..

44 Commits

Author SHA1 Message Date
Sebastiaan van Stijn
5beb93de84 Merge pull request #50309 from crazy-max/28.x_pick_buildkit-0.23.2
[28.x backport] vendor: update buildkit to v0.23.2
2025-07-02 15:31:29 +02:00
Tonis Tiigi
e17e96e3c5 vendor: update buildkit to v0.23.2
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-07-02 11:48:40 +02:00
Sebastiaan van Stijn
e0183475e0 Merge pull request #50264 from vvoland/50263-28.x
[28.x backport] gha/bin-image: add major and minor version image tags
2025-06-25 14:47:55 +02:00
Paweł Gronowski
a2af8bdebd gha/bin-image: add major and minor version image tags
Adding image tags that follow the semver major and minor versions (e.g., `28`
and `28.3`) for the moby-bin images.

This makes it easier for users to reference the latest build within a
major or minor version series without having to know the exact
minor/patch version.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 38b98bcf68)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-24 23:32:35 +02:00
Paweł Gronowski
265f709647 Merge pull request #50247 from vvoland/50245-28.x
[28.x backport] docs: cut api docs for v1.51
2025-06-20 16:22:35 +00:00
Paweł Gronowski
b2a9318a1e docs: cut api docs for v1.51
Used by the upcoming 28.3.0 release

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit ef50844a0b)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-20 18:16:33 +02:00
Sebastiaan van Stijn
b3e2e22b2a Merge pull request #50244 from vvoland/50177-28.x
[28.x backport] gha: lower timeouts on "build" and "merge" steps
2025-06-20 17:37:41 +02:00
Sebastiaan van Stijn
c571cd8513 Merge pull request #50243 from vvoland/50238-28.x
[28.x backport] vendor: update buildkit to v0.23.1
2025-06-20 17:36:18 +02:00
Sebastiaan van Stijn
8c713c1af4 gha: lower timeouts on "build" and "merge" steps
We had some runs timeout after 120 minutes; expected duration is much
lower than that, so let's lower the timeout to make actions fail faster.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0a30b98447)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-20 16:29:08 +02:00
Paweł Gronowski
539c115023 Merge pull request #50240 from thaJeztah/28.x_backport_validate_mirrors
[28.x backport] daemon/config: Validate: add missing validation for registry mirrors and improve errors
2025-06-20 14:16:09 +00:00
CrazyMax
8e7ea470cf vendor: update buildkit to v0.23.1
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
(cherry picked from commit 5a02e7f4e3)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-20 16:05:04 +02:00
Jonathan A. Sternberg
222baf4ccb vendor: github.com/moby/buildkit v0.23.0
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
(cherry picked from commit e43968d7ed)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-20 16:04:30 +02:00
Paweł Gronowski
1627e828d7 Merge pull request #50241 from thaJeztah/28.x_backport_update_cgroups
[28.x backport] vendor: github.com/opencontainers/cgroups v0.0.3
2025-06-20 14:00:46 +00:00
Paweł Gronowski
4070ebda88 Merge pull request #50242 from thaJeztah/28.x_backport_fix_event_ordering
[28.x backport] daemon: containerStop: fix ordering of "stop" and "die" events
2025-06-20 13:38:31 +00:00
Paweł Gronowski
b613ac489e Merge pull request #50239 from vvoland/50237-28.x
[28.x backport] Update containerd to v2.1.3
2025-06-20 11:36:57 +00:00
Sebastiaan van Stijn
0e0ca09ddc daemon: containerStop: fix ordering of "stop" and "die" events
Commit 8e6cd44ce4 added synchronisation to
wait for the container's status to be updated in memory. However, since
952902efbc, a defer was used to produce
the container's "stop" event.

As a result of the sychronisation that was added, the "die" event would
now be produced before the "stop" event.

This patch moves the locking inside the defer to restore the previous
behavior.

Unfortunately the order of events is still not guaranteed, because events
are emited from multiple goroutines that don't have synchronisation between
them; this is something to look at for follow ups. This patch keeps the status
quo and should preserve the old behavior, which was "more" correct in most
cases.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 062082ec9b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 13:29:19 +02:00
Sebastiaan van Stijn
e62b0e2234 vendor: github.com/opencontainers/cgroups v0.0.3
- ConvertCPUSharesToCgroupV2Value: improve
- Add .github/dependabot.yml
- Remove annotations from Resources (fixes a regression introduced in v0.0.2)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a90da2edc3)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 13:26:46 +02:00
Sebastiaan van Stijn
06ab9cd1ed daemon/config: Validate: add missing validation for registry mirrors
Validation of registry mirrors was performed during daemon startup,
but after the config-file was validated. As a result, the `--validate`
option would incorrectly print that the configuration was valid, but
the daemon would fail to start;

    echo '{"registry-mirrors":["example.com"]}' > my-config.json
    dockerd --config-file ./my-config.json --validate
    configuration OK

    dockerd --config-file ./my-config.json
    # ...
    failed to start daemon: invalid mirror: no scheme specified for "example.com": must use either 'https://' or 'http://'

With this patch applied, validation is also performed as part of the
daemon config validation;

    echo '{"registry-mirrors":["example.com"]}' > my-config.json
    dockerd --config-file ./my-config.json --validate
    unable to configure the Docker daemon with file ./my-config.json: merged configuration validation from file and command line flags failed: invalid mirror: no scheme specified for "example.com": must use either 'https://' or 'http://'

    # fix the invalid config
    echo '{"registry-mirrors":["https://example.com"]}' > my-config.json
    dockerd --config-file ./my-config.json --validate
    configuration OK

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1d8545d60c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 13:20:27 +02:00
Sebastiaan van Stijn
97aa4e8550 registry: ValidateMirror: improve validation for missing schemes
Before this patch, a missing scheme would sometimes produce a confusing
error message. If no scheme was specified at all, an empty "" would be
included in the message;

    echo '{"registry-mirrors":["example.com"]}' > my-config.json
    dockerd --config-file ./my-config.json
    # ...
    failed to start daemon: invalid mirror: unsupported scheme "" in "example.com"

If a scheme was missing, but a port was included, the hostname would be
printed as the scheme;

    echo '{"registry-mirrors":["example.com:8080"]}' > my-config.json
    dockerd --config-file ./my-config.json
    # ...
    failed to start daemon: invalid mirror: unsupported scheme "example.com" in "example.com:8080"

With this patch applied, the error messages are slightly more user-friendly;

    echo '{"registry-mirrors":["example.com"]}' > my-config.json
    dockerd --config-file ./my-config.json
    # ...
    failed to start daemon: invalid mirror: no scheme specified for "example.com": must use either 'https://' or 'http://'

    echo '{"registry-mirrors":["example.com:8080"]}' > my-config.json
    dockerd --config-file ./my-config.json
    # ...
    failed to start daemon: invalid mirror: no scheme specified for "example.com:8080": must use either 'https://' or 'http://'

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 307c18598d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 13:20:26 +02:00
Derek McGowan
e18a9c95b8 Update containerd to v2.1.3
Fixes various issues with pulling from registries

Signed-off-by: Derek McGowan <derek@mcg.dev>
(cherry picked from commit b466c35da1)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-20 11:28:31 +02:00
Sebastiaan van Stijn
b959bebdfc Merge pull request #50219 from thaJeztah/28.x_backport_deprecate_execconfig_detach
[28.x backport] api/types/container: deprecate ExecOptions.Detach
2025-06-18 23:03:00 +02:00
Sebastiaan van Stijn
02ade1a34c Merge pull request #50210 from thaJeztah/28.x_backport_pkg_idtools_deprecate
[28.x backport] pkg/idtools: deprecate IdentityMapping, Identity.Chown
2025-06-18 23:02:12 +02:00
Paweł Gronowski
106c4b0af6 Merge pull request #50211 from thaJeztah/28.x_backport_bump_swarmkit
[28.x backport] vendor: github.com/moby/swarmkit/v2 v2.0.0
2025-06-17 16:18:54 +00:00
Sebastiaan van Stijn
54d2eee6d6 Merge pull request #50217 from thaJeztah/28.x_backport_update-buildkit-v0.23.0-rc2
[28.x backport] vendor: update buildkit to v0.13.0-rc2
2025-06-17 15:10:47 +02:00
Sebastiaan van Stijn
09fef2b26e api/types/container: deprecate ExecOptions.Detach
This field was added in 5130fe5d38, which
added it for use as intermediate struct when parsing CLI flags (through
`runconfig.ParseExec`) in c786a8ee5e.

Commit 9d9dff3d0d rewrote the CLI to use
Cobra, and as part of this introduced a separate `execOptions` type in
`api/client/container`.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0c182d4d57)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-17 13:10:50 +02:00
Tonis Tiigi
44c8cd2e8f vendor: update buildkit to v0.13.0-rc2
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 1289519b03)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-17 11:36:27 +02:00
Cory Snider
78b6204f9e vendor: github.com/moby/swarmkit/v2 v2.0.0
Use the tagged version instead of the v2.0.0-20250613170222-a45be3cac15c
pseudo-version. The referenced commit has not changed.

Signed-off-by: Cory Snider <csnider@mirantis.com>
(cherry picked from commit c3ac979ecf)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-17 11:23:57 +02:00
Cory Snider
cf98237186 vendor: github.com/moby/swarmkit/v2 v2.0.0-20250613170222-a45be3cac15c
- fix task scheduler infinite loop

full diff: 8c19597365...a45be3cac1

Signed-off-by: Cory Snider <csnider@mirantis.com>
(cherry picked from commit 2d60b8eacd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-16 17:53:26 +02:00
Sebastiaan van Stijn
fd96b01b0e pkg/idtools: deprecate IdentityMapping, Identity.Chown
The IdentityMapping and Identity types are still used internally, but
should be considered transitional.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b7ef527bdc)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-16 17:40:11 +02:00
Paweł Gronowski
6a1fb46d48 Merge pull request #50169 from robmry/revert_overlay_refactoring
[28.x]: Revert overlay bug fixes / refactoring
2025-06-13 15:49:07 +00:00
Rob Murray
7acb079403 Revert "libn/networkdb: don't exceed broadcast size limit"
This reverts commit dacf445614.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:49 +01:00
Rob Murray
0df31cf585 Revert "libn/networkdb: fix data race in GetTableByNetwork"
This reverts commit ec65f2d21b.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:48 +01:00
Rob Murray
83b2fc245d Revert "Fix possible overlapping IPs when ingressNA == nil"
This reverts commit 56ad941564.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:46 +01:00
Rob Murray
e079583ab4 Revert "libnetwork/networkdb: use correct index in GetTableByNetwork"
This reverts commit d5c370dee6.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:45 +01:00
Rob Murray
cfd5e5e4d4 Revert "libn/networkdb: b'cast watch events from local POV"
This reverts commit c68671d908.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:44 +01:00
Rob Murray
576cf73add Revert "libn/networkdb: record tombstones for all deletes"
This reverts commit ada8bc3695.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:43 +01:00
Rob Murray
2297ae3e64 Revert "libn/networkdb: Watch() without race conditions"
This reverts commit a3aea15257.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:41 +01:00
Rob Murray
cc60ec8d3c Revert "libn/networkdb: stop table events from racing network leaves"
This reverts commit 270a4d41dc.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:40 +01:00
Rob Murray
b5b349dbd6 Revert "libn/osl: drop unused AddNeighbor force parameter"
This reverts commit 3bdf99d127.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:39 +01:00
Rob Murray
35916f0869 Revert "libn/osl: refactor func (*Namespace) AddNeighbor"
This reverts commit b6d76eb572.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:38 +01:00
Rob Murray
3eb59ba5a2 Revert "libnetwork/osl: remove superfluous locks in Namespace"
This reverts commit 9866738736.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:37 +01:00
Rob Murray
5d6ae34753 Revert "libnetwork/osl: stop tracking neighbor entries"
This reverts commit 0d6e7cd983.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:36 +01:00
Rob Murray
ea818a7f6f Revert "libnetwork/internal/setmatrix: make keys generic"
This reverts commit 0317f773a6.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:33 +01:00
Rob Murray
78ccc20545 Revert "libn/d/overlay: use netip types more"
This reverts commit d188df0039.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-06-11 12:05:26 +01:00
4767 changed files with 141781 additions and 253288 deletions

View File

@@ -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
View File

@@ -1 +1,3 @@
Dockerfile* linguist-language=Dockerfile
vendor.mod linguist-language=Go-Module
vendor.sum linguist-language=Go-Checksums

159
.github/labeler.yml vendored
View File

@@ -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'

View File

@@ -16,7 +16,7 @@ on:
workflow_call:
env:
ALPINE_VERSION: "3.22"
ALPINE_VERSION: "3.21"
jobs:
run:

45
.github/workflows/.test-prepare.yml vendored Normal file
View 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));
});

View File

@@ -16,7 +16,7 @@ on:
workflow_call:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
SETUP_BUILDX_VERSION: edge
@@ -106,7 +106,7 @@ jobs:
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@v4

View File

@@ -21,13 +21,13 @@ on:
default: "graphdriver"
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
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
@@ -144,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)}`);
@@ -192,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
@@ -270,7 +265,7 @@ jobs:
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@v4
@@ -302,7 +297,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false
cache-dependency-path: vendor.sum
-
name: Install gotestlist
run:
@@ -333,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'
});
}
@@ -409,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
@@ -469,7 +437,7 @@ jobs:
if: always()
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
@@ -486,13 +454,13 @@ jobs:
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@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

View File

@@ -1,207 +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.4"
TESTSTAT_VERSION: v0.1.25
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@v4
-
name: Set up Lima
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
id: lima-actions-setup
with:
version: v1.2.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="$(basename ${{ inputs.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@v4
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@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Prepare reports
run: echo "TESTREPORTS_NAME=$(basename ${{ inputs.template }})*" >> $GITHUB_ENV
-
name: Download reports
uses: actions/download-artifact@v4
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

View File

@@ -28,12 +28,12 @@ on:
default: false
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
WINDOWS_BASE_TAG_2019: ltsc2019
WINDOWS_BASE_TAG_2022: ltsc2022
WINDOWS_BASE_TAG_2025: ltsc2025
TEST_IMAGE_NAME: moby:test
TEST_CTN_NAME: moby
DOCKER_BUILDKIT: 0
@@ -65,11 +65,23 @@ jobs:
run: |
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod"
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
If ("${{ inputs.os }}" -eq "windows-2019") {
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} 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: |
@@ -80,12 +92,15 @@ jobs:
& docker build `
--build-arg WINDOWS_BASE_IMAGE `
--build-arg WINDOWS_BASE_IMAGE_TAG `
--build-arg GO_VERSION `
-t ${{ env.TEST_IMAGE_NAME }} `
-f Dockerfile.windows .
-
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
@@ -130,11 +145,23 @@ jobs:
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod"
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
If ("${{ inputs.os }}" -eq "windows-2019") {
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} 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: |
@@ -145,12 +172,15 @@ jobs:
& docker build `
--build-arg WINDOWS_BASE_IMAGE `
--build-arg WINDOWS_BASE_IMAGE_TAG `
--build-arg GO_VERSION `
-t ${{ env.TEST_IMAGE_NAME }} `
-f Dockerfile.windows .
-
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
-
@@ -184,7 +214,7 @@ jobs:
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@v4
@@ -214,7 +244,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false
cache-dependency-path: vendor.sum
-
name: Install gotestlist
run:
@@ -267,12 +297,6 @@ jobs:
uses: actions/checkout@v4
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Set up Jaeger
run: |
@@ -297,8 +321,8 @@ jobs:
name: Init
run: |
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
If ("${{ inputs.os }}" -eq "windows-2019") {
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2019 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} 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
}
@@ -342,13 +366,10 @@ jobs:
"--exec-root=$env:TEMP\moby-exec", `
"--pidfile=$env:TEMP\docker.pid", `
"--register-service"
If ("${{ inputs.storage }}" -eq "graphdriver") {
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 "DOCKER_MIN_API_VERSION=1.24@TEST_INTEGRATION_USE_GRAPHDRIVER=1"
echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} Else {
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d DOCKER_MIN_API_VERSION=1.24
& 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
}
Write-Host "Starting service"
Start-Service -Name docker
@@ -407,6 +428,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 == './...'
@@ -414,6 +441,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
@@ -422,6 +450,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 }}
-
@@ -498,7 +527,7 @@ jobs:
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@v4

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
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,13 +87,12 @@ 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:
@@ -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
@@ -156,7 +150,7 @@ 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:
@@ -165,7 +159,7 @@ jobs:
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@v4
@@ -185,7 +179,6 @@ 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:
@@ -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
@@ -259,7 +249,7 @@ 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:
@@ -268,7 +258,7 @@ jobs:
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@v4

View File

@@ -22,7 +22,6 @@ on:
- '[0-9]+.x'
tags:
- 'v*'
- 'docker-v*'
pull_request:
env:
@@ -37,13 +36,12 @@ 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:
@@ -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
@@ -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:
@@ -174,9 +170,9 @@ 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

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
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:
@@ -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:
@@ -108,7 +106,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: false
cache-dependency-path: vendor.sum
-
name: BuildKit ref
run: |
@@ -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:
@@ -191,7 +188,6 @@ jobs:
- name: Env
run: |
Get-ChildItem Env: | Out-String
- name: Moby - Init
run: |
New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build"
@@ -202,7 +198,18 @@ jobs:
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: |
@@ -213,6 +220,7 @@ jobs:
& docker build `
--build-arg WINDOWS_BASE_IMAGE `
--build-arg WINDOWS_BASE_IMAGE_TAG `
--build-arg GO_VERSION `
-t ${{ env.TEST_IMAGE_NAME }} `
-f Dockerfile.windows .
@@ -258,7 +266,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,27 +309,22 @@ 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@v4
with:
path: moby
- name: Set up Go
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@v4
with:
@@ -357,7 +359,6 @@ jobs:
testFlags="${testFlags} --run=TestIntegration/$testSliceOffset.*/worker=${{ matrix.worker }}"
fi
echo "TESTFLAGS=${testFlags}" >> $GITHUB_ENV
- name: Test
shell: bash
run: |

View File

@@ -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:
@@ -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:

View File

@@ -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.4"
jobs:
codeql:
runs-on: ubuntu-24.04
@@ -50,11 +46,19 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Set up Go
# 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.4"
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:

View File

@@ -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

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.24.4"
GIT_PAGER: "cat"
PAGER: "cat"
SETUP_BUILDX_VERSION: edge
@@ -44,7 +44,6 @@ jobs:
mode:
- ""
- systemd
- firewalld
steps:
-
name: Prepare
@@ -66,11 +65,10 @@ jobs:
targets: dev
set: |
*.cache-from=type=gha,scope=dev${{ matrix.mode }}
*.cache-to=type=gha,scope=dev${{ matrix.mode }}
*.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
@@ -86,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
@@ -156,7 +153,6 @@ jobs:
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:
@@ -179,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:

View File

@@ -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

View File

@@ -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 }}

42
.github/workflows/windows-2019.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: windows-2019
# 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:
schedule:
- cron: '0 10 * * *'
workflow_dispatch:
jobs:
validate-dco:
uses: ./.github/workflows/.dco.yml
test-prepare:
uses: ./.github/workflows/.test-prepare.yml
needs:
- validate-dco
run:
needs:
- test-prepare
uses: ./.github/workflows/.windows.yml
secrets: inherit
strategy:
fail-fast: false
matrix:
storage: ${{ fromJson(needs.test-prepare.outputs.matrix) }}
with:
os: windows-2019
storage: ${{ matrix.storage }}
send_coverage: false

View File

@@ -14,25 +14,32 @@ concurrency:
cancel-in-progress: true
on:
schedule:
- cron: '0 10 * * *'
workflow_dispatch:
push:
branches:
- 'master'
- '[0-9]+.[0-9]+'
- '[0-9]+.x'
pull_request:
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 }}

View File

@@ -1,43 +0,0 @@
name: windows-2025
# 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
run:
needs: validate-dco
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
uses: ./.github/workflows/.windows.yml
secrets: inherit
strategy:
fail-fast: false
matrix:
storage:
- graphdriver
- snapshotter
with:
os: windows-2025
storage: ${{ matrix.storage }}
send_coverage: false

4
.gitignore vendored
View File

@@ -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

View File

@@ -3,7 +3,7 @@ version: "2"
run:
# prevent golangci-lint from deducting the go version to lint for through go.mod,
# which causes it to fallback to go1.17 semantics.
go: "1.25.4"
go: "1.24.4"
concurrency: 2
# Only supported with go modules enabled (build flag -mod=vendor only valid when using modules)
# modules-download-mode: vendor
@@ -69,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:
@@ -105,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
@@ -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
@@ -314,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'
@@ -333,21 +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
# Log a warning if an exclusion rule is unused.
# Default: false

View File

@@ -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
View File

@@ -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>

View File

@@ -1,30 +1,27 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.24.4
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=v28.5.0
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.29.1
ARG BUILDX_VERSION=0.24.0
# COMPOSE_VERSION is the version of compose to install in the dev container.
ARG COMPOSE_VERSION=v2.40.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
@@ -84,18 +82,25 @@ 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
# 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
# hack/validate/swagger-gen
ARG GO_SWAGGER_VERSION=v0.33.1
RUN --mount=type=cache,target=/root/.cache/go-build,id=swagger-build-$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
GOBIN=/build CGO_ENABLED=0 xx-go install "github.com/go-swagger/go-swagger/cmd/swagger@${GO_SWAGGER_VERSION}"
xx-go build -o /build/swagger ./cmd/swagger
xx-verify /build/swagger
EOT
@@ -116,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 \
@@ -130,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
@@ -140,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
@@ -152,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
@@ -162,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.1.5
# 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
@@ -197,28 +205,27 @@ FROM base AS golangci_lint
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
@@ -250,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.3
# 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
@@ -321,8 +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"
# When updating, also update go.mod and hack/dockerfile/install/rootlesskit.installer accordingly.
ARG ROOTLESSKIT_VERSION=v2.3.5
# 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
@@ -334,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 \
@@ -494,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 \
@@ -502,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 \
@@ -536,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
@@ -574,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")

View File

@@ -5,17 +5,18 @@
# This represents the bare minimum required to build and test Docker.
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.24.4
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 \

View File

@@ -161,17 +161,12 @@ FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
# Use PowerShell as the default shell
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
ARG GO_VERSION=1.25.4
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
ARG GOTESTSUM_VERSION=v1.13.0
ARG GO_VERSION=1.24.4
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.
@@ -181,6 +176,7 @@ 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..."'; `
} `

View File

@@ -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"

View File

@@ -38,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 \
@@ -55,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 \

View File

@@ -1,11 +1,9 @@
The Moby Project
================
[![PkgGoDev](https://pkg.go.dev/badge/github.com/moby/moby/v2)](https://pkg.go.dev/github.com/moby/moby/v2)
![GitHub License](https://img.shields.io/github/license/moby/moby)
[![Go Report Card](https://goreportcard.com/badge/github.com/moby/moby/v2)](https://goreportcard.com/report/github.com/moby/moby/v2)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/docker/docker)](https://pkg.go.dev/github.com/docker/docker)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/docker)](https://goreportcard.com/report/github.com/docker/docker)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/moby/moby/badge)](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10989/badge)](https://www.bestpractices.dev/projects/10989)
![Moby Project logo](docs/static_files/moby-project-logo.png "The Moby Project")

View File

@@ -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.

View File

@@ -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
View 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.

View File

@@ -1,18 +1,12 @@
# Engine API
[![PkgGoDev](https://pkg.go.dev/badge/github.com/moby/moby/api)](https://pkg.go.dev/github.com/moby/moby/api)
![GitHub License](https://img.shields.io/github/license/moby/moby)
[![Go Report Card](https://goreportcard.com/badge/github.com/moby/moby/api)](https://goreportcard.com/report/github.com/moby/moby/api)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/moby/moby/badge)](https://scorecard.dev/viewer/?uri=github.com/moby/moby)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10989/badge)](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
View 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"
)

File diff suppressed because it is too large Load Diff

View File

@@ -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).

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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=

View File

@@ -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 &registry.AuthConfig{}, nil
}
decoded, err := base64.URLEncoding.DecodeString(authEncoded)
if err != nil {
var e base64.CorruptInputError
if errors.As(err, &e) {
return &registry.AuthConfig{}, invalid(errors.New("must be a valid base64url-encoded string"))
}
return &registry.AuthConfig{}, invalid(err)
}
if bytes.Equal(decoded, []byte("{}")) {
return &registry.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 := &registry.AuthConfig{}
if err := json.NewDecoder(r).Decode(authConfig); err != nil {
// always return an (empty) AuthConfig to increase compatibility with
// the existing API.
return &registry.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 }

View File

@@ -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)
}
}
})
}
}

View File

@@ -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
}
}

View File

@@ -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.
"""

View 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
}

View File

@@ -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"
)

View 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)
}

View File

@@ -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 != "" {

View File

@@ -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)

View 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
}

View File

@@ -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)
}

View 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)
}
}
}

View 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))
})
}
}

View 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)
}
}

View 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))
}

View 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
}

View 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
}

View File

@@ -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),

View 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)
}

View 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),
}
}

View 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
}

View 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
}

View 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),
}
}

View File

@@ -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,7 +82,7 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
return err
}
config := &backend.ContainerListOptions{
config := &container.ListOptions{
All: httputils.BoolValue(r, "all"),
Size: httputils.BoolValue(r, "size"),
Since: r.Form.Get("since"),
@@ -138,12 +119,6 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
}
}
if versions.LessThan(version, "1.52") {
for _, c := range containers {
c.Health = nil
}
}
return httputils.WriteJSON(w, http.StatusOK, containers)
}
@@ -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,49 +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
}
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.Equal(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. Starting with API 1.53, such bindings will be discarded.", strings.Join(emptyPBs, ", "))
}
return ""
}
// epConfigForNetMode finds, or creates, an entry in netConfig.EndpointsConfig
// corresponding to nwMode.
//
@@ -1160,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
}

View File

@@ -4,10 +4,9 @@ import (
"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"
)
@@ -16,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,
}
@@ -141,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)
@@ -155,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))
})
}
}

View 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)
}

View 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)
}

View 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)
}

View File

@@ -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"
)

View 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
}
}

View 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)
}

View File

@@ -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),
}
}

View File

@@ -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
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"net/http"
"github.com/moby/moby/v2/daemon/server/httputils"
"github.com/docker/docker/api/server/httputils"
)
// ExperimentalRoute defines an experimental API route that can be enabled or disabled.

View File

@@ -1,3 +1,6 @@
// 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 (
@@ -8,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"

View 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)
}

View 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),
}
}

View File

@@ -0,0 +1,602 @@
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.
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 != "" {
// the new format is to handle the authConfig as a header. Ignore invalid
// AuthConfig to increase compatibility with the existing API.
authConfig, _ = registry.DecodeAuthConfig(authEncoded)
} else {
// the old format is supported for compatibility if there was no authConfig header
var err error
authConfig, err = registry.DecodeAuthConfigBody(r.Body)
if err != nil {
return errors.Wrap(err, "bad parameters and missing X-Registry-Auth")
}
}
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
}

View 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)
}

View File

@@ -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 }

View 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...)
}

View 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
}

View 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),
}
}

View File

@@ -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

View File

@@ -0,0 +1,29 @@
package plugin
import (
"context"
"io"
"net/http"
"github.com/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/plugin"
)
// Backend for Plugin
type Backend interface {
Disable(name string, config *backend.PluginDisableConfig) error
Enable(name string, config *backend.PluginEnableConfig) error
List(filters.Args) ([]types.Plugin, error)
Inspect(name string) (*types.Plugin, error)
Remove(name string, config *backend.PluginRmConfig) error
Set(name string, args []string) error
Privileges(ctx context.Context, ref reference.Named, metaHeaders http.Header, authConfig *registry.AuthConfig) (types.PluginPrivileges, error)
Pull(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer, opts ...plugin.CreateOpt) error
Push(ctx context.Context, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, outStream io.Writer) error
Upgrade(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) error
CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) error
}

View File

@@ -0,0 +1,39 @@
package plugin
import "github.com/docker/docker/api/server/router"
// pluginRouter is a router to talk with the plugin controller
type pluginRouter struct {
backend Backend
routes []router.Route
}
// NewRouter initializes a new plugin router
func NewRouter(b Backend) router.Router {
r := &pluginRouter{
backend: b,
}
r.initRoutes()
return r
}
// Routes returns the available routers to the plugin controller
func (pr *pluginRouter) Routes() []router.Route {
return pr.routes
}
func (pr *pluginRouter) initRoutes() {
pr.routes = []router.Route{
router.NewGetRoute("/plugins", pr.listPlugins),
router.NewGetRoute("/plugins/{name:.*}/json", pr.inspectPlugin),
router.NewGetRoute("/plugins/privileges", pr.getPrivileges),
router.NewDeleteRoute("/plugins/{name:.*}", pr.removePlugin),
router.NewPostRoute("/plugins/{name:.*}/enable", pr.enablePlugin),
router.NewPostRoute("/plugins/{name:.*}/disable", pr.disablePlugin),
router.NewPostRoute("/plugins/pull", pr.pullPlugin),
router.NewPostRoute("/plugins/{name:.*}/push", pr.pushPlugin),
router.NewPostRoute("/plugins/{name:.*}/upgrade", pr.upgradePlugin),
router.NewPostRoute("/plugins/{name:.*}/set", pr.setPlugin),
router.NewPostRoute("/plugins/create", pr.createPlugin),
}
}

View File

@@ -7,14 +7,13 @@ import (
"strings"
"github.com/distribution/reference"
"github.com/moby/moby/api/pkg/authconfig"
"github.com/moby/moby/api/types/plugin"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/v2/daemon/internal/filters"
"github.com/moby/moby/v2/daemon/internal/streamformatter"
"github.com/moby/moby/v2/daemon/server/backend"
"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"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/streamformatter"
"github.com/pkg/errors"
)
@@ -27,7 +26,7 @@ func parseHeaders(headers http.Header) (map[string][]string, *registry.AuthConfi
}
// Ignore invalid AuthConfig to increase compatibility with the existing API.
authConfig, _ := authconfig.Decode(headers.Get(registry.AuthHeader))
authConfig, _ := registry.DecodeAuthConfig(headers.Get(registry.AuthHeader))
return metaHeaders, authConfig
}
@@ -85,7 +84,7 @@ func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter
return errors.Wrap(err, "failed to parse form")
}
var privileges plugin.Privileges
var privileges types.PluginPrivileges
if err := httputils.ReadJSON(r, &privileges); err != nil {
return err
}
@@ -120,7 +119,7 @@ func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r
return errors.Wrap(err, "failed to parse form")
}
var privileges plugin.Privileges
var privileges types.PluginPrivileges
if err := httputils.ReadJSON(r, &privileges); err != nil {
return err
}
@@ -187,10 +186,11 @@ func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter,
return err
}
err := pr.backend.CreateFromContext(ctx, r.Body, &backend.PluginCreateConfig{
options := &types.PluginCreateOptions{
RepoName: r.FormValue("name"),
})
if err != nil {
}
if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
return err
}
// TODO: send progress bar
@@ -203,13 +203,14 @@ func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter,
return err
}
name := vars["name"]
timeout, err := strconv.Atoi(r.Form.Get("timeout"))
if err != nil {
return err
}
config := &backend.PluginEnableConfig{Timeout: timeout}
name := vars["name"]
return pr.backend.Enable(name, &backend.PluginEnableConfig{Timeout: timeout})
return pr.backend.Enable(name, config)
}
func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -217,9 +218,12 @@ func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter
return err
}
return pr.backend.Disable(vars["name"], &backend.PluginDisableConfig{
name := vars["name"]
config := &backend.PluginDisableConfig{
ForceDisable: httputils.BoolValue(r, "force"),
})
}
return pr.backend.Disable(name, config)
}
func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -228,9 +232,10 @@ func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter,
}
name := vars["name"]
return pr.backend.Remove(name, &backend.PluginRmConfig{
config := &backend.PluginRmConfig{
ForceRemove: httputils.BoolValue(r, "force"),
})
}
return pr.backend.Remove(name, config)
}
func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View File

@@ -1,6 +1,6 @@
package router
import "github.com/moby/moby/v2/daemon/server/httputils"
import "github.com/docker/docker/api/server/httputils"
// Router defines an interface to specify a group of routes to add to the docker server.
type Router interface {

View File

@@ -1,6 +1,6 @@
package session
import "github.com/moby/moby/v2/daemon/server/router"
import "github.com/docker/docker/api/server/router"
// sessionRouter is a router to talk with the session controller
type sessionRouter struct {

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