mirror of
https://github.com/moby/moby.git
synced 2026-01-12 03:01:38 +00:00
Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ffc61430b | ||
|
|
d3893b58ff | ||
|
|
1d9c8619cd | ||
|
|
64f79562fb | ||
|
|
05cf8e8130 | ||
|
|
5892aae60f | ||
|
|
7adb590e16 | ||
|
|
b5aacf8161 | ||
|
|
b732cfd392 | ||
|
|
50fb65f0f5 | ||
|
|
32bcbdfe65 | ||
|
|
f66ef31605 | ||
|
|
acb95e4544 | ||
|
|
335ed29345 | ||
|
|
0ef846ce2e | ||
|
|
4a1747d2e4 | ||
|
|
af25852baa | ||
|
|
7a9c831e6a | ||
|
|
649bb2b9b8 | ||
|
|
457399013b | ||
|
|
3bd0f582c9 | ||
|
|
be50480621 | ||
|
|
016ad9b3e8 | ||
|
|
87778af711 | ||
|
|
8bf037b246 | ||
|
|
8afe75ffa9 | ||
|
|
e2bade43e7 | ||
|
|
e0091d6616 | ||
|
|
42f3f7ed86 | ||
|
|
aace62f6d3 | ||
|
|
bb50485dfd | ||
|
|
5dcea89ce1 | ||
|
|
01eb4835c9 | ||
|
|
cd44aba8db | ||
|
|
2435d75b89 | ||
|
|
80d1e863f5 | ||
|
|
ee29fd944b | ||
|
|
b8ee9a7829 | ||
|
|
d9e097e328 | ||
|
|
2bef272269 | ||
|
|
3f9d07570a | ||
|
|
806849eb62 | ||
|
|
c24c37bd8a | ||
|
|
c306276ab1 | ||
|
|
6eb4d7f33b | ||
|
|
186eb805f6 | ||
|
|
d5e31e03b6 | ||
|
|
85ad299668 | ||
|
|
4735ce7ff2 | ||
|
|
e84365f967 | ||
|
|
5899e935d4 | ||
|
|
4d5f1d6bbc | ||
|
|
96534f015d | ||
|
|
49e24566d0 | ||
|
|
6424ae830b | ||
|
|
6055b07292 | ||
|
|
98518e0734 | ||
|
|
2f379ecfd6 | ||
|
|
8b61625a5e | ||
|
|
575d03df66 | ||
|
|
a13eea29fb | ||
|
|
136893e33b | ||
|
|
290fc0440c | ||
|
|
0556ba23a4 | ||
|
|
35a29c7328 | ||
|
|
6bca2bf3bf | ||
|
|
210c4d6f4b | ||
|
|
f50cb0c7bd | ||
|
|
0a6a5a9140 | ||
|
|
f3743766e9 | ||
|
|
e6a7df0e00 | ||
|
|
d3c5b613ac | ||
|
|
7ed0771d20 | ||
|
|
6285ec378c | ||
|
|
c92fd5220a | ||
|
|
aaa8a90747 | ||
|
|
5e48bbd14c | ||
|
|
6776279896 | ||
|
|
7db3243e34 | ||
|
|
aec7a80c6f | ||
|
|
d7aa1e14e5 | ||
|
|
5652c59647 | ||
|
|
458af2b1e0 | ||
|
|
58729344aa | ||
|
|
3d96894184 | ||
|
|
789a8755b8 | ||
|
|
f7298b326e | ||
|
|
1c18ad6ca6 | ||
|
|
ae4a10df67 | ||
|
|
24c882c3e0 | ||
|
|
03a0ee4202 | ||
|
|
df620567eb | ||
|
|
b3133d7471 | ||
|
|
8c552012ae | ||
|
|
aa47b29dbc | ||
|
|
61d547fd06 | ||
|
|
e5fbc3f75a | ||
|
|
1a078977e1 | ||
|
|
c4198e6053 | ||
|
|
8e70a1b23e | ||
|
|
c671434cd2 | ||
|
|
647ba03224 | ||
|
|
2f65bb7bb5 | ||
|
|
961fe27408 | ||
|
|
087cf6f238 | ||
|
|
0b9d68f59d | ||
|
|
cbf0779bfc | ||
|
|
0139309fef | ||
|
|
18278d3dc1 | ||
|
|
e1c7956764 | ||
|
|
4b3329d3dd | ||
|
|
32d442aee1 | ||
|
|
75afe3201b | ||
|
|
8018ee4689 | ||
|
|
ed376a603f | ||
|
|
1d45ea52f4 | ||
|
|
a27b0381a6 | ||
|
|
1fc19772e0 | ||
|
|
9bdb6adf92 | ||
|
|
7dbab75fec | ||
|
|
e7b1501832 | ||
|
|
4217d9ea0a | ||
|
|
4c6b8e737f | ||
|
|
e370f224ae | ||
|
|
ac1a867282 | ||
|
|
2949fee1d3 | ||
|
|
7861aa7e80 | ||
|
|
ebe29481ec | ||
|
|
f9c68e5fbc | ||
|
|
3452a76589 | ||
|
|
fec801a103 | ||
|
|
143a25144a | ||
|
|
f5899cc1f6 | ||
|
|
d9e39914a7 | ||
|
|
0a59892a88 | ||
|
|
042f0799db | ||
|
|
ec8ec9056c | ||
|
|
659604f9ee | ||
|
|
6660133ffb | ||
|
|
67b3563d09 | ||
|
|
7a4ea19803 | ||
|
|
ae6e9333c0 | ||
|
|
0d9acd24fe | ||
|
|
37bc639704 | ||
|
|
04eccf8165 | ||
|
|
24722779ff | ||
|
|
9d8acb7bd1 | ||
|
|
4b78458e4b | ||
|
|
d64bab35ee | ||
|
|
329d671aef | ||
|
|
4cc2081119 | ||
|
|
27df42255c | ||
|
|
9ee7d30aef | ||
|
|
8a4b7c5af8 | ||
|
|
7d50989467 | ||
|
|
a753ca64e2 | ||
|
|
ac1c329245 | ||
|
|
5276c2b6e0 | ||
|
|
1b0d37bdc2 | ||
|
|
baf1fd1c3f | ||
|
|
992dc33fc5 | ||
|
|
ef1545ed4a | ||
|
|
876f5eda51 | ||
|
|
463850e59e | ||
|
|
47a3dad256 | ||
|
|
a0bc3ebae4 | ||
|
|
922b6aa672 | ||
|
|
0e605cf972 | ||
|
|
878c41791b | ||
|
|
654e80abc2 | ||
|
|
0869b089e4 | ||
|
|
3467ba6451 | ||
|
|
f9b886c01b | ||
|
|
07140c0eca | ||
|
|
d5ad186d49 | ||
|
|
4d924c35f7 | ||
|
|
ea662c5c8a | ||
|
|
68b7ba0d03 | ||
|
|
821e4ec4c7 | ||
|
|
5ea7b8d091 | ||
|
|
1331b8c39a | ||
|
|
907f037141 | ||
|
|
a5b597ea51 | ||
|
|
8bbfa32741 | ||
|
|
807e415260 | ||
|
|
8587a1c617 | ||
|
|
9717369913 | ||
|
|
ed0c147c8f | ||
|
|
90be9ab802 | ||
|
|
d73f7031e0 | ||
|
|
ea7f7f168e | ||
|
|
233c49438b | ||
|
|
2b7424512a | ||
|
|
f77a3274b4 | ||
|
|
c76bb6a3a3 | ||
|
|
71846e82c1 | ||
|
|
ecbc27aa22 | ||
|
|
c01f02cfcb | ||
|
|
ce79cd19f6 | ||
|
|
1235338836 | ||
|
|
763d2b7996 | ||
|
|
e9eff01dca | ||
|
|
69ef9a7f90 | ||
|
|
86770904be | ||
|
|
31b98f9502 | ||
|
|
bfffb0974e | ||
|
|
e28bc0d271 | ||
|
|
d169a57306 | ||
|
|
63640838ba | ||
|
|
269e55a915 | ||
|
|
012dd239ce |
2
.github/actions/setup-runner/action.yml
vendored
2
.github/actions/setup-runner/action.yml
vendored
@@ -13,7 +13,7 @@ runs:
|
||||
shell: bash
|
||||
- run: |
|
||||
if [ ! -e /etc/docker/daemon.json ]; then
|
||||
echo '{}' | tee /etc/docker/daemon.json >/dev/null
|
||||
echo '{}' | sudo tee /etc/docker/daemon.json >/dev/null
|
||||
fi
|
||||
DOCKERD_CONFIG=$(jq '.+{"experimental":true,"live-restore":true,"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' /etc/docker/daemon.json)
|
||||
sudo tee /etc/docker/daemon.json <<<"$DOCKERD_CONFIG" >/dev/null
|
||||
|
||||
2
.github/workflows/.windows.yml
vendored
2
.github/workflows/.windows.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
default: false
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.20.4"
|
||||
GO_VERSION: "1.20.5"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.3
|
||||
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
|
||||
|
||||
110
.github/workflows/bin-image.yml
vendored
Normal file
110
.github/workflows/bin-image.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: bin-image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '[0-9]+.[0-9]+'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PLATFORM: Moby Engine
|
||||
PRODUCT: Moby
|
||||
DEFAULT_PRODUCT_LICENSE: Moby
|
||||
PACKAGER_NAME: Moby
|
||||
|
||||
jobs:
|
||||
validate-dco:
|
||||
uses: ./.github/workflows/.dco.yml
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.matrix }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Create platforms matrix
|
||||
id: platforms
|
||||
run: |
|
||||
echo "matrix=$(docker buildx bake bin-image-cross --print | jq -cr '.target."bin-image-cross".platforms')" >>${GITHUB_OUTPUT}
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: moby-bin
|
||||
### versioning strategy
|
||||
## push semver tag v23.0.0
|
||||
# moby/moby-bin:23.0.0
|
||||
# moby/moby-bin:latest
|
||||
## push semver prelease tag v23.0.0-beta.1
|
||||
# moby/moby-bin:23.0.0-beta.1
|
||||
## push on master
|
||||
# moby/moby-bin:master
|
||||
## push on 23.0 branch
|
||||
# moby/moby-bin:23.0
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
-
|
||||
name: Rename meta bake definition file
|
||||
run: |
|
||||
mv "${{ steps.meta.outputs.bake-file }}" "/tmp/bake-meta.json"
|
||||
-
|
||||
name: Upload meta bake definition
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bake-meta
|
||||
path: /tmp/bake-meta.json
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- validate-dco
|
||||
- prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Download meta bake definition
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: bake-meta
|
||||
path: /tmp
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
/tmp/bake-meta.json
|
||||
targets: bin-image
|
||||
set: |
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.output=type=cacheonly
|
||||
2
.github/workflows/buildkit.yml
vendored
2
.github/workflows/buildkit.yml
vendored
@@ -106,7 +106,7 @@ jobs:
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
sudo rm /etc/docker/daemon.json
|
||||
sudo rm -f /etc/docker/daemon.json
|
||||
sudo service docker restart
|
||||
docker version
|
||||
docker info
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.20.4"
|
||||
GO_VERSION: "1.20.5"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.3
|
||||
ITG_CLI_MATRIX_SIZE: 6
|
||||
|
||||
119
Dockerfile
119
Dockerfile
@@ -1,12 +1,18 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.20.4
|
||||
ARG GO_VERSION=1.20.5
|
||||
ARG BASE_DEBIAN_DISTRO="bullseye"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
ARG XX_VERSION=1.2.1
|
||||
|
||||
ARG VPNKIT_VERSION=0.5.0
|
||||
ARG DOCKERCLI_VERSION=v17.06.2-ce
|
||||
|
||||
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_VERSION=v24.0.2
|
||||
# cli version used for integration-cli tests
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v17.06.2-ce
|
||||
ARG BUILDX_VERSION=0.11.0
|
||||
|
||||
ARG SYSTEMD="false"
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
@@ -192,7 +198,7 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
|
||||
# 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.0
|
||||
ARG CONTAINERD_VERSION=v1.7.1
|
||||
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS containerd-build
|
||||
@@ -243,34 +249,29 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
GOBIN=/build/ GO111MODULE=on go install "mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION}" \
|
||||
&& /build/shfmt --version
|
||||
|
||||
# dockercli
|
||||
FROM base AS dockercli-src
|
||||
WORKDIR /tmp/dockercli
|
||||
RUN git init . && git remote add origin "https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_VERSION
|
||||
RUN git fetch -q --depth 1 origin "${DOCKERCLI_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
RUN [ -d ./components/cli ] && mv ./components/cli /usr/src/dockercli || mv /tmp/dockercli /usr/src/dockercli
|
||||
WORKDIR /usr/src/dockercli
|
||||
|
||||
FROM base AS dockercli
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
COPY hack/dockerfile/cli.sh /download-or-build-cli.sh
|
||||
ARG DOCKERCLI_REPOSITORY
|
||||
ARG DOCKERCLI_VERSION
|
||||
ARG DOCKERCLI_CHANNEL=stable
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apt-get install -y --no-install-recommends gcc libc6-dev
|
||||
RUN --mount=from=dockercli-src,src=/usr/src/dockercli,rw \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=dockercli-build-$TARGETPLATFORM <<EOT
|
||||
set -e
|
||||
DOWNLOAD_URL="https://download.docker.com/linux/static/${DOCKERCLI_CHANNEL}/$(xx-info march)/docker-${DOCKERCLI_VERSION#v}.tgz"
|
||||
if curl --head --silent --fail "${DOWNLOAD_URL}" 1>/dev/null 2>&1; then
|
||||
mkdir /build
|
||||
curl -Ls "${DOWNLOAD_URL}" | tar -xz docker/docker
|
||||
mv docker/docker /build/docker
|
||||
else
|
||||
CGO_ENABLED=0 xx-go build -o /build/docker ./cmd/docker
|
||||
fi
|
||||
xx-verify /build/docker
|
||||
EOT
|
||||
RUN --mount=type=cache,id=dockercli-git-$TARGETPLATFORM,sharing=locked,target=./.git \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=dockercli-build-$TARGETPLATFORM \
|
||||
rm -f ./.git/*.lock \
|
||||
&& /download-or-build-cli.sh ${DOCKERCLI_VERSION} ${DOCKERCLI_REPOSITORY} /build \
|
||||
&& /build/docker --version
|
||||
|
||||
FROM base AS dockercli-integration
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
COPY hack/dockerfile/cli.sh /download-or-build-cli.sh
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=cache,id=dockercli-integration-git-$TARGETPLATFORM,sharing=locked,target=./.git \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=dockercli-integration-build-$TARGETPLATFORM \
|
||||
rm -f ./.git/*.lock \
|
||||
&& /download-or-build-cli.sh ${DOCKERCLI_INTEGRATION_VERSION} ${DOCKERCLI_INTEGRATION_REPOSITORY} /build \
|
||||
&& /build/docker --version
|
||||
|
||||
# runc
|
||||
FROM base AS runc-src
|
||||
@@ -437,28 +438,36 @@ FROM binary-dummy AS containerutil-linux
|
||||
FROM containerutil-build AS containerutil-windows-amd64
|
||||
FROM containerutil-windows-${TARGETARCH} AS containerutil-windows
|
||||
FROM containerutil-${TARGETOS} AS containerutil
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} as buildx
|
||||
|
||||
FROM base AS dev-systemd-false
|
||||
COPY --from=dockercli /build/ /usr/local/cli
|
||||
COPY --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --from=swagger /build/ /usr/local/bin/
|
||||
COPY --from=delve /build/ /usr/local/bin/
|
||||
COPY --from=tomll /build/ /usr/local/bin/
|
||||
COPY --from=gowinres /build/ /usr/local/bin/
|
||||
COPY --from=tini /build/ /usr/local/bin/
|
||||
COPY --from=registry /build/ /usr/local/bin/
|
||||
COPY --from=criu /build/ /usr/local/bin/
|
||||
COPY --from=gotestsum /build/ /usr/local/bin/
|
||||
COPY --from=golangci_lint /build/ /usr/local/bin/
|
||||
COPY --from=shfmt /build/ /usr/local/bin/
|
||||
COPY --from=runc /build/ /usr/local/bin/
|
||||
COPY --from=containerd /build/ /usr/local/bin/
|
||||
COPY --from=rootlesskit /build/ /usr/local/bin/
|
||||
COPY --from=vpnkit / /usr/local/bin/
|
||||
COPY --from=containerutil /build/ /usr/local/bin/
|
||||
COPY --from=crun /build/ /usr/local/bin/
|
||||
COPY hack/dockerfile/etc/docker/ /etc/docker/
|
||||
COPY --link --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --link --from=swagger /build/ /usr/local/bin/
|
||||
COPY --link --from=delve /build/ /usr/local/bin/
|
||||
COPY --link --from=tomll /build/ /usr/local/bin/
|
||||
COPY --link --from=gowinres /build/ /usr/local/bin/
|
||||
COPY --link --from=tini /build/ /usr/local/bin/
|
||||
COPY --link --from=registry /build/ /usr/local/bin/
|
||||
|
||||
# Skip the CRIU stage for now, as the opensuse package repository is sometimes
|
||||
# unstable, and we're currently not using it in CI.
|
||||
#
|
||||
# FIXME(thaJeztah): re-enable this stage when https://github.com/moby/moby/issues/38963 is resolved (see https://github.com/moby/moby/pull/38984)
|
||||
# COPY --link --from=criu /build/ /usr/local/bin/
|
||||
COPY --link --from=gotestsum /build/ /usr/local/bin/
|
||||
COPY --link --from=golangci_lint /build/ /usr/local/bin/
|
||||
COPY --link --from=shfmt /build/ /usr/local/bin/
|
||||
COPY --link --from=runc /build/ /usr/local/bin/
|
||||
COPY --link --from=containerd /build/ /usr/local/bin/
|
||||
COPY --link --from=rootlesskit /build/ /usr/local/bin/
|
||||
COPY --link --from=vpnkit / /usr/local/bin/
|
||||
COPY --link --from=containerutil /build/ /usr/local/bin/
|
||||
COPY --link --from=crun /build/ /usr/local/bin/
|
||||
COPY --link hack/dockerfile/etc/docker/ /etc/docker/
|
||||
COPY --link --from=buildx /buildx /usr/local/libexec/docker/cli-plugins/docker-buildx
|
||||
|
||||
ENV PATH=/usr/local/cli:$PATH
|
||||
ENV TEST_CLIENT_BINARY=/usr/local/cli-integration/docker
|
||||
ENV CONTAINERD_ADDRESS=/run/docker/containerd/containerd.sock
|
||||
ENV CONTAINERD_NAMESPACE=moby
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
@@ -544,6 +553,8 @@ RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
libsecret-1-dev \
|
||||
libsystemd-dev \
|
||||
libudev-dev
|
||||
COPY --link --from=dockercli /build/ /usr/local/cli
|
||||
COPY --link --from=dockercli-integration /build/ /usr/local/cli-integration
|
||||
|
||||
FROM base AS build
|
||||
COPY --from=gowinres /build/ /usr/local/bin/
|
||||
@@ -615,13 +626,13 @@ COPY --from=build /build/ /
|
||||
# usage:
|
||||
# > docker buildx bake all
|
||||
FROM scratch AS all
|
||||
COPY --from=tini /build/ /
|
||||
COPY --from=runc /build/ /
|
||||
COPY --from=containerd /build/ /
|
||||
COPY --from=rootlesskit /build/ /
|
||||
COPY --from=containerutil /build/ /
|
||||
COPY --from=vpnkit / /
|
||||
COPY --from=build /build /
|
||||
COPY --link --from=tini /build/ /
|
||||
COPY --link --from=runc /build/ /
|
||||
COPY --link --from=containerd /build/ /
|
||||
COPY --link --from=rootlesskit /build/ /
|
||||
COPY --link --from=containerutil /build/ /
|
||||
COPY --link --from=vpnkit / /
|
||||
COPY --link --from=build /build /
|
||||
|
||||
# smoke tests
|
||||
# usage:
|
||||
@@ -641,4 +652,4 @@ EOT
|
||||
# > make shell
|
||||
# > SYSTEMD=true make shell
|
||||
FROM dev-base AS dev
|
||||
COPY . .
|
||||
COPY --link . .
|
||||
|
||||
@@ -71,8 +71,8 @@ RUN apk --no-cache add \
|
||||
tar \
|
||||
xz
|
||||
|
||||
COPY hack/test/e2e-run.sh /scripts/run.sh
|
||||
COPY hack/make/.ensure-emptyfs /scripts/ensure-emptyfs.sh
|
||||
COPY hack/test/e2e-run.sh /scripts/run.sh
|
||||
COPY hack/make/.build-empty-images /scripts/build-empty-images.sh
|
||||
|
||||
COPY integration/testdata /tests/integration/testdata
|
||||
COPY integration/build/testdata /tests/integration/build/testdata
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# This represents the bare minimum required to build and test Docker.
|
||||
|
||||
ARG GO_VERSION=1.20.4
|
||||
ARG GO_VERSION=1.20.5
|
||||
|
||||
ARG BASE_DEBIAN_DISTRO="bullseye"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
||||
@@ -165,10 +165,10 @@ FROM microsoft/windowsservercore
|
||||
# Use PowerShell as the default shell
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
ARG GO_VERSION=1.20.4
|
||||
ARG GO_VERSION=1.20.5
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
ARG GOWINRES_VERSION=v0.3.0
|
||||
ARG CONTAINERD_VERSION=v1.7.0
|
||||
ARG CONTAINERD_VERSION=v1.7.1
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
|
||||
8
Makefile
8
Makefile
@@ -41,6 +41,10 @@ DOCKER_ENVS := \
|
||||
-e DOCKER_BUILDKIT \
|
||||
-e DOCKER_BASH_COMPLETION_PATH \
|
||||
-e DOCKER_CLI_PATH \
|
||||
-e DOCKERCLI_VERSION \
|
||||
-e DOCKERCLI_REPOSITORY \
|
||||
-e DOCKERCLI_INTEGRATION_VERSION \
|
||||
-e DOCKERCLI_INTEGRATION_REPOSITORY \
|
||||
-e DOCKER_DEBUG \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT \
|
||||
@@ -136,6 +140,10 @@ endif
|
||||
DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
|
||||
DOCKER_BUILD_ARGS += --build-arg=GO_VERSION
|
||||
DOCKER_BUILD_ARGS += --build-arg=DOCKERCLI_VERSION
|
||||
DOCKER_BUILD_ARGS += --build-arg=DOCKERCLI_REPOSITORY
|
||||
DOCKER_BUILD_ARGS += --build-arg=DOCKERCLI_INTEGRATION_VERSION
|
||||
DOCKER_BUILD_ARGS += --build-arg=DOCKERCLI_INTEGRATION_REPOSITORY
|
||||
ifdef DOCKER_SYSTEMD
|
||||
DOCKER_BUILD_ARGS += --build-arg=SYSTEMD=true
|
||||
endif
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
containerpkg "github.com/docker/docker/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/websocket"
|
||||
@@ -44,7 +44,7 @@ func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter,
|
||||
}
|
||||
|
||||
config, _, _, err := s.decoder.DecodeConfig(r.Body)
|
||||
if err != nil && err != io.EOF { // Do not fail if body is empty.
|
||||
if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty.
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -486,6 +486,9 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
|
||||
config, hostConfig, networkingConfig, err := s.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
|
||||
}
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
@@ -566,7 +569,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
hostConfig.Annotations = nil
|
||||
}
|
||||
|
||||
var platform *specs.Platform
|
||||
var platform *ocispec.Platform
|
||||
if versions.GreaterThanOrEqualTo(version, "1.41") {
|
||||
if v := r.Form.Get("platform"); v != "" {
|
||||
p, err := platforms.Parse(v)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/errdefs"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
distributionInspect.Descriptor = v1.Descriptor{
|
||||
distributionInspect.Descriptor = ocispec.Descriptor{
|
||||
MediaType: descriptor.MediaType,
|
||||
Digest: descriptor.Digest,
|
||||
Size: descriptor.Size,
|
||||
@@ -107,7 +107,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
||||
switch mnfstObj := mnfst.(type) {
|
||||
case *manifestlist.DeserializedManifestList:
|
||||
for _, m := range mnfstObj.Manifests {
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, v1.Platform{
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, ocispec.Platform{
|
||||
Architecture: m.Platform.Architecture,
|
||||
OS: m.Platform.OS,
|
||||
OSVersion: m.Platform.OSVersion,
|
||||
@@ -117,7 +117,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
||||
}
|
||||
case *schema2.DeserializedManifest:
|
||||
configJSON, err := blobsrvc.Get(ctx, mnfstObj.Config.Digest)
|
||||
var platform v1.Platform
|
||||
var platform ocispec.Platform
|
||||
if err == nil {
|
||||
err := json.Unmarshal(configJSON, &platform)
|
||||
if err == nil && (platform.OS != "" || platform.Architecture != "") {
|
||||
@@ -125,7 +125,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
||||
}
|
||||
}
|
||||
case *schema1.SignedManifest:
|
||||
platform := v1.Platform{
|
||||
platform := ocispec.Platform{
|
||||
Architecture: mnfstObj.Architecture,
|
||||
OS: "linux",
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Backend is all the methods that need to be implemented
|
||||
@@ -32,12 +32,12 @@ type imageBackend interface {
|
||||
|
||||
type importExportBackend interface {
|
||||
LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error
|
||||
ImportImage(ctx context.Context, ref reference.Named, platform *specs.Platform, msg string, layerReader io.Reader, changes []string) (dockerimage.ID, 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, outStream io.Writer) error
|
||||
}
|
||||
|
||||
type registryBackend interface {
|
||||
PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PullImage(ctx context.Context, image, tag string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrit
|
||||
comment = r.Form.Get("message")
|
||||
progressErr error
|
||||
output = ioutils.NewWriteFlusher(w)
|
||||
platform *specs.Platform
|
||||
platform *ocispec.Platform
|
||||
)
|
||||
defer output.Close()
|
||||
|
||||
@@ -282,6 +282,14 @@ func (ir *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, er
|
||||
comment = img.History[len(img.History)-1].Comment
|
||||
}
|
||||
|
||||
// Make sure we output empty arrays instead of nil.
|
||||
if repoTags == nil {
|
||||
repoTags = []string{}
|
||||
}
|
||||
if repoDigests == nil {
|
||||
repoDigests = []string{}
|
||||
}
|
||||
|
||||
return &types.ImageInspect{
|
||||
ID: img.ID().String(),
|
||||
RepoTags: repoTags,
|
||||
|
||||
@@ -5162,42 +5162,8 @@ definitions:
|
||||
ServerVersion:
|
||||
description: |
|
||||
Version string of the daemon.
|
||||
|
||||
> **Note**: the [standalone Swarm API](https://docs.docker.com/swarm/swarm-api/)
|
||||
> returns the Swarm version instead of the daemon version, for example
|
||||
> `swarm/1.2.8`.
|
||||
type: "string"
|
||||
example: "17.06.0-ce"
|
||||
ClusterStore:
|
||||
description: |
|
||||
URL of the distributed storage backend.
|
||||
|
||||
|
||||
The storage backend is used for multihost networking (to store
|
||||
network and endpoint information) and by the node discovery mechanism.
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: This field is only propagated when using standalone Swarm
|
||||
> mode, and overlay networking using an external k/v store. Overlay
|
||||
> networks with Swarm mode enabled use the built-in raft store, and
|
||||
> this field will be empty.
|
||||
type: "string"
|
||||
example: "consul://consul.corp.example.com:8600/some/path"
|
||||
ClusterAdvertise:
|
||||
description: |
|
||||
The network endpoint that the Engine advertises for the purpose of
|
||||
node discovery. ClusterAdvertise is a `host:port` combination on which
|
||||
the daemon is reachable by other hosts.
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: This field is only propagated when using standalone Swarm
|
||||
> mode, and overlay networking using an external k/v store. Overlay
|
||||
> networks with Swarm mode enabled use the built-in raft store, and
|
||||
> this field will be empty.
|
||||
type: "string"
|
||||
example: "node5.corp.example.com:8000"
|
||||
example: "24.0.2"
|
||||
Runtimes:
|
||||
description: |
|
||||
List of [OCI compliant](https://github.com/opencontainers/runtime-spec)
|
||||
@@ -10393,6 +10359,12 @@ paths:
|
||||
default if omitted.
|
||||
required: true
|
||||
type: "string"
|
||||
- name: "force"
|
||||
in: "query"
|
||||
description: |
|
||||
Force disable a plugin even if still in use.
|
||||
required: false
|
||||
type: "boolean"
|
||||
tags: ["Plugin"]
|
||||
/plugins/{name}/upgrade:
|
||||
post:
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// PullOption defines different modes for accessing images
|
||||
@@ -42,5 +42,5 @@ type GetImageAndLayerOptions struct {
|
||||
PullOption PullOption
|
||||
AuthConfig map[string]registry.AuthConfig
|
||||
Output io.Writer
|
||||
Platform *specs.Platform
|
||||
Platform *ocispec.Platform
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package types // import "github.com/docker/docker/api/types"
|
||||
import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// configs holds structs used for internal communication between the
|
||||
@@ -16,7 +16,7 @@ type ContainerCreateConfig struct {
|
||||
Config *container.Config
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
Platform *specs.Platform
|
||||
Platform *ocispec.Platform
|
||||
AdjustCPUShares bool
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package image
|
||||
|
||||
import specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
// GetImageOpts holds parameters to inspect an image.
|
||||
type GetImageOpts struct {
|
||||
Platform *specs.Platform
|
||||
Platform *ocispec.Platform
|
||||
Details bool
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ServiceConfig stores daemon registry services configuration.
|
||||
@@ -113,8 +113,8 @@ type SearchResults struct {
|
||||
type DistributionInspect struct {
|
||||
// Descriptor contains information about the manifest, including
|
||||
// the content addressable digest
|
||||
Descriptor v1.Descriptor
|
||||
Descriptor ocispec.Descriptor
|
||||
// Platforms contains the list of platforms supported by the image,
|
||||
// obtained by parsing the manifest
|
||||
Platforms []v1.Platform
|
||||
Platforms []ocispec.Platform
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/moby/buildkit/solver"
|
||||
"github.com/moby/buildkit/worker"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
func ResolveCacheImporterFunc(sm *session.Manager, resolverFunc docker.RegistryHosts, cs content.Store, rs reference.Store, is imagestore.Store) remotecache.ResolveCacheImporterFunc {
|
||||
upstream := registryremotecache.ResolveCacheImporterFunc(sm, cs, resolverFunc)
|
||||
|
||||
return func(ctx context.Context, group session.Group, attrs map[string]string) (remotecache.Importer, specs.Descriptor, error) {
|
||||
return func(ctx context.Context, group session.Group, attrs map[string]string) (remotecache.Importer, ocispec.Descriptor, error) {
|
||||
if dt, err := tryImportLocal(rs, is, attrs["ref"]); err == nil {
|
||||
return newLocalImporter(dt), specs.Descriptor{}, nil
|
||||
return newLocalImporter(dt), ocispec.Descriptor{}, nil
|
||||
}
|
||||
return upstream(ctx, group, attrs)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ type localImporter struct {
|
||||
dt []byte
|
||||
}
|
||||
|
||||
func (li *localImporter) Resolve(ctx context.Context, _ specs.Descriptor, id string, w worker.Worker) (solver.CacheManager, error) {
|
||||
func (li *localImporter) Resolve(ctx context.Context, _ ocispec.Descriptor, id string, w worker.Worker) (solver.CacheManager, error) {
|
||||
cc := v1.NewCacheChains()
|
||||
if err := li.importInlineCache(ctx, li.dt, cc); err != nil {
|
||||
return nil, err
|
||||
@@ -96,7 +96,7 @@ func (li *localImporter) importInlineCache(ctx context.Context, dt []byte, cc so
|
||||
layers := v1.DescriptorProvider{}
|
||||
for i, diffID := range img.Rootfs.DiffIDs {
|
||||
dgst := digest.Digest(diffID.String())
|
||||
desc := specs.Descriptor{
|
||||
desc := ocispec.Descriptor{
|
||||
Digest: dgst,
|
||||
Size: -1,
|
||||
MediaType: images.MediaTypeDockerSchema2Layer,
|
||||
@@ -157,6 +157,6 @@ func parseCreatedLayerInfo(img image) ([]string, []string, error) {
|
||||
type emptyProvider struct {
|
||||
}
|
||||
|
||||
func (p *emptyProvider) ReaderAt(ctx context.Context, dec specs.Descriptor) (content.ReaderAt, error) {
|
||||
func (p *emptyProvider) ReaderAt(ctx context.Context, dec ocispec.Descriptor) (content.ReaderAt, error) {
|
||||
return nil, errors.Errorf("ReaderAt not implemented for empty provider")
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ var cacheFields = map[string]bool{
|
||||
type Opt struct {
|
||||
SessionManager *session.Manager
|
||||
Root string
|
||||
EngineID string
|
||||
Dist images.DistributionServices
|
||||
ImageTagger mobyexporter.ImageTagger
|
||||
NetworkController *libnetwork.Controller
|
||||
@@ -354,11 +355,7 @@ func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder.
|
||||
exporterName := ""
|
||||
exporterAttrs := map[string]string{}
|
||||
if len(opt.Options.Outputs) == 0 {
|
||||
if b.useSnapshotter {
|
||||
exporterName = client.ExporterImage
|
||||
} else {
|
||||
exporterName = exporter.Moby
|
||||
}
|
||||
exporterName = exporter.Moby
|
||||
} else {
|
||||
// cacheonly is a special type for triggering skipping all exporters
|
||||
if opt.Options.Outputs[0].Type != "cacheonly" {
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"github.com/docker/docker/builder/builder-next/adapters/containerimage"
|
||||
"github.com/docker/docker/builder/builder-next/adapters/localinlinecache"
|
||||
"github.com/docker/docker/builder/builder-next/adapters/snapshot"
|
||||
"github.com/docker/docker/builder/builder-next/exporter"
|
||||
"github.com/docker/docker/builder/builder-next/exporter/mobyexporter"
|
||||
"github.com/docker/docker/builder/builder-next/imagerefchecker"
|
||||
mobyworker "github.com/docker/docker/builder/builder-next/worker"
|
||||
wlabel "github.com/docker/docker/builder/builder-next/worker/label"
|
||||
"github.com/docker/docker/daemon/config"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
units "github.com/docker/go-units"
|
||||
@@ -96,6 +96,7 @@ func newSnapshotterController(ctx context.Context, rt http.RoundTripper, opt Opt
|
||||
|
||||
wo.GCPolicy = policy
|
||||
wo.RegistryHosts = opt.RegistryHosts
|
||||
wo.Labels = getLabels(opt, wo.Labels)
|
||||
|
||||
exec, err := newExecutor(opt.Root, opt.DefaultCgroupParent, opt.NetworkController, dns, opt.Rootless, opt.IdentityMapping, opt.ApparmorProfile)
|
||||
if err != nil {
|
||||
@@ -312,7 +313,7 @@ func newGraphDriverController(ctx context.Context, rt http.RoundTripper, opt Opt
|
||||
}
|
||||
|
||||
wopt := mobyworker.Opt{
|
||||
ID: exporter.Moby,
|
||||
ID: opt.EngineID,
|
||||
ContentStore: store,
|
||||
CacheManager: cm,
|
||||
GCPolicy: gcPolicy,
|
||||
@@ -326,6 +327,7 @@ func newGraphDriverController(ctx context.Context, rt http.RoundTripper, opt Opt
|
||||
Layers: layers,
|
||||
Platforms: archutil.SupportedPlatforms(true),
|
||||
LeaseManager: lm,
|
||||
Labels: getLabels(opt, nil),
|
||||
}
|
||||
|
||||
wc := &worker.Controller{}
|
||||
@@ -412,3 +414,11 @@ func getEntitlements(conf config.BuilderConfig) []string {
|
||||
}
|
||||
return ents
|
||||
}
|
||||
|
||||
func getLabels(opt Opt, labels map[string]string) map[string]string {
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
labels[wlabel.HostGatewayIP] = opt.DNSConfig.HostGatewayIP.String()
|
||||
return labels
|
||||
}
|
||||
|
||||
9
builder/builder-next/worker/label/label.go
Normal file
9
builder/builder-next/worker/label/label.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package label
|
||||
|
||||
// Pre-defined label keys similar to BuildKit ones
|
||||
// https://github.com/moby/buildkit/blob/v0.11.6/worker/label/label.go#L3-L16
|
||||
const (
|
||||
prefix = "org.mobyproject.buildkit.worker.moby."
|
||||
|
||||
HostGatewayIP = prefix + "host-gateway-ip"
|
||||
)
|
||||
@@ -50,7 +50,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
version.Version = "v0.11.6"
|
||||
version.Version = "v0.11.7-0.20230525183624-798ad6b0ce9f"
|
||||
}
|
||||
|
||||
const labelCreatedAt = "buildkit/createdat"
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
containerpkg "github.com/docker/docker/container"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,7 +46,7 @@ type Backend interface {
|
||||
// ContainerCreateWorkdir creates the workdir
|
||||
ContainerCreateWorkdir(containerID string) error
|
||||
|
||||
CreateImage(config []byte, parent string) (Image, error)
|
||||
CreateImage(ctx context.Context, config []byte, parent string, contentStoreDigest digest.Digest) (Image, error)
|
||||
|
||||
ImageCacheBuilder
|
||||
}
|
||||
@@ -104,6 +105,7 @@ type ROLayer interface {
|
||||
Release() error
|
||||
NewRWLayer() (RWLayer, error)
|
||||
DiffID() layer.DiffID
|
||||
ContentStoreDigest() digest.Digest
|
||||
}
|
||||
|
||||
// RWLayer is active layer that can be read/modified
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/syncmap"
|
||||
@@ -125,7 +125,7 @@ type Builder struct {
|
||||
pathCache pathCache
|
||||
containerManager *containerManager
|
||||
imageProber ImageProber
|
||||
platform *specs.Platform
|
||||
platform *ocispec.Platform
|
||||
}
|
||||
|
||||
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ type copier struct {
|
||||
source builder.Source
|
||||
pathCache pathCache
|
||||
download sourceDownloader
|
||||
platform *specs.Platform
|
||||
platform *ocispec.Platform
|
||||
// for cleanup. TODO: having copier.cleanup() is error prone and hard to
|
||||
// follow. Code calling performCopy should manage the lifecycle of its params.
|
||||
// Copier should take override source as input, not imageMount.
|
||||
@@ -86,7 +86,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
|
||||
platform := req.builder.platform
|
||||
if platform == nil {
|
||||
// May be nil if not explicitly set in API/dockerfile
|
||||
platform = &specs.Platform{}
|
||||
platform = &ocispec.Platform{}
|
||||
}
|
||||
if platform.OS == "" {
|
||||
// Default to the dispatch requests operating system if not explicit in API/dockerfile
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
"github.com/moby/sys/signal"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -158,7 +158,7 @@ func initializeStage(ctx context.Context, d dispatchRequest, cmd *instructions.S
|
||||
return err
|
||||
}
|
||||
|
||||
var platform *specs.Platform
|
||||
var platform *ocispec.Platform
|
||||
if v := cmd.Platform; v != "" {
|
||||
v, err := d.getExpandedString(d.shlex, v)
|
||||
if err != nil {
|
||||
@@ -232,7 +232,7 @@ func (d *dispatchRequest) getExpandedString(shlex *shell.Lex, str string) (strin
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (d *dispatchRequest) getImageOrStage(ctx context.Context, name string, platform *specs.Platform) (builder.Image, error) {
|
||||
func (d *dispatchRequest) getImageOrStage(ctx context.Context, name string, platform *ocispec.Platform) (builder.Image, error) {
|
||||
var localOnly bool
|
||||
if im, ok := d.stages.getByName(name); ok {
|
||||
name = im.Image
|
||||
@@ -266,7 +266,7 @@ func (d *dispatchRequest) getImageOrStage(ctx context.Context, name string, plat
|
||||
return imageMount.Image(), nil
|
||||
}
|
||||
|
||||
func (d *dispatchRequest) getFromImage(ctx context.Context, shlex *shell.Lex, basename string, platform *specs.Platform) (builder.Image, error) {
|
||||
func (d *dispatchRequest) getFromImage(ctx context.Context, shlex *shell.Lex, basename string, platform *ocispec.Platform) (builder.Image, error) {
|
||||
name, err := d.getExpandedString(shlex, basename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -21,8 +21,11 @@ type dispatchTestCase struct {
|
||||
files map[string]string
|
||||
}
|
||||
|
||||
func init() {
|
||||
reexec.Init()
|
||||
func TestMain(m *testing.M) {
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestDispatch(t *testing.T) {
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/builder"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type getAndMountFunc func(context.Context, string, bool, *specs.Platform) (builder.Image, builder.ROLayer, error)
|
||||
type getAndMountFunc func(context.Context, string, bool, *ocispec.Platform) (builder.Image, builder.ROLayer, error)
|
||||
|
||||
// imageSources mounts images and provides a cache for mounted images. It tracks
|
||||
// all images so they can be unmounted at the end of the build.
|
||||
@@ -24,7 +24,7 @@ type imageSources struct {
|
||||
}
|
||||
|
||||
func newImageSources(options builderOptions) *imageSources {
|
||||
getAndMount := func(ctx context.Context, idOrRef string, localOnly bool, platform *specs.Platform) (builder.Image, builder.ROLayer, error) {
|
||||
getAndMount := func(ctx context.Context, idOrRef string, localOnly bool, platform *ocispec.Platform) (builder.Image, builder.ROLayer, error) {
|
||||
pullOption := backend.PullOptionNoPull
|
||||
if !localOnly {
|
||||
if options.Options.PullParent {
|
||||
@@ -47,7 +47,7 @@ func newImageSources(options builderOptions) *imageSources {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *imageSources) Get(ctx context.Context, idOrRef string, localOnly bool, platform *specs.Platform) (*imageMount, error) {
|
||||
func (m *imageSources) Get(ctx context.Context, idOrRef string, localOnly bool, platform *ocispec.Platform) (*imageMount, error) {
|
||||
if im, ok := m.byImageID[idOrRef]; ok {
|
||||
return im, nil
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (m *imageSources) Unmount() (retErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *imageSources) Add(im *imageMount, platform *specs.Platform) {
|
||||
func (m *imageSources) Add(im *imageMount, platform *ocispec.Platform) {
|
||||
switch im.image {
|
||||
case nil:
|
||||
// Set the platform for scratch images
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-connections/nat"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -63,7 +63,7 @@ func (b *Builder) commitContainer(ctx context.Context, dispatchState *dispatchSt
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
|
||||
func (b *Builder) exportImage(ctx context.Context, state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
|
||||
newLayer, err := layer.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,7 +74,7 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
|
||||
return errors.Errorf("unexpected image type")
|
||||
}
|
||||
|
||||
platform := &specs.Platform{
|
||||
platform := &ocispec.Platform{
|
||||
OS: parentImage.OS,
|
||||
Architecture: parentImage.Architecture,
|
||||
Variant: parentImage.Variant,
|
||||
@@ -98,7 +98,15 @@ func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, paren
|
||||
return errors.Wrap(err, "failed to encode image config")
|
||||
}
|
||||
|
||||
exportedImage, err := b.docker.CreateImage(config, state.imageID)
|
||||
// when writing the new image's manifest, we now need to pass in the new layer's digest.
|
||||
// before the containerd store work this was unnecessary since we get the layer id
|
||||
// from the image's RootFS ChainID -- see:
|
||||
// https://github.com/moby/moby/blob/8cf66ed7322fa885ef99c4c044fa23e1727301dc/image/store.go#L162
|
||||
// however, with the containerd store we can't do this. An alternative implementation here
|
||||
// without changing the signature would be to get the layer digest by walking the content store
|
||||
// and filtering the objects to find the layer with the DiffID we want, but that has performance
|
||||
// implications that should be called out/investigated
|
||||
exportedImage, err := b.docker.CreateImage(ctx, config, state.imageID, newLayer.ContentStoreDigest())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to export image")
|
||||
}
|
||||
@@ -170,7 +178,7 @@ func (b *Builder) performCopy(ctx context.Context, req dispatchRequest, inst cop
|
||||
return errors.Wrapf(err, "failed to copy files")
|
||||
}
|
||||
}
|
||||
return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
|
||||
return b.exportImage(ctx, state, rwLayer, imageMount.Image(), runConfigWithCommentCmd)
|
||||
}
|
||||
|
||||
func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -193,6 +194,7 @@ type MockROLayer struct {
|
||||
diffID layer.DiffID
|
||||
}
|
||||
|
||||
func (l *MockROLayer) ContentStoreDigest() digest.Digest { return "" }
|
||||
func (l *MockROLayer) Release() error { return nil }
|
||||
func (l *MockROLayer) NewRWLayer() (builder.RWLayer, error) { return nil, nil }
|
||||
func (l *MockROLayer) DiffID() layer.DiffID { return l.diffID }
|
||||
@@ -217,6 +219,6 @@ func TestExportImage(t *testing.T) {
|
||||
imageSources: getMockImageSource(nil, nil, nil),
|
||||
docker: getMockBuildBackend(),
|
||||
}
|
||||
err := b.exportImage(ds, layer, parentImage, runConfig)
|
||||
err := b.exportImage(context.TODO(), ds, layer, parentImage, runConfig)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
containerpkg "github.com/docker/docker/container"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// MockBackend implements the builder.Backend interface for unit testing
|
||||
@@ -80,7 +81,7 @@ func (m *MockBackend) MakeImageCache(ctx context.Context, cacheFrom []string) (b
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) CreateImage(config []byte, parent string) (builder.Image, error) {
|
||||
func (m *MockBackend) CreateImage(ctx context.Context, config []byte, parent string, layerDigest digest.Digest) (builder.Image, error) {
|
||||
return &mockImage{id: "test"}, nil
|
||||
}
|
||||
|
||||
@@ -119,6 +120,10 @@ func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (str
|
||||
|
||||
type mockLayer struct{}
|
||||
|
||||
func (l *mockLayer) ContentStoreDigest() digest.Digest {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *mockLayer) Release() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ type hashed interface {
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// CachableSource is a source that contains cache records for its contents
|
||||
// CachableSource is a source that contains cache records for its contents.
|
||||
//
|
||||
// Deprecated: this type was used for the experimental "stream" support for the classic builder, which is no longer supported.
|
||||
type CachableSource struct {
|
||||
mu sync.Mutex
|
||||
root string
|
||||
@@ -23,7 +25,9 @@ type CachableSource struct {
|
||||
txn *iradix.Txn
|
||||
}
|
||||
|
||||
// NewCachableSource creates new CachableSource
|
||||
// NewCachableSource creates new CachableSource.
|
||||
//
|
||||
// Deprecated: this type was used for the experimental "stream" support for the classic builder, which is no longer supported.
|
||||
func NewCachableSource(root string) *CachableSource {
|
||||
ts := &CachableSource{
|
||||
tree: iradix.New(),
|
||||
|
||||
@@ -17,8 +17,11 @@ const (
|
||||
contents = "contents test"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Init()
|
||||
func TestMain(m *testing.M) {
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCloseRootDirectory(t *testing.T) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type configWrapper struct {
|
||||
@@ -20,7 +20,7 @@ type configWrapper struct {
|
||||
|
||||
// ContainerCreate creates a new container based on the given configuration.
|
||||
// It can be associated with a name, but it's not mandatory.
|
||||
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.CreateResponse, error) {
|
||||
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
|
||||
var response container.CreateResponse
|
||||
|
||||
if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
|
||||
@@ -75,7 +75,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
|
||||
// Similar to containerd's platforms.Format(), but does allow components to be
|
||||
// omitted (e.g. pass "architecture" only, without "os":
|
||||
// https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263
|
||||
func formatPlatform(platform *specs.Platform) string {
|
||||
func formatPlatform(platform *ocispec.Platform) string {
|
||||
if platform == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
|
||||
@@ -47,7 +47,7 @@ type CommonAPIClient interface {
|
||||
type ContainerAPIClient interface {
|
||||
ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
|
||||
ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error)
|
||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.CreateResponse, error)
|
||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
|
||||
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
|
||||
ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
|
||||
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@@ -91,10 +91,10 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
|
||||
}, nil
|
||||
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
|
||||
b, err := json.Marshal(registrytypes.DistributionInspect{
|
||||
Descriptor: v1.Descriptor{
|
||||
Descriptor: ocispec.Descriptor{
|
||||
Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
|
||||
},
|
||||
Platforms: []v1.Platform{
|
||||
Platforms: []ocispec.Platform{
|
||||
{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
@@ -171,7 +171,7 @@ func TestServiceCreateDigestPinning(t *testing.T) {
|
||||
} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
|
||||
// resolvable images
|
||||
b, err := json.Marshal(registrytypes.DistributionInspect{
|
||||
Descriptor: v1.Descriptor{
|
||||
Descriptor: ocispec.Descriptor{
|
||||
Digest: digest.Digest(dgst),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -351,6 +351,7 @@ func newRouterOptions(ctx context.Context, config *config.Config, d *daemon.Daem
|
||||
bk, err := buildkit.New(ctx, buildkit.Opt{
|
||||
SessionManager: sm,
|
||||
Root: filepath.Join(config.Root, "buildkit"),
|
||||
EngineID: d.ID(),
|
||||
Dist: d.DistributionServices(),
|
||||
ImageTagger: d.ImageService(),
|
||||
NetworkController: d.NetworkController(),
|
||||
|
||||
@@ -6,13 +6,9 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/pkg/aaparser"
|
||||
)
|
||||
|
||||
type profileData struct {
|
||||
Version int
|
||||
}
|
||||
type profileData struct{}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
@@ -22,15 +18,6 @@ func main() {
|
||||
// parse the arg
|
||||
apparmorProfilePath := os.Args[1]
|
||||
|
||||
version, err := aaparser.GetVersion()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
data := profileData{
|
||||
Version: version,
|
||||
}
|
||||
fmt.Printf("apparmor_parser is of version %+v\n", data)
|
||||
|
||||
// parse the template
|
||||
compiled, err := template.New("apparmor_profile").Parse(dockerProfileTemplate)
|
||||
if err != nil {
|
||||
@@ -48,6 +35,7 @@ func main() {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data := profileData{}
|
||||
if err := compiled.Execute(f, data); err != nil {
|
||||
log.Fatalf("executing template failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -149,9 +149,7 @@ profile /usr/bin/docker (attach_disconnected, complain) {
|
||||
}
|
||||
# xz works via pipes, so we do not need access to the filesystem.
|
||||
profile /usr/bin/xz (complain) {
|
||||
{{if ge .Version 209000}}
|
||||
signal (receive) peer=/usr/bin/docker,
|
||||
{{end}}
|
||||
/etc/ld.so.cache r,
|
||||
/lib/** rm,
|
||||
/usr/bin/xz rm,
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
# To publish: Needs someone with publishing rights
|
||||
ARG WINDOWS_BASE_IMAGE=mcr.microsoft.com/windows/servercore
|
||||
ARG WINDOWS_BASE_IMAGE_TAG=ltsc2022
|
||||
ARG BUSYBOX_VERSION=FRP-3329-gcf0fa4d13
|
||||
ARG BUSYBOX_VERSION=FRP-5007-g82accfc19
|
||||
|
||||
# Checksum taken from https://frippery.org/files/busybox/SHA256SUM
|
||||
ARG BUSYBOX_SHA256SUM=bfaeb88638e580fc522a68e69072e305308f9747563e51fa085eec60ca39a5ae
|
||||
ARG BUSYBOX_SHA256SUM=2d6fff0b2de5c034c92990d696c0d85a677b8a75931fa1ec30694fbf1f1df5c9
|
||||
|
||||
FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
|
||||
RUN mkdir C:\tmp && mkdir C:\bin
|
||||
|
||||
@@ -351,7 +351,7 @@ echo " - \"$(wrap_color 'overlay' blue)\":"
|
||||
check_flags VXLAN BRIDGE_VLAN_FILTERING | sed 's/^/ /'
|
||||
echo ' Optional (for encrypted networks):'
|
||||
check_flags CRYPTO CRYPTO_AEAD CRYPTO_GCM CRYPTO_SEQIV CRYPTO_GHASH \
|
||||
XFRM XFRM_USER XFRM_ALGO INET_ESP | sed 's/^/ /'
|
||||
XFRM XFRM_USER XFRM_ALGO INET_ESP NETFILTER_XT_MATCH_BPF | sed 's/^/ /'
|
||||
if [ "$kernelMajor" -lt 5 ] || [ "$kernelMajor" -eq 5 -a "$kernelMinor" -le 3 ]; then
|
||||
check_flags INET_XFRM_MODE_TRANSPORT | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
@@ -58,13 +58,20 @@ func NodeFromGRPC(n swarmapi.Node) types.Node {
|
||||
}
|
||||
for _, csi := range n.Description.CSIInfo {
|
||||
if csi != nil {
|
||||
convertedInfo := types.NodeCSIInfo{
|
||||
PluginName: csi.PluginName,
|
||||
NodeID: csi.NodeID,
|
||||
MaxVolumesPerNode: csi.MaxVolumesPerNode,
|
||||
}
|
||||
|
||||
if csi.AccessibleTopology != nil {
|
||||
convertedInfo.AccessibleTopology = &types.Topology{
|
||||
Segments: csi.AccessibleTopology.Segments,
|
||||
}
|
||||
}
|
||||
|
||||
node.Description.CSIInfo = append(
|
||||
node.Description.CSIInfo,
|
||||
types.NodeCSIInfo{
|
||||
PluginName: csi.PluginName,
|
||||
NodeID: csi.NodeID,
|
||||
MaxVolumesPerNode: csi.MaxVolumesPerNode,
|
||||
},
|
||||
node.Description.CSIInfo, convertedInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
60
daemon/cluster/convert/node_test.go
Normal file
60
daemon/cluster/convert/node_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
types "github.com/docker/docker/api/types/swarm"
|
||||
swarmapi "github.com/moby/swarmkit/v2/api"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TestNodeCSIInfoFromGRPC tests that conversion of the NodeCSIInfo from the
|
||||
// gRPC to the Docker types is correct.
|
||||
func TestNodeCSIInfoFromGRPC(t *testing.T) {
|
||||
node := &swarmapi.Node{
|
||||
ID: "someID",
|
||||
Description: &swarmapi.NodeDescription{
|
||||
CSIInfo: []*swarmapi.NodeCSIInfo{
|
||||
&swarmapi.NodeCSIInfo{
|
||||
PluginName: "plugin1",
|
||||
NodeID: "p1n1",
|
||||
MaxVolumesPerNode: 1,
|
||||
},
|
||||
&swarmapi.NodeCSIInfo{
|
||||
PluginName: "plugin2",
|
||||
NodeID: "p2n1",
|
||||
MaxVolumesPerNode: 2,
|
||||
AccessibleTopology: &swarmapi.Topology{
|
||||
Segments: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []types.NodeCSIInfo{
|
||||
{
|
||||
PluginName: "plugin1",
|
||||
NodeID: "p1n1",
|
||||
MaxVolumesPerNode: 1,
|
||||
},
|
||||
{
|
||||
PluginName: "plugin2",
|
||||
NodeID: "p2n1",
|
||||
MaxVolumesPerNode: 2,
|
||||
AccessibleTopology: &types.Topology{
|
||||
Segments: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual := NodeFromGRPC(*node)
|
||||
|
||||
assert.DeepEqual(t, actual.Description.CSIInfo, expected)
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/docker/docker/plugin"
|
||||
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||
"github.com/moby/swarmkit/v2/agent/exec"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Backend defines the executor component for a swarm agent.
|
||||
@@ -75,7 +75,7 @@ type VolumeBackend interface {
|
||||
|
||||
// ImageBackend is used by an executor to perform image operations
|
||||
type ImageBackend interface {
|
||||
PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PullImage(ctx context.Context, image, tag string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
GetRepository(context.Context, reference.Named, *registry.AuthConfig) (distribution.Repository, error)
|
||||
GetImage(ctx context.Context, refOrID string, options opts.GetImageOpts) (*image.Image, error)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,92 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
imagetype "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/image"
|
||||
)
|
||||
|
||||
// MakeImageCache creates a stateful image cache.
|
||||
func (i *ImageService) MakeImageCache(ctx context.Context, cacheFrom []string) (builder.ImageCache, error) {
|
||||
panic("not implemented")
|
||||
images := []*image.Image{}
|
||||
for _, c := range cacheFrom {
|
||||
im, err := i.GetImage(ctx, c, imagetype.GetImageOpts{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, im)
|
||||
}
|
||||
return &imageCache{images: images, c: i}, nil
|
||||
}
|
||||
|
||||
type imageCache struct {
|
||||
images []*image.Image
|
||||
c *ImageService
|
||||
}
|
||||
|
||||
func (ic *imageCache) GetCache(parentID string, cfg *container.Config) (imageID string, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
if parentID == "" {
|
||||
// TODO handle "parentless" image cache lookups ("FROM scratch")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parent, err := ic.c.GetImage(ctx, parentID, imagetype.GetImageOpts{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, localCachedImage := range ic.images {
|
||||
if isMatch(localCachedImage, parent, cfg) {
|
||||
return localCachedImage.ID().String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
children, err := ic.c.Children(ctx, parent.ID())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, children := range children {
|
||||
childImage, err := ic.c.GetImage(ctx, children.String(), imagetype.GetImageOpts{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if isMatch(childImage, parent, cfg) {
|
||||
return children.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// isMatch checks whether a given target can be used as cache for the given
|
||||
// parent image/config combination.
|
||||
// A target can only be an immediate child of the given parent image. For
|
||||
// a parent image with `n` history entries, a valid target must have `n+1`
|
||||
// entries and the extra entry must match the provided config
|
||||
func isMatch(target, parent *image.Image, cfg *container.Config) bool {
|
||||
if target == nil || parent == nil || cfg == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(target.History) != len(parent.History)+1 ||
|
||||
len(target.RootFS.DiffIDs) != len(parent.RootFS.DiffIDs)+1 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range parent.History {
|
||||
if !reflect.DeepEqual(parent.History[i], target.History[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
childCreatedBy := target.History[len(target.History)-1].CreatedBy
|
||||
return childCreatedBy == strings.Join(cfg.Cmd, " ")
|
||||
}
|
||||
|
||||
@@ -9,16 +9,13 @@ import (
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// walkPresentChildren is a simple wrapper for containerdimages.Walk with
|
||||
// presentChildrenHandler wrapping a simple handler that only operates on
|
||||
// walked Descriptor and doesn't return any errror.
|
||||
// walkPresentChildren is a simple wrapper for containerdimages.Walk with presentChildrenHandler.
|
||||
// This is only a convenient helper to reduce boilerplate.
|
||||
func (i *ImageService) walkPresentChildren(ctx context.Context, target ocispec.Descriptor, f func(context.Context, ocispec.Descriptor)) error {
|
||||
func (i *ImageService) walkPresentChildren(ctx context.Context, target ocispec.Descriptor, f func(context.Context, ocispec.Descriptor) error) error {
|
||||
store := i.client.ContentStore()
|
||||
return containerdimages.Walk(ctx, presentChildrenHandler(store, containerdimages.HandlerFunc(
|
||||
func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
f(ctx, desc)
|
||||
return nil, nil
|
||||
return nil, f(ctx, desc)
|
||||
})), target)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
cplatforms "github.com/containerd/containerd/platforms"
|
||||
@@ -33,7 +32,7 @@ var truncatedID = regexp.MustCompile(`^([a-f0-9]{4,64})$`)
|
||||
|
||||
// GetImage returns an image corresponding to the image referred to by refOrID.
|
||||
func (i *ImageService) GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error) {
|
||||
desc, err := i.resolveDescriptor(ctx, refOrID)
|
||||
desc, err := i.resolveImage(ctx, refOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -44,20 +43,31 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
||||
}
|
||||
|
||||
cs := i.client.ContentStore()
|
||||
conf, err := containerdimages.Config(ctx, cs, desc, platform)
|
||||
|
||||
var presentImages []ocispec.Image
|
||||
err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
|
||||
conf, err := img.Config(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ociimage ocispec.Image
|
||||
if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
|
||||
return err
|
||||
}
|
||||
presentImages = append(presentImages, ociimage)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageConfigBytes, err := content.ReadBlob(ctx, cs, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(presentImages) == 0 {
|
||||
return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
|
||||
}
|
||||
|
||||
var ociimage ocispec.Image
|
||||
if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.SliceStable(presentImages, func(i, j int) bool {
|
||||
return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
|
||||
})
|
||||
ociimage := presentImages[0]
|
||||
|
||||
rootfs := image.NewRootFS()
|
||||
for _, id := range ociimage.RootFS.DiffIDs {
|
||||
@@ -68,11 +78,30 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
||||
exposedPorts[nat.Port(k)] = v
|
||||
}
|
||||
|
||||
img := image.NewImage(image.ID(desc.Digest))
|
||||
derefTimeSafely := func(t *time.Time) time.Time {
|
||||
if t != nil {
|
||||
return *t
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
var imgHistory []image.History
|
||||
for _, h := range ociimage.History {
|
||||
imgHistory = append(imgHistory, image.History{
|
||||
Created: derefTimeSafely(h.Created),
|
||||
Author: h.Author,
|
||||
CreatedBy: h.CreatedBy,
|
||||
Comment: h.Comment,
|
||||
EmptyLayer: h.EmptyLayer,
|
||||
})
|
||||
}
|
||||
|
||||
img := image.NewImage(image.ID(desc.Target.Digest))
|
||||
img.V1Image = image.V1Image{
|
||||
ID: string(desc.Digest),
|
||||
ID: string(desc.Target.Digest),
|
||||
OS: ociimage.OS,
|
||||
Architecture: ociimage.Architecture,
|
||||
Created: derefTimeSafely(ociimage.Created),
|
||||
Config: &containertypes.Config{
|
||||
Entrypoint: ociimage.Config.Entrypoint,
|
||||
Env: ociimage.Config.Env,
|
||||
@@ -87,15 +116,16 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
||||
}
|
||||
|
||||
img.RootFS = rootfs
|
||||
img.History = imgHistory
|
||||
|
||||
if options.Details {
|
||||
lastUpdated := time.Unix(0, 0)
|
||||
size, err := i.size(ctx, desc, platform)
|
||||
size, err := i.size(ctx, desc.Target, platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Digest.String())
|
||||
tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,7 +155,7 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
||||
}
|
||||
refs = append(refs, name)
|
||||
|
||||
digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Digest)
|
||||
digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest)
|
||||
if err != nil {
|
||||
// This could only happen if digest is invalid, but considering that
|
||||
// we get it from the Descriptor it's highly unlikely.
|
||||
|
||||
@@ -2,23 +2,512 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
registrypkg "github.com/docker/docker/registry"
|
||||
|
||||
// "github.com/docker/docker/api/types/container"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/errdefs"
|
||||
dimage "github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetImageAndReleasableLayer returns an image and releaseable layer for a
|
||||
// reference or ID. Every call to GetImageAndReleasableLayer MUST call
|
||||
// releasableLayer.Release() to prevent leaking of layers.
|
||||
func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
|
||||
return nil, nil, errdefs.NotImplemented(errors.New("not implemented"))
|
||||
if refOrID == "" { // from SCRATCH
|
||||
os := runtime.GOOS
|
||||
if runtime.GOOS == "windows" {
|
||||
os = "linux"
|
||||
}
|
||||
if opts.Platform != nil {
|
||||
os = opts.Platform.OS
|
||||
}
|
||||
if !system.IsOSSupported(os) {
|
||||
return nil, nil, system.ErrNotSupportedOperatingSystem
|
||||
}
|
||||
return nil, &rolayer{
|
||||
key: "",
|
||||
c: i.client,
|
||||
snapshotter: i.snapshotter,
|
||||
diffID: "",
|
||||
root: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if opts.PullOption != backend.PullOptionForcePull {
|
||||
// TODO(laurazard): same as below
|
||||
img, err := i.GetImage(ctx, refOrID, image.GetImageOpts{Platform: opts.Platform})
|
||||
if err != nil && opts.PullOption == backend.PullOptionNoPull {
|
||||
return nil, nil, err
|
||||
}
|
||||
imgDesc, err := i.resolveDescriptor(ctx, refOrID)
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if img != nil {
|
||||
if !system.IsOSSupported(img.OperatingSystem()) {
|
||||
return nil, nil, system.ErrNotSupportedOperatingSystem
|
||||
}
|
||||
|
||||
layer, err := newROLayerForImage(ctx, &imgDesc, i, opts, refOrID, opts.Platform)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return img, layer, nil
|
||||
}
|
||||
}
|
||||
|
||||
ctx, _, err := i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
|
||||
// TODO(laurazard): do we really need a new method here to pull the image?
|
||||
imgDesc, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO(laurazard): pullForBuilder should return whatever we
|
||||
// need here instead of having to go and get it again
|
||||
img, err := i.GetImage(ctx, refOrID, imagetypes.GetImageOpts{
|
||||
Platform: opts.Platform,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
layer, err := newROLayerForImage(ctx, imgDesc, i, opts, refOrID, opts.Platform)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return img, layer, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.AuthConfig, output io.Writer, platform *ocispec.Platform) (*ocispec.Descriptor, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taggedRef := reference.TagNameOnly(ref)
|
||||
|
||||
pullRegistryAuth := ®istry.AuthConfig{}
|
||||
if len(authConfigs) > 0 {
|
||||
// The request came with a full auth config, use it
|
||||
repoInfo, err := i.registryService.ResolveRepository(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvedConfig := registrypkg.ResolveAuthConfig(authConfigs, repoInfo.Index)
|
||||
pullRegistryAuth = &resolvedConfig
|
||||
}
|
||||
|
||||
if err := i.PullImage(ctx, ref.Name(), taggedRef.(reference.NamedTagged).Tag(), platform, nil, pullRegistryAuth, output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img, err := i.GetImage(ctx, name, imagetypes.GetImageOpts{Platform: platform})
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) && img != nil && platform != nil {
|
||||
imgPlat := ocispec.Platform{
|
||||
OS: img.OS,
|
||||
Architecture: img.BaseImgArch(),
|
||||
Variant: img.BaseImgVariant(),
|
||||
}
|
||||
|
||||
p := *platform
|
||||
if !platforms.Only(p).Match(imgPlat) {
|
||||
po := streamformatter.NewJSONProgressOutput(output, false)
|
||||
progress.Messagef(po, "", `
|
||||
WARNING: Pulled image with specified platform (%s), but the resulting image's configured platform (%s) does not match.
|
||||
This is most likely caused by a bug in the build system that created the fetched image (%s).
|
||||
Please notify the image author to correct the configuration.`,
|
||||
platforms.Format(p), platforms.Format(imgPlat), name,
|
||||
)
|
||||
logrus.WithError(err).WithField("image", name).Warn("Ignoring error about platform mismatch where the manifest list points to an image whose configuration does not match the platform in the manifest.")
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !system.IsOSSupported(img.OperatingSystem()) {
|
||||
return nil, system.ErrNotSupportedOperatingSystem
|
||||
}
|
||||
|
||||
imgDesc, err := i.resolveDescriptor(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &imgDesc, err
|
||||
}
|
||||
|
||||
func newROLayerForImage(ctx context.Context, imgDesc *ocispec.Descriptor, i *ImageService, opts backend.GetImageAndLayerOptions, refOrID string, platform *ocispec.Platform) (builder.ROLayer, error) {
|
||||
if imgDesc == nil {
|
||||
return nil, fmt.Errorf("can't make an RO layer for a nil image :'(")
|
||||
}
|
||||
|
||||
platMatcher := platforms.Default()
|
||||
if platform != nil {
|
||||
platMatcher = platforms.Only(*platform)
|
||||
}
|
||||
|
||||
// this needs it's own context + lease so that it doesn't get cleaned before we're ready
|
||||
confDesc, err := containerdimages.Config(ctx, i.client.ContentStore(), *imgDesc, platMatcher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diffIDs, err := containerdimages.RootFS(ctx, i.client.ContentStore(), confDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parent := identity.ChainID(diffIDs).String()
|
||||
|
||||
s := i.client.SnapshotService(i.snapshotter)
|
||||
key := stringid.GenerateRandomID()
|
||||
ctx, _, err = i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
mounts, err := s.View(ctx, key, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempMountLocation := os.TempDir()
|
||||
root, err := os.MkdirTemp(tempMountLocation, "rootfs-mount")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.All(mounts, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rolayer{
|
||||
key: key,
|
||||
c: i.client,
|
||||
snapshotter: i.snapshotter,
|
||||
diffID: digest.Digest(parent),
|
||||
root: root,
|
||||
contentStoreDigest: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
type rolayer struct {
|
||||
key string
|
||||
c *containerd.Client
|
||||
snapshotter string
|
||||
diffID digest.Digest
|
||||
root string
|
||||
contentStoreDigest digest.Digest
|
||||
}
|
||||
|
||||
func (rl *rolayer) ContentStoreDigest() digest.Digest {
|
||||
return rl.contentStoreDigest
|
||||
}
|
||||
|
||||
func (rl *rolayer) DiffID() layer.DiffID {
|
||||
if rl.diffID == "" {
|
||||
return layer.DigestSHA256EmptyTar
|
||||
}
|
||||
return layer.DiffID(rl.diffID)
|
||||
}
|
||||
|
||||
func (rl *rolayer) Release() error {
|
||||
snapshotter := rl.c.SnapshotService(rl.snapshotter)
|
||||
err := snapshotter.Remove(context.TODO(), rl.key)
|
||||
if err != nil && !cerrdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if rl.root == "" { // nothing to release
|
||||
return nil
|
||||
}
|
||||
if err := mount.UnmountAll(rl.root, 0); err != nil {
|
||||
logrus.WithError(err).WithField("root", rl.root).Error("failed to unmount ROLayer")
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(rl.root); err != nil {
|
||||
logrus.WithError(err).WithField("dir", rl.root).Error("failed to remove mount temp dir")
|
||||
return err
|
||||
}
|
||||
rl.root = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRWLayer creates a new read-write layer for the builder
|
||||
func (rl *rolayer) NewRWLayer() (builder.RWLayer, error) {
|
||||
snapshotter := rl.c.SnapshotService(rl.snapshotter)
|
||||
|
||||
// we need this here for the prepared snapshots or
|
||||
// we'll have racy behaviour where sometimes they
|
||||
// will get GC'd before we commit/use them
|
||||
ctx, _, err := rl.c.WithLease(context.TODO(), leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
|
||||
key := stringid.GenerateRandomID()
|
||||
mounts, err := snapshotter.Prepare(ctx, key, rl.diffID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root, err := os.MkdirTemp(os.TempDir(), "rootfs-mount")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := mount.All(mounts, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rwlayer{
|
||||
key: key,
|
||||
parent: rl.key,
|
||||
c: rl.c,
|
||||
snapshotter: rl.snapshotter,
|
||||
root: root,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type rwlayer struct {
|
||||
key string
|
||||
parent string
|
||||
c *containerd.Client
|
||||
snapshotter string
|
||||
root string
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Root() string {
|
||||
return rw.root
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Commit() (builder.ROLayer, error) {
|
||||
// we need this here for the prepared snapshots or
|
||||
// we'll have racy behaviour where sometimes they
|
||||
// will get GC'd before we commit/use them
|
||||
ctx, _, err := rw.c.WithLease(context.TODO(), leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
snapshotter := rw.c.SnapshotService(rw.snapshotter)
|
||||
|
||||
key := stringid.GenerateRandomID()
|
||||
err = snapshotter.Commit(ctx, key, rw.key)
|
||||
if err != nil && !cerrdefs.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
differ := rw.c.DiffService()
|
||||
desc, err := rootfs.CreateDiff(ctx, key, snapshotter, differ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := rw.c.ContentStore().Info(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diffIDStr, ok := info.Labels["containerd.io/uncompressed"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid differ response with no diffID")
|
||||
}
|
||||
diffID, err := digest.Parse(diffIDStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rolayer{
|
||||
key: key,
|
||||
c: rw.c,
|
||||
snapshotter: rw.snapshotter,
|
||||
diffID: diffID,
|
||||
root: "",
|
||||
contentStoreDigest: desc.Digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Release() error {
|
||||
snapshotter := rw.c.SnapshotService(rw.snapshotter)
|
||||
err := snapshotter.Remove(context.TODO(), rw.key)
|
||||
if err != nil && !cerrdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if rw.root == "" { // nothing to release
|
||||
return nil
|
||||
}
|
||||
if err := mount.UnmountAll(rw.root, 0); err != nil {
|
||||
logrus.WithError(err).WithField("root", rw.root).Error("failed to unmount ROLayer")
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(rw.root); err != nil {
|
||||
logrus.WithError(err).WithField("dir", rw.root).Error("failed to remove mount temp dir")
|
||||
return err
|
||||
}
|
||||
rw.root = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateImage creates a new image by adding a config and ID to the image store.
|
||||
// This is similar to LoadImage() except that it receives JSON encoded bytes of
|
||||
// an image instead of a tar archive.
|
||||
func (i *ImageService) CreateImage(config []byte, parent string) (builder.Image, error) {
|
||||
return nil, errdefs.NotImplemented(errors.New("not implemented"))
|
||||
func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent string, layerDigest digest.Digest) (builder.Image, error) {
|
||||
imgToCreate, err := dimage.NewFromJSON(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootfs := ocispec.RootFS{
|
||||
Type: imgToCreate.RootFS.Type,
|
||||
DiffIDs: []digest.Digest{},
|
||||
}
|
||||
for _, diffId := range imgToCreate.RootFS.DiffIDs {
|
||||
rootfs.DiffIDs = append(rootfs.DiffIDs, digest.Digest(diffId))
|
||||
}
|
||||
exposedPorts := make(map[string]struct{}, len(imgToCreate.Config.ExposedPorts))
|
||||
for k, v := range imgToCreate.Config.ExposedPorts {
|
||||
exposedPorts[string(k)] = v
|
||||
}
|
||||
|
||||
var ociHistory []ocispec.History
|
||||
for _, history := range imgToCreate.History {
|
||||
created := history.Created
|
||||
ociHistory = append(ociHistory, ocispec.History{
|
||||
Created: &created,
|
||||
CreatedBy: history.CreatedBy,
|
||||
Author: history.Author,
|
||||
Comment: history.Comment,
|
||||
EmptyLayer: history.EmptyLayer,
|
||||
})
|
||||
}
|
||||
|
||||
// make an ocispec.Image from the docker/image.Image
|
||||
ociImgToCreate := ocispec.Image{
|
||||
Created: &imgToCreate.Created,
|
||||
Author: imgToCreate.Author,
|
||||
Platform: ocispec.Platform{
|
||||
Architecture: imgToCreate.Architecture,
|
||||
Variant: imgToCreate.Variant,
|
||||
OS: imgToCreate.OS,
|
||||
OSVersion: imgToCreate.OSVersion,
|
||||
OSFeatures: imgToCreate.OSFeatures,
|
||||
},
|
||||
Config: ocispec.ImageConfig{
|
||||
User: imgToCreate.Config.User,
|
||||
ExposedPorts: exposedPorts,
|
||||
Env: imgToCreate.Config.Env,
|
||||
Entrypoint: imgToCreate.Config.Entrypoint,
|
||||
Cmd: imgToCreate.Config.Cmd,
|
||||
Volumes: imgToCreate.Config.Volumes,
|
||||
WorkingDir: imgToCreate.Config.WorkingDir,
|
||||
Labels: imgToCreate.Config.Labels,
|
||||
StopSignal: imgToCreate.Config.StopSignal,
|
||||
},
|
||||
RootFS: rootfs,
|
||||
History: ociHistory,
|
||||
}
|
||||
|
||||
var layers []ocispec.Descriptor
|
||||
// if the image has a parent, we need to start with the parents layers descriptors
|
||||
if parent != "" {
|
||||
parentDesc, err := i.resolveDescriptor(ctx, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentImageManifest, err := containerdimages.Manifest(ctx, i.client.ContentStore(), parentDesc, platforms.Default())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layers = parentImageManifest.Layers
|
||||
}
|
||||
|
||||
// get the info for the new layers
|
||||
info, err := i.client.ContentStore().Info(ctx, layerDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// append the new layer descriptor
|
||||
layers = append(layers,
|
||||
ocispec.Descriptor{
|
||||
MediaType: containerdimages.MediaTypeDockerSchema2LayerGzip,
|
||||
Digest: layerDigest,
|
||||
Size: info.Size,
|
||||
},
|
||||
)
|
||||
|
||||
// necessary to prevent the contents from being GC'd
|
||||
// between writing them here and creating an image
|
||||
ctx, done, err := i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer done(ctx)
|
||||
|
||||
commitManifestDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), ociImgToCreate, layers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// image create
|
||||
img := containerdimages.Image{
|
||||
Name: danglingImageName(commitManifestDesc.Digest),
|
||||
Target: commitManifestDesc,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
createdImage, err := i.client.ImageService().Update(ctx, img)
|
||||
if err != nil {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if createdImage, err = i.client.ImageService().Create(ctx, img); err != nil {
|
||||
return nil, fmt.Errorf("failed to create new image: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.unpackImage(ctx, createdImage, platforms.DefaultSpec()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newImage := dimage.NewImage(dimage.ID(createdImage.Target.Digest))
|
||||
newImage.V1Image = imgToCreate.V1Image
|
||||
newImage.V1Image.ID = string(createdImage.Target.Digest)
|
||||
newImage.History = imgToCreate.History
|
||||
return newImage, nil
|
||||
}
|
||||
|
||||
@@ -127,3 +127,71 @@ func isRootfsChildOf(child ocispec.RootFS, parent ocispec.RootFS) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// parents returns a slice of image IDs whose entire rootfs contents match,
|
||||
// in order, the childs first layers, excluding images with the exact same
|
||||
// rootfs.
|
||||
//
|
||||
// Called from image_delete.go to prune dangling parents.
|
||||
func (i *ImageService) parents(ctx context.Context, id image.ID) ([]imageWithRootfs, error) {
|
||||
target, err := i.resolveDescriptor(ctx, id.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get child image")
|
||||
}
|
||||
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
allPlatforms, err := containerdimages.Platforms(ctx, cs, target)
|
||||
if err != nil {
|
||||
return nil, errdefs.System(errors.Wrap(err, "failed to list platforms supported by image"))
|
||||
}
|
||||
|
||||
var childRootFS []ocispec.RootFS
|
||||
for _, platform := range allPlatforms {
|
||||
rootfs, err := platformRootfs(ctx, cs, target, platform)
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs"))
|
||||
}
|
||||
|
||||
childRootFS = append(childRootFS, rootfs)
|
||||
}
|
||||
|
||||
imgs, err := i.client.ImageService().List(ctx)
|
||||
if err != nil {
|
||||
return nil, errdefs.System(errors.Wrap(err, "failed to list all images"))
|
||||
}
|
||||
|
||||
var parents []imageWithRootfs
|
||||
for _, img := range imgs {
|
||||
nextImage:
|
||||
for _, platform := range allPlatforms {
|
||||
rootfs, err := platformRootfs(ctx, cs, img.Target, platform)
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs"))
|
||||
}
|
||||
|
||||
for _, childRoot := range childRootFS {
|
||||
if isRootfsChildOf(childRoot, rootfs) {
|
||||
parents = append(parents, imageWithRootfs{
|
||||
img: img,
|
||||
rootfs: rootfs,
|
||||
})
|
||||
break nextImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parents, nil
|
||||
}
|
||||
|
||||
type imageWithRootfs struct {
|
||||
img containerdimages.Image
|
||||
rootfs ocispec.RootFS
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
@@ -132,20 +131,23 @@ func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, o
|
||||
}
|
||||
logrus.Debugf("generateCommitImageConfig(): arch=%q, os=%q", arch, os)
|
||||
return ocispec.Image{
|
||||
Architecture: arch,
|
||||
OS: os,
|
||||
Created: &createdTime,
|
||||
Author: opts.Author,
|
||||
Config: containerConfigToOciImageConfig(opts.Config),
|
||||
Platform: ocispec.Platform{
|
||||
Architecture: arch,
|
||||
OS: os,
|
||||
},
|
||||
Created: &createdTime,
|
||||
Author: opts.Author,
|
||||
Config: containerConfigToOciImageConfig(opts.Config),
|
||||
RootFS: ocispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: append(baseConfig.RootFS.DiffIDs, diffID),
|
||||
},
|
||||
History: append(baseConfig.History, ocispec.History{
|
||||
Created: &createdTime,
|
||||
CreatedBy: "", // FIXME(ndeloof) ?
|
||||
Author: opts.Author,
|
||||
Comment: opts.Comment,
|
||||
Created: &createdTime,
|
||||
CreatedBy: strings.Join(opts.ContainerConfig.Cmd, " "),
|
||||
Author: opts.Author,
|
||||
Comment: opts.Comment,
|
||||
// TODO(laurazard): this check might be incorrect
|
||||
EmptyLayer: diffID == "",
|
||||
}),
|
||||
}
|
||||
@@ -297,5 +299,13 @@ func uniquePart() string {
|
||||
//
|
||||
// This is a temporary shim. Should be removed when builder stops using commit.
|
||||
func (i *ImageService) CommitBuildStep(ctx context.Context, c backend.CommitConfig) (image.ID, error) {
|
||||
return "", errdefs.NotImplemented(errors.New("not implemented"))
|
||||
ctr := i.containers.Get(c.ContainerID)
|
||||
if ctr == nil {
|
||||
// TODO: use typed error
|
||||
return "", fmt.Errorf("container not found: %s", c.ContainerID)
|
||||
}
|
||||
c.ContainerMountLabel = ctr.MountLabel
|
||||
c.ContainerOS = ctr.OS
|
||||
c.ParentImageID = string(ctr.ImageID)
|
||||
return i.CommitImage(ctx, c)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -30,8 +36,6 @@ import (
|
||||
// are divided into two categories grouped by their severity:
|
||||
//
|
||||
// Hard Conflict:
|
||||
// - a pull or build using the image.
|
||||
// - any descendant image.
|
||||
// - any running container using the image.
|
||||
//
|
||||
// Soft Conflict:
|
||||
@@ -45,8 +49,6 @@ import (
|
||||
// meaning any delete conflicts will cause the image to not be deleted and the
|
||||
// conflict will not be reported.
|
||||
//
|
||||
// TODO(thaJeztah): implement ImageDelete "force" options; see https://github.com/moby/moby/issues/43850
|
||||
// TODO(thaJeztah): implement ImageDelete "prune" options; see https://github.com/moby/moby/issues/43849
|
||||
// TODO(thaJeztah): image delete should send prometheus counters; see https://github.com/moby/moby/issues/45268
|
||||
func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) {
|
||||
parsedRef, err := reference.ParseNormalizedNamed(imageRef)
|
||||
@@ -59,28 +61,279 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
possiblyDeletedConfigs := map[digest.Digest]struct{}{}
|
||||
if err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, d ocispec.Descriptor) {
|
||||
if images.IsConfigType(d.MediaType) {
|
||||
possiblyDeletedConfigs[d.Digest] = struct{}{}
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
imgID := image.ID(img.Target.Digest)
|
||||
|
||||
if isImageIDPrefix(imgID.String(), imageRef) {
|
||||
return i.deleteAll(ctx, img, force, prune)
|
||||
}
|
||||
|
||||
err = i.client.ImageService().Delete(ctx, img.Name, images.SynchronousDelete())
|
||||
singleRef, err := i.isSingleReference(ctx, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Workaround for: https://github.com/moby/buildkit/issues/3797
|
||||
if err := i.unleaseSnapshotsFromDeletedConfigs(context.Background(), possiblyDeletedConfigs); err != nil {
|
||||
logrus.WithError(err).Warn("failed to unlease snapshots")
|
||||
if !singleRef {
|
||||
err := i.client.ImageService().Delete(ctx, img.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
||||
records := []types.ImageDeleteResponseItem{{Untagged: reference.FamiliarString(reference.TagNameOnly(parsedRef))}}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
imgID := string(img.Target.Digest)
|
||||
i.LogImageEvent(imgID, imgID, "untag")
|
||||
i.LogImageEvent(imgID, imgID, "delete")
|
||||
using := func(c *container.Container) bool {
|
||||
return c.ImageID == imgID
|
||||
}
|
||||
ctr := i.containers.First(using)
|
||||
if ctr != nil {
|
||||
if !force {
|
||||
// If we removed the repository reference then
|
||||
// this image would remain "dangling" and since
|
||||
// we really want to avoid that the client must
|
||||
// explicitly force its removal.
|
||||
refString := reference.FamiliarString(reference.TagNameOnly(parsedRef))
|
||||
err := &imageDeleteConflict{
|
||||
reference: refString,
|
||||
used: true,
|
||||
message: fmt.Sprintf("container %s is using its referenced image %s",
|
||||
stringid.TruncateID(ctr.ID),
|
||||
stringid.TruncateID(imgID.String())),
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []types.ImageDeleteResponseItem{{Untagged: reference.FamiliarString(parsedRef)}}, nil
|
||||
err := i.softImageDelete(ctx, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
||||
records := []types.ImageDeleteResponseItem{{Untagged: reference.FamiliarString(reference.TagNameOnly(parsedRef))}}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
return i.deleteAll(ctx, img, force, prune)
|
||||
}
|
||||
|
||||
// deleteAll deletes the image from the daemon, and if prune is true,
|
||||
// also deletes dangling parents if there is no conflict in doing so.
|
||||
// Parent images are removed quietly, and if there is any issue/conflict
|
||||
// it is logged but does not halt execution/an error is not returned.
|
||||
func (i *ImageService) deleteAll(ctx context.Context, img images.Image, force, prune bool) ([]types.ImageDeleteResponseItem, error) {
|
||||
var records []types.ImageDeleteResponseItem
|
||||
|
||||
// Workaround for: https://github.com/moby/buildkit/issues/3797
|
||||
possiblyDeletedConfigs := map[digest.Digest]struct{}{}
|
||||
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, d ocispec.Descriptor) error {
|
||||
if images.IsConfigType(d.MediaType) {
|
||||
possiblyDeletedConfigs[d.Digest] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := i.unleaseSnapshotsFromDeletedConfigs(context.Background(), possiblyDeletedConfigs); err != nil {
|
||||
logrus.WithError(err).Warn("failed to unlease snapshots")
|
||||
}
|
||||
}()
|
||||
|
||||
imgID := img.Target.Digest.String()
|
||||
|
||||
var parents []imageWithRootfs
|
||||
if prune {
|
||||
parents, err = i.parents(ctx, image.ID(imgID))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("failed to get image parents")
|
||||
}
|
||||
sortParentsByAffinity(parents)
|
||||
}
|
||||
|
||||
imageRefs, err := i.client.ImageService().List(ctx, "target.digest=="+imgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, imageRef := range imageRefs {
|
||||
if err := i.imageDeleteHelper(ctx, imageRef, &records, force); err != nil {
|
||||
return records, err
|
||||
}
|
||||
}
|
||||
i.LogImageEvent(imgID, imgID, "delete")
|
||||
records = append(records, types.ImageDeleteResponseItem{Deleted: imgID})
|
||||
|
||||
for _, parent := range parents {
|
||||
if !isDanglingImage(parent.img) {
|
||||
break
|
||||
}
|
||||
err = i.imageDeleteHelper(ctx, parent.img, &records, false)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("failed to remove image parent")
|
||||
break
|
||||
}
|
||||
parentID := parent.img.Target.Digest.String()
|
||||
i.LogImageEvent(parentID, parentID, "delete")
|
||||
records = append(records, types.ImageDeleteResponseItem{Deleted: parentID})
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// isImageIDPrefix returns whether the given
|
||||
// possiblePrefix is a prefix of the given imageID.
|
||||
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|
||||
if strings.HasPrefix(imageID, possiblePrefix) {
|
||||
return true
|
||||
}
|
||||
if i := strings.IndexRune(imageID, ':'); i >= 0 {
|
||||
return strings.HasPrefix(imageID[i+1:], possiblePrefix)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sortParentsByAffinity(parents []imageWithRootfs) {
|
||||
sort.Slice(parents, func(i, j int) bool {
|
||||
lenRootfsI := len(parents[i].rootfs.DiffIDs)
|
||||
lenRootfsJ := len(parents[j].rootfs.DiffIDs)
|
||||
if lenRootfsI == lenRootfsJ {
|
||||
return isDanglingImage(parents[i].img)
|
||||
}
|
||||
return lenRootfsI > lenRootfsJ
|
||||
})
|
||||
}
|
||||
|
||||
// isSingleReference returns true if there are no other images in the
|
||||
// daemon targeting the same content as `img` that are not dangling.
|
||||
func (i *ImageService) isSingleReference(ctx context.Context, img images.Image) (bool, error) {
|
||||
refs, err := i.client.ImageService().List(ctx, "target.digest=="+img.Target.Digest.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, ref := range refs {
|
||||
if !isDanglingImage(ref) && ref.Name != img.Name {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type conflictType int
|
||||
|
||||
const (
|
||||
conflictRunningContainer conflictType = 1 << iota
|
||||
conflictActiveReference
|
||||
conflictStoppedContainer
|
||||
conflictHard = conflictRunningContainer
|
||||
conflictSoft = conflictActiveReference | conflictStoppedContainer
|
||||
)
|
||||
|
||||
// imageDeleteHelper attempts to delete the given image from this daemon.
|
||||
// If the image has any hard delete conflicts (running containers using
|
||||
// the image) then it cannot be deleted. If the image has any soft delete
|
||||
// conflicts (any tags/digests referencing the image or any stopped container
|
||||
// using the image) then it can only be deleted if force is true. Any deleted
|
||||
// images and untagged references are appended to the given records. If any
|
||||
// error or conflict is encountered, it will be returned immediately without
|
||||
// deleting the image.
|
||||
func (i *ImageService) imageDeleteHelper(ctx context.Context, img images.Image, records *[]types.ImageDeleteResponseItem, force bool) error {
|
||||
// First, determine if this image has any conflicts. Ignore soft conflicts
|
||||
// if force is true.
|
||||
c := conflictHard
|
||||
if !force {
|
||||
c |= conflictSoft
|
||||
}
|
||||
|
||||
imgID := image.ID(img.Target.Digest)
|
||||
|
||||
err := i.checkImageDeleteConflict(ctx, imgID, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
untaggedRef, err := reference.ParseAnyReference(img.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = i.client.ImageService().Delete(ctx, img.Name, images.SynchronousDelete())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
||||
*records = append(*records, types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(untaggedRef)})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImageDeleteConflict holds a soft or hard conflict and associated
|
||||
// error. A hard conflict represents a running container using the
|
||||
// image, while a soft conflict is any tags/digests referencing the
|
||||
// given image or any stopped container using the image.
|
||||
// Implements the error interface.
|
||||
type imageDeleteConflict struct {
|
||||
hard bool
|
||||
used bool
|
||||
reference string
|
||||
message string
|
||||
}
|
||||
|
||||
func (idc *imageDeleteConflict) Error() string {
|
||||
var forceMsg string
|
||||
if idc.hard {
|
||||
forceMsg = "cannot be forced"
|
||||
} else {
|
||||
forceMsg = "must be forced"
|
||||
}
|
||||
return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", idc.reference, forceMsg, idc.message)
|
||||
}
|
||||
|
||||
func (imageDeleteConflict) Conflict() {}
|
||||
|
||||
// checkImageDeleteConflict returns a conflict representing
|
||||
// any issue preventing deletion of the given image ID, and
|
||||
// nil if there are none. It takes a bitmask representing a
|
||||
// filter for which conflict types the caller cares about,
|
||||
// and will only check for these conflict types.
|
||||
func (i *ImageService) checkImageDeleteConflict(ctx context.Context, imgID image.ID, mask conflictType) error {
|
||||
if mask&conflictRunningContainer != 0 {
|
||||
running := func(c *container.Container) bool {
|
||||
return c.ImageID == imgID && c.IsRunning()
|
||||
}
|
||||
if ctr := i.containers.First(running); ctr != nil {
|
||||
return &imageDeleteConflict{
|
||||
reference: stringid.TruncateID(imgID.String()),
|
||||
hard: true,
|
||||
used: true,
|
||||
message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(ctr.ID)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mask&conflictStoppedContainer != 0 {
|
||||
stopped := func(c *container.Container) bool {
|
||||
return !c.IsRunning() && c.ImageID == imgID
|
||||
}
|
||||
if ctr := i.containers.First(stopped); ctr != nil {
|
||||
return &imageDeleteConflict{
|
||||
reference: stringid.TruncateID(imgID.String()),
|
||||
used: true,
|
||||
message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(ctr.ID)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mask&conflictActiveReference != 0 {
|
||||
refs, err := i.client.ImageService().List(ctx, "target.digest=="+imgID.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(refs) > 1 {
|
||||
return &imageDeleteConflict{
|
||||
reference: stringid.TruncateID(imgID.String()),
|
||||
message: "image is referenced in multiple repositories",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -125,16 +126,9 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
|
||||
return errdefs.System(err)
|
||||
}
|
||||
|
||||
store := i.client.ContentStore()
|
||||
progress := streamformatter.NewStdoutWriter(outStream)
|
||||
|
||||
for _, img := range imgs {
|
||||
allPlatforms, err := containerdimages.Platforms(ctx, store, img.Target)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("image", img.Name).Debug("failed to get image platforms")
|
||||
return errdefs.Unknown(err)
|
||||
}
|
||||
|
||||
name := img.Name
|
||||
loadedMsg := "Loaded image"
|
||||
|
||||
@@ -145,17 +139,25 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
|
||||
name = reference.FamiliarName(reference.TagNameOnly(named))
|
||||
}
|
||||
|
||||
for _, platform := range allPlatforms {
|
||||
err = i.walkImageManifests(ctx, img, func(platformImg *ImageManifest) error {
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"platform": platform,
|
||||
"image": name,
|
||||
"manifest": platformImg.Target().Digest,
|
||||
})
|
||||
platformImg := containerd.NewImageWithPlatform(i.client, img, cplatforms.OnlyStrict(platform))
|
||||
|
||||
if isPseudo, err := platformImg.IsPseudoImage(ctx); isPseudo || err != nil {
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("failed to read manifest")
|
||||
} else {
|
||||
logger.Debug("don't unpack non-image manifest")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
|
||||
if err != nil {
|
||||
logger.WithError(err).Debug("failed to check if image is unpacked")
|
||||
continue
|
||||
logger.WithError(err).Warn("failed to check if image is unpacked")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !unpacked {
|
||||
@@ -166,6 +168,10 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
|
||||
}
|
||||
}
|
||||
logger.WithField("alreadyUnpacked", unpacked).WithError(err).Debug("unpack")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to unpack loaded image")
|
||||
}
|
||||
|
||||
fmt.Fprintf(progress, "%s: %s\n", loadedMsg, name)
|
||||
|
||||
@@ -2,13 +2,12 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
cplatforms "github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/distribution/reference"
|
||||
imagetype "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/platforms"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -18,32 +17,39 @@ import (
|
||||
// ImageHistory returns a slice of HistoryResponseItem structures for the
|
||||
// specified image name by walking the image lineage.
|
||||
func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
|
||||
desc, err := i.resolveDescriptor(ctx, name)
|
||||
desc, err := i.resolveImage(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := i.client.ContentStore()
|
||||
// TODO: pass the platform from the cli
|
||||
conf, err := containerdimages.Config(ctx, cs, desc, platforms.AllPlatformsWithPreference(cplatforms.Default()))
|
||||
// TODO: pass platform in from the CLI
|
||||
platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
|
||||
|
||||
var presentImages []ocispec.Image
|
||||
err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
|
||||
conf, err := img.Config(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ociimage ocispec.Image
|
||||
if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
|
||||
return err
|
||||
}
|
||||
presentImages = append(presentImages, ociimage)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diffIDs, err := containerdimages.RootFS(ctx, cs, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(presentImages) == 0 {
|
||||
return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
|
||||
}
|
||||
|
||||
blob, err := content.ReadBlob(ctx, cs, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var image ocispec.Image
|
||||
if err := json.Unmarshal(blob, &image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.SliceStable(presentImages, func(i, j int) bool {
|
||||
return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
|
||||
})
|
||||
ociimage := presentImages[0]
|
||||
|
||||
var (
|
||||
history []*imagetype.HistoryResponseItem
|
||||
@@ -51,6 +57,7 @@ func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imaget
|
||||
)
|
||||
s := i.client.SnapshotService(i.snapshotter)
|
||||
|
||||
diffIDs := ociimage.RootFS.DiffIDs
|
||||
for i := range diffIDs {
|
||||
chainID := identity.ChainID(diffIDs[0 : i+1]).String()
|
||||
|
||||
@@ -62,7 +69,7 @@ func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imaget
|
||||
sizes = append(sizes, use.Size)
|
||||
}
|
||||
|
||||
for _, h := range image.History {
|
||||
for _, h := range ociimage.History {
|
||||
size := int64(0)
|
||||
if !h.EmptyLayer {
|
||||
if len(sizes) == 0 {
|
||||
@@ -83,20 +90,23 @@ func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imaget
|
||||
}
|
||||
|
||||
if len(history) != 0 {
|
||||
history[0].ID = desc.Digest.String()
|
||||
history[0].ID = desc.Target.Digest.String()
|
||||
|
||||
tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Digest.String())
|
||||
tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := make([]string, len(tagged))
|
||||
for i, t := range tagged {
|
||||
var tags []string
|
||||
for _, t := range tagged {
|
||||
if isDanglingImage(t) {
|
||||
continue
|
||||
}
|
||||
name, err := reference.ParseNamed(t.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags[i] = reference.FamiliarString(name)
|
||||
tags = append(tags, reference.FamiliarString(name))
|
||||
}
|
||||
history[0].Tags = tags
|
||||
}
|
||||
|
||||
@@ -86,11 +86,10 @@ func (i *ImageService) ImportImage(ctx context.Context, ref reference.Named, pla
|
||||
ociCfg := containerConfigToOciImageConfig(imageConfig)
|
||||
createdAt := time.Now()
|
||||
config := ocispec.Image{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
Created: &createdAt,
|
||||
Author: "",
|
||||
Config: ociCfg,
|
||||
Platform: *platform,
|
||||
Created: &createdAt,
|
||||
Author: "",
|
||||
Config: ociCfg,
|
||||
RootFS: ocispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []digest.Digest{uncompressedDigest},
|
||||
|
||||
@@ -6,20 +6,17 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/labels"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
timetypes "github.com/docker/docker/api/types/time"
|
||||
"github.com/moby/buildkit/util/attestation"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -87,72 +84,41 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions)
|
||||
continue
|
||||
}
|
||||
|
||||
err := images.Walk(ctx, images.HandlerFunc(func(ctx context.Context, desc v1.Descriptor) ([]v1.Descriptor, error) {
|
||||
if images.IsIndexType(desc.MediaType) {
|
||||
return images.Children(ctx, contentStore, desc)
|
||||
err := i.walkImageManifests(ctx, img, func(img *ImageManifest) error {
|
||||
if isPseudo, err := img.IsPseudoImage(ctx); isPseudo || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if images.IsManifestType(desc.MediaType) {
|
||||
// Ignore buildkit attestation manifests
|
||||
// https://github.com/moby/buildkit/blob/v0.11.4/docs/attestations/attestation-storage.md
|
||||
// This would have also been caught by the isImageManifest call below, but it requires
|
||||
// an additional content read and deserialization of Manifest.
|
||||
if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
|
||||
return nil, nil
|
||||
}
|
||||
available, err := img.CheckContentAvailable(ctx)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"manifest": img.Target(),
|
||||
"image": img.Name(),
|
||||
}).Warn("checking availability of platform specific manifest failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
mfst, err := images.Manifest(ctx, contentStore, desc, platforms.All)
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isImageManifest(mfst) {
|
||||
return nil, nil
|
||||
}
|
||||
image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
platform, err := getManifestPlatform(ctx, contentStore, desc, mfst.Config)
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
summaries = append(summaries, image)
|
||||
|
||||
pm := platforms.OnlyStrict(platform)
|
||||
available, _, _, missing, err := images.Check(ctx, contentStore, img.Target, pm)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"platform": platform,
|
||||
"image": img.Target,
|
||||
}).Warn("checking availability of platform content failed")
|
||||
return nil, nil
|
||||
}
|
||||
if !available || len(missing) > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c8dImage := containerd.NewImageWithPlatform(i.client, img, pm)
|
||||
image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, c8dImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summaries = append(summaries, image)
|
||||
|
||||
if opts.SharedSize {
|
||||
root = append(root, &chainIDs)
|
||||
for _, id := range chainIDs {
|
||||
layers[id] = layers[id] + 1
|
||||
}
|
||||
if opts.SharedSize {
|
||||
root = append(root, &chainIDs)
|
||||
for _, id := range chainIDs {
|
||||
layers[id] = layers[id] + 1
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}), img.Target)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -173,7 +139,7 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions)
|
||||
return summaries, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, image containerd.Image) (*types.ImageSummary, []digest.Digest, error) {
|
||||
func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, image *ImageManifest) (*types.ImageSummary, []digest.Digest, error) {
|
||||
diffIDs, err := image.RootFS(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -418,7 +384,7 @@ func setupLabelFilter(store content.Store, fltrs filters.Args) (func(image image
|
||||
// processing more content (otherwise it will run for all children).
|
||||
// It will be returned once a matching config is found.
|
||||
errFoundConfig := errors.New("success, found matching config")
|
||||
err := images.Dispatch(ctx, presentChildrenHandler(store, images.HandlerFunc(func(ctx context.Context, desc v1.Descriptor) (subdescs []v1.Descriptor, err error) {
|
||||
err := images.Dispatch(ctx, presentChildrenHandler(store, images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
|
||||
if !images.IsConfigType(desc.MediaType) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -509,35 +475,8 @@ func computeSharedSize(chainIDs []digest.Digest, layers map[digest.Digest]int, s
|
||||
return sharedSize, nil
|
||||
}
|
||||
|
||||
// getManifestPlatform returns a platform specified by the manifest descriptor
|
||||
// or reads it from its config.
|
||||
func getManifestPlatform(ctx context.Context, store content.Provider, manifestDesc, configDesc v1.Descriptor) (v1.Platform, error) {
|
||||
var platform v1.Platform
|
||||
if manifestDesc.Platform != nil {
|
||||
platform = *manifestDesc.Platform
|
||||
} else {
|
||||
// Config is technically a v1.Image, but it has the same member as v1.Platform
|
||||
// which makes the v1.Platform a subset of Image so we can unmarshal directly.
|
||||
if err := readConfig(ctx, store, configDesc, &platform); err != nil {
|
||||
return platform, err
|
||||
}
|
||||
}
|
||||
return platforms.Normalize(platform), nil
|
||||
}
|
||||
|
||||
// isImageManifest returns true if the manifest has any layer that is a known image layer.
|
||||
// Some manifests use the image media type for compatibility, even if they are not a real image.
|
||||
func isImageManifest(mfst v1.Manifest) bool {
|
||||
for _, l := range mfst.Layers {
|
||||
if images.IsLayerType(l.MediaType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// readConfig reads content pointed by the descriptor and unmarshals it into a specified output.
|
||||
func readConfig(ctx context.Context, store content.Provider, desc v1.Descriptor, out interface{}) error {
|
||||
func readConfig(ctx context.Context, store content.Provider, desc ocispec.Descriptor, out interface{}) error {
|
||||
data, err := content.ReadBlob(ctx, store, desc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read config content")
|
||||
|
||||
151
daemon/containerd/image_manifest.go
Normal file
151
daemon/containerd/image_manifest.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
cplatforms "github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/moby/buildkit/util/attestation"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotManifestOrIndex = errdefs.InvalidParameter(errors.New("descriptor is neither a manifest or index"))
|
||||
errNotManifest = errdefs.InvalidParameter(errors.New("descriptor isn't a manifest"))
|
||||
)
|
||||
|
||||
// walkImageManifests calls the handler for each locally present manifest in
|
||||
// the image. The image implements the containerd.Image interface, but all
|
||||
// operations act on the specific manifest instead of the index.
|
||||
func (i *ImageService) walkImageManifests(ctx context.Context, img containerdimages.Image, handler func(img *ImageManifest) error) error {
|
||||
desc := img.Target
|
||||
|
||||
handleManifest := func(ctx context.Context, d ocispec.Descriptor) error {
|
||||
platformImg, err := i.NewImageManifest(ctx, img, d)
|
||||
if err != nil {
|
||||
if err == errNotManifest {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return handler(platformImg)
|
||||
}
|
||||
|
||||
if containerdimages.IsManifestType(desc.MediaType) {
|
||||
return handleManifest(ctx, desc)
|
||||
}
|
||||
|
||||
if containerdimages.IsIndexType(desc.MediaType) {
|
||||
return i.walkPresentChildren(ctx, desc, handleManifest)
|
||||
}
|
||||
|
||||
return errNotManifestOrIndex
|
||||
}
|
||||
|
||||
type ImageManifest struct {
|
||||
containerd.Image
|
||||
|
||||
// Parent of the manifest (index/manifest list)
|
||||
RealTarget ocispec.Descriptor
|
||||
|
||||
manifest *ocispec.Manifest
|
||||
}
|
||||
|
||||
func (i *ImageService) NewImageManifest(ctx context.Context, img containerdimages.Image, manifestDesc ocispec.Descriptor) (*ImageManifest, error) {
|
||||
if !containerdimages.IsManifestType(manifestDesc.MediaType) {
|
||||
return nil, errNotManifest
|
||||
}
|
||||
|
||||
parent := img.Target
|
||||
img.Target = manifestDesc
|
||||
|
||||
c8dImg := containerd.NewImageWithPlatform(i.client, img, cplatforms.All)
|
||||
return &ImageManifest{
|
||||
Image: c8dImg,
|
||||
RealTarget: parent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (im *ImageManifest) Metadata() containerdimages.Image {
|
||||
md := im.Image.Metadata()
|
||||
md.Target = im.RealTarget
|
||||
return md
|
||||
}
|
||||
|
||||
// IsPseudoImage returns false if the manifest has no layers or any of its layers is a known image layer.
|
||||
// Some manifests use the image media type for compatibility, even if they are not a real image.
|
||||
func (im *ImageManifest) IsPseudoImage(ctx context.Context) (bool, error) {
|
||||
desc := im.Target()
|
||||
|
||||
// Quick check for buildkit attestation manifests
|
||||
// https://github.com/moby/buildkit/blob/v0.11.4/docs/attestations/attestation-storage.md
|
||||
// This would have also been caught by the layer check below, but it requires
|
||||
// an additional content read and deserialization of Manifest.
|
||||
if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
mfst, err := im.Manifest(ctx)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if len(mfst.Layers) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
for _, l := range mfst.Layers {
|
||||
if images.IsLayerType(l.MediaType) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (im *ImageManifest) Manifest(ctx context.Context) (ocispec.Manifest, error) {
|
||||
if im.manifest != nil {
|
||||
return *im.manifest, nil
|
||||
}
|
||||
|
||||
mfst, err := readManifest(ctx, im.ContentStore(), im.Target())
|
||||
if err != nil {
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
im.manifest = &mfst
|
||||
return mfst, nil
|
||||
}
|
||||
|
||||
func (im *ImageManifest) CheckContentAvailable(ctx context.Context) (bool, error) {
|
||||
// The target is already a platform-specific manifest, so no need to match platform.
|
||||
pm := cplatforms.All
|
||||
|
||||
available, _, _, missing, err := containerdimages.Check(ctx, im.ContentStore(), im.Target(), pm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !available || len(missing) > 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func readManifest(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (ocispec.Manifest, error) {
|
||||
p, err := content.ReadBlob(ctx, store, desc)
|
||||
if err != nil {
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
var mfst ocispec.Manifest
|
||||
if err := json.Unmarshal(p, &mfst); err != nil {
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
return mfst, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
@@ -69,35 +70,50 @@ func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFu
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// How many images make reference to a particular target digest.
|
||||
digestRefCount := map[digest.Digest]int{}
|
||||
// Images considered for pruning.
|
||||
imagesToPrune := map[string]containerdimages.Image{}
|
||||
for _, img := range allImages {
|
||||
digestRefCount[img.Target.Digest] += 1
|
||||
|
||||
if !danglingOnly || isDanglingImage(img) {
|
||||
imagesToPrune[img.Name] = img
|
||||
canBePruned := filterFunc(img)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"image": img.Name,
|
||||
"canBePruned": canBePruned,
|
||||
}).Debug("considering image for pruning")
|
||||
|
||||
if canBePruned {
|
||||
imagesToPrune[img.Name] = img
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
for name, img := range imagesToPrune {
|
||||
filteredOut := !filterFunc(img)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"image": name,
|
||||
"filteredOut": filteredOut,
|
||||
}).Debug("filtering image")
|
||||
// Image specified by digests that are used by containers.
|
||||
usedDigests := map[digest.Digest]struct{}{}
|
||||
|
||||
if filteredOut {
|
||||
delete(imagesToPrune, name)
|
||||
}
|
||||
}
|
||||
|
||||
containers := i.containers.List()
|
||||
|
||||
var errs error
|
||||
// Exclude images used by existing containers
|
||||
for _, ctr := range containers {
|
||||
for _, ctr := range i.containers.List() {
|
||||
// If the original image was deleted, make sure we don't delete the dangling image
|
||||
delete(imagesToPrune, danglingImageName(ctr.ImageID.Digest()))
|
||||
|
||||
// Config.Image is the image reference passed by user.
|
||||
// For example: container created by `docker run alpine` will have Image="alpine"
|
||||
// Warning: This doesn't handle truncated ids:
|
||||
// `docker run 124c7d2` will have Image="124c7d270790"
|
||||
// Config.ImageID is the resolved content digest based on the user's Config.Image.
|
||||
// For example: container created by:
|
||||
// `docker run alpine` will have Config.Image="alpine"
|
||||
// `docker run 82d1e9d` will have Config.Image="82d1e9d"
|
||||
// but both will have ImageID="sha256:82d1e9d7ed48a7523bdebc18cf6290bdb97b82302a8a9c27d4fe885949ea94d1"
|
||||
imageDgst := ctr.ImageID.Digest()
|
||||
|
||||
// If user didn't specify an explicit image, mark the digest as used.
|
||||
normalizedImageID := "sha256:" + strings.TrimPrefix(ctr.Config.Image, "sha256:")
|
||||
if strings.HasPrefix(imageDgst.String(), normalizedImageID) {
|
||||
usedDigests[imageDgst] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(ctr.Config.Image)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"ctr": ctr.ID,
|
||||
@@ -106,12 +122,28 @@ func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFu
|
||||
}).Debug("filtering container's image")
|
||||
|
||||
if err == nil {
|
||||
// If user provided a specific image name, exclude that image.
|
||||
name := reference.TagNameOnly(ref)
|
||||
delete(imagesToPrune, name.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Create dangling images for images that will be deleted but are still in use.
|
||||
for _, img := range imagesToPrune {
|
||||
dgst := img.Target.Digest
|
||||
|
||||
digestRefCount[dgst] -= 1
|
||||
if digestRefCount[dgst] == 0 {
|
||||
if _, isUsed := usedDigests[dgst]; isUsed {
|
||||
if err := i.ensureDanglingImage(ctx, img); err != nil {
|
||||
return &report, errors.Wrapf(err, "failed to create ensure dangling image for %s", img.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
possiblyDeletedConfigs := map[digest.Digest]struct{}{}
|
||||
var errs error
|
||||
|
||||
// Workaround for https://github.com/moby/buildkit/issues/3797
|
||||
defer func() {
|
||||
@@ -125,11 +157,12 @@ func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFu
|
||||
|
||||
blobs := []ocispec.Descriptor{}
|
||||
|
||||
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) {
|
||||
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) error {
|
||||
blobs = append(blobs, desc)
|
||||
if containerdimages.IsConfigType(desc.MediaType) {
|
||||
possiblyDeletedConfigs[desc.Digest] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
@@ -186,10 +219,11 @@ func (i *ImageService) unleaseSnapshotsFromDeletedConfigs(ctx context.Context, p
|
||||
|
||||
var errs error
|
||||
for _, img := range all {
|
||||
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) {
|
||||
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) error {
|
||||
if containerdimages.IsConfigType(desc.MediaType) {
|
||||
delete(possiblyDeletedConfigs, desc.Digest)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
|
||||
@@ -14,13 +14,13 @@ import (
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PullImage initiates a pull operation. image is the repository name to pull, and
|
||||
// tagOrDigest may be either empty, or indicate a specific tag or digest to pull.
|
||||
func (i *ImageService) PullImage(ctx context.Context, image, tagOrDigest string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
func (i *ImageService) PullImage(ctx context.Context, image, tagOrDigest string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
var opts []containerd.RemoteOpt
|
||||
if platform != nil {
|
||||
opts = append(opts, containerd.WithPlatform(platforms.Format(*platform)))
|
||||
@@ -45,11 +45,11 @@ func (i *ImageService) PullImage(ctx context.Context, image, tagOrDigest string,
|
||||
}
|
||||
}
|
||||
|
||||
resolver, _ := i.newResolverFromAuthConfig(authConfig)
|
||||
resolver, _ := i.newResolverFromAuthConfig(ctx, authConfig)
|
||||
opts = append(opts, containerd.WithResolver(resolver))
|
||||
|
||||
jobs := newJobs()
|
||||
h := images.HandlerFunc(func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
|
||||
h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
if desc.MediaType != images.MediaTypeDockerSchema1Manifest {
|
||||
jobs.Add(desc)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (i *ImageService) PushImage(ctx context.Context, targetRef reference.Named,
|
||||
target := img.Target
|
||||
store := i.client.ContentStore()
|
||||
|
||||
resolver, tracker := i.newResolverFromAuthConfig(authConfig)
|
||||
resolver, tracker := i.newResolverFromAuthConfig(ctx, authConfig)
|
||||
progress := pushProgress{Tracker: tracker}
|
||||
jobsQueue := newJobs()
|
||||
finishProgress := jobsQueue.showProgress(ctx, out, combinedProgress([]progressUpdater{
|
||||
|
||||
@@ -3,16 +3,17 @@ package containerd
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// PrepareSnapshot prepares a snapshot from a parent image for a container
|
||||
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *v1.Platform) error {
|
||||
desc, err := i.resolveDescriptor(ctx, parentImage)
|
||||
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform) error {
|
||||
img, err := i.resolveImage(ctx, parentImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -24,7 +25,19 @@ func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentIma
|
||||
matcher = platforms.Only(*platform)
|
||||
}
|
||||
|
||||
desc, err = containerdimages.Config(ctx, cs, desc, matcher)
|
||||
platformImg := containerd.NewImageWithPlatform(i.client, img, matcher)
|
||||
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unpacked {
|
||||
if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
desc, err := containerdimages.Config(ctx, cs, img.Target, matcher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ package containerd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -19,17 +17,13 @@ func (i *ImageService) Mount(ctx context.Context, container *container.Container
|
||||
return err
|
||||
}
|
||||
|
||||
// The temporary location will be under /var/lib/docker/... because
|
||||
// we set the `TMPDIR`
|
||||
root, err := os.MkdirTemp("", fmt.Sprintf("%s_rootfs-mount", container.ID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp dir: %w", err)
|
||||
}
|
||||
|
||||
if err := mount.All(mounts, root); err != nil {
|
||||
var root string
|
||||
if root, err = i.refCountMounter.Mount(mounts, container.ID); err != nil {
|
||||
return fmt.Errorf("failed to mount %s: %w", root, err)
|
||||
}
|
||||
|
||||
logrus.WithField("container", container.ID).Debugf("container mounted via snapshotter: %v", root)
|
||||
|
||||
container.BaseFS = root
|
||||
return nil
|
||||
}
|
||||
@@ -38,15 +32,10 @@ func (i *ImageService) Mount(ctx context.Context, container *container.Container
|
||||
func (i *ImageService) Unmount(ctx context.Context, container *container.Container) error {
|
||||
root := container.BaseFS
|
||||
|
||||
if err := mount.UnmountAll(root, 0); err != nil {
|
||||
if err := i.refCountMounter.Unmount(root); err != nil {
|
||||
logrus.WithField("container", container.ID).WithError(err).Error("error unmounting container")
|
||||
return fmt.Errorf("failed to unmount %s: %w", root, err)
|
||||
}
|
||||
|
||||
if err := os.Remove(root); err != nil {
|
||||
logrus.WithError(err).WithField("dir", root).Error("failed to remove mount temp dir")
|
||||
}
|
||||
|
||||
container.BaseFS = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (p pullProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out pro
|
||||
} else if p.ShowExists {
|
||||
out.WriteProgress(progress.Progress{
|
||||
ID: stringid.TruncateID(j.Digest.Encoded()),
|
||||
Action: "Exists",
|
||||
Action: "Already exists",
|
||||
HideCounts: true,
|
||||
LastUpdate: true,
|
||||
})
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
"github.com/containerd/containerd/version"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/pkg/useragent"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (i *ImageService) newResolverFromAuthConfig(authConfig *registrytypes.AuthConfig) (remotes.Resolver, docker.StatusTracker) {
|
||||
func (i *ImageService) newResolverFromAuthConfig(ctx context.Context, authConfig *registrytypes.AuthConfig) (remotes.Resolver, docker.StatusTracker) {
|
||||
tracker := docker.NewInMemoryTracker()
|
||||
hostsFn := i.registryHosts.RegistryHosts()
|
||||
|
||||
hosts := hostsWrapper(hostsFn, authConfig, i.registryService)
|
||||
headers := http.Header{}
|
||||
headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx, useragent.VersionInfo{Name: "containerd-client", Version: version.Version}, useragent.VersionInfo{Name: "storage-driver", Version: i.snapshotter}))
|
||||
|
||||
return docker.NewResolver(docker.ResolverOptions{
|
||||
Hosts: hosts,
|
||||
Tracker: tracker,
|
||||
Headers: headers,
|
||||
}), tracker
|
||||
}
|
||||
|
||||
func hostsWrapper(hostsFn docker.RegistryHosts, authConfig *registrytypes.AuthConfig, regService RegistryConfigProvider) docker.RegistryHosts {
|
||||
func hostsWrapper(hostsFn docker.RegistryHosts, optAuthConfig *registrytypes.AuthConfig, regService RegistryConfigProvider) docker.RegistryHosts {
|
||||
var authorizer docker.Authorizer
|
||||
if optAuthConfig != nil {
|
||||
authorizer = docker.NewDockerAuthorizer(authorizationCredsFromAuthConfig(*optAuthConfig))
|
||||
}
|
||||
|
||||
return func(n string) ([]docker.RegistryHost, error) {
|
||||
hosts, err := hostsFn(n)
|
||||
if err != nil {
|
||||
@@ -33,12 +45,7 @@ func hostsWrapper(hostsFn docker.RegistryHosts, authConfig *registrytypes.AuthCo
|
||||
|
||||
for i := range hosts {
|
||||
if hosts[i].Authorizer == nil {
|
||||
var opts []docker.AuthorizerOpt
|
||||
if authConfig != nil {
|
||||
opts = append(opts, authorizationCredsFromAuthConfig(*authConfig))
|
||||
}
|
||||
hosts[i].Authorizer = docker.NewDockerAuthorizer(opts...)
|
||||
|
||||
hosts[i].Authorizer = authorizer
|
||||
isInsecure := regService.IsInsecureRegistry(hosts[i].Host)
|
||||
if hosts[i].Client.Transport != nil && isInsecure {
|
||||
hosts[i].Client.Transport = httpFallback{super: hosts[i].Client.Transport}
|
||||
@@ -51,13 +58,16 @@ func hostsWrapper(hostsFn docker.RegistryHosts, authConfig *registrytypes.AuthCo
|
||||
|
||||
func authorizationCredsFromAuthConfig(authConfig registrytypes.AuthConfig) docker.AuthorizerOpt {
|
||||
cfgHost := registry.ConvertToHostname(authConfig.ServerAddress)
|
||||
if cfgHost == registry.IndexHostname {
|
||||
if cfgHost == "" || cfgHost == registry.IndexHostname {
|
||||
cfgHost = registry.DefaultRegistryHost
|
||||
}
|
||||
|
||||
return docker.WithAuthCreds(func(host string) (string, string, error) {
|
||||
if cfgHost != host {
|
||||
logrus.WithField("host", host).WithField("cfgHost", cfgHost).Warn("Host doesn't match")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"host": host,
|
||||
"cfgHost": cfgHost,
|
||||
}).Warn("Host doesn't match")
|
||||
return "", "", nil
|
||||
}
|
||||
if authConfig.IdentityToken != "" {
|
||||
|
||||
@@ -10,13 +10,16 @@ import (
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/docker/distribution/reference"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/container"
|
||||
daemonevents "github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/daemon/images"
|
||||
"github.com/docker/docker/daemon/snapshotter"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -33,6 +36,7 @@ type ImageService struct {
|
||||
registryService RegistryConfigProvider
|
||||
eventsService *daemonevents.Events
|
||||
pruneRunning atomic.Bool
|
||||
refCountMounter snapshotter.Mounter
|
||||
}
|
||||
|
||||
type RegistryHostsProvider interface {
|
||||
@@ -41,15 +45,17 @@ type RegistryHostsProvider interface {
|
||||
|
||||
type RegistryConfigProvider interface {
|
||||
IsInsecureRegistry(host string) bool
|
||||
ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error)
|
||||
}
|
||||
|
||||
type ImageServiceConfig struct {
|
||||
Client *containerd.Client
|
||||
Containers container.Store
|
||||
Snapshotter string
|
||||
HostsProvider RegistryHostsProvider
|
||||
Registry RegistryConfigProvider
|
||||
EventsService *daemonevents.Events
|
||||
Client *containerd.Client
|
||||
Containers container.Store
|
||||
Snapshotter string
|
||||
HostsProvider RegistryHostsProvider
|
||||
Registry RegistryConfigProvider
|
||||
EventsService *daemonevents.Events
|
||||
RefCountMounter snapshotter.Mounter
|
||||
}
|
||||
|
||||
// NewService creates a new ImageService.
|
||||
@@ -61,6 +67,7 @@ func NewService(config ImageServiceConfig) *ImageService {
|
||||
registryHosts: config.HostsProvider,
|
||||
registryService: config.Registry,
|
||||
eventsService: config.EventsService,
|
||||
refCountMounter: config.RefCountMounter,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -18,10 +19,10 @@ func (i *ImageService) softImageDelete(ctx context.Context, img containerdimages
|
||||
|
||||
// If the image already exists, persist it as dangling image
|
||||
// but only if no other image has the same target.
|
||||
digest := img.Target.Digest.String()
|
||||
imgs, err := is.List(ctx, "target.digest=="+digest)
|
||||
dgst := img.Target.Digest.String()
|
||||
imgs, err := is.List(ctx, "target.digest=="+dgst)
|
||||
if err != nil {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to check if there are images targeting digest %s", digest))
|
||||
return errdefs.System(errors.Wrapf(err, "failed to check if there are images targeting digest %s", dgst))
|
||||
}
|
||||
|
||||
// From this point explicitly ignore the passed context
|
||||
@@ -29,19 +30,12 @@ func (i *ImageService) softImageDelete(ctx context.Context, img containerdimages
|
||||
|
||||
// Create dangling image if this is the last image pointing to this target.
|
||||
if len(imgs) == 1 {
|
||||
danglingImage := img
|
||||
|
||||
danglingImage.Name = danglingImageName(img.Target.Digest)
|
||||
delete(danglingImage.Labels, "io.containerd.image.name")
|
||||
delete(danglingImage.Labels, "org.opencontainers.image.ref.name")
|
||||
|
||||
_, err = is.Create(context.Background(), danglingImage)
|
||||
err = i.ensureDanglingImage(context.Background(), img)
|
||||
|
||||
// Error out in case we couldn't persist the old image.
|
||||
// If it already exists, then just continue.
|
||||
if err != nil && !cerrdefs.IsAlreadyExists(err) {
|
||||
if err != nil {
|
||||
return errdefs.System(errors.Wrapf(err, "failed to create a dangling image for the replaced image %s with digest %s",
|
||||
danglingImage.Name, danglingImage.Target.Digest.String()))
|
||||
img.Name, img.Target.Digest.String()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +50,29 @@ func (i *ImageService) softImageDelete(ctx context.Context, img containerdimages
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageService) ensureDanglingImage(ctx context.Context, from containerdimages.Image) error {
|
||||
danglingImage := from
|
||||
|
||||
danglingImage.Labels = make(map[string]string)
|
||||
for k, v := range from.Labels {
|
||||
switch k {
|
||||
case containerdimages.AnnotationImageName, ocispec.AnnotationRefName:
|
||||
// Don't copy name labels.
|
||||
default:
|
||||
danglingImage.Labels[k] = v
|
||||
}
|
||||
}
|
||||
danglingImage.Name = danglingImageName(from.Target.Digest)
|
||||
|
||||
_, err := i.client.ImageService().Create(context.Background(), danglingImage)
|
||||
// If it already exists, then just continue.
|
||||
if cerrdefs.IsAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func danglingImageName(digest digest.Digest) string {
|
||||
return "moby-dangling@" + digest.String()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/runconfig"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -74,7 +74,7 @@ func (daemon *Daemon) containerCreate(ctx context.Context, opts createOpts) (con
|
||||
}
|
||||
if img != nil {
|
||||
p := maximumSpec()
|
||||
imgPlat := v1.Platform{
|
||||
imgPlat := ocispec.Platform{
|
||||
OS: img.OS,
|
||||
Architecture: img.Architecture,
|
||||
Variant: img.Variant,
|
||||
@@ -117,7 +117,7 @@ func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *contai
|
||||
var (
|
||||
ctr *container.Container
|
||||
img *image.Image
|
||||
imgManifest *v1.Descriptor
|
||||
imgManifest *ocispec.Descriptor
|
||||
imgID image.ID
|
||||
err error
|
||||
os = runtime.GOOS
|
||||
@@ -345,7 +345,7 @@ func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
|
||||
}
|
||||
|
||||
// maximumSpec returns the distribution platform with maximum compatibility for the current node.
|
||||
func maximumSpec() v1.Platform {
|
||||
func maximumSpec() ocispec.Platform {
|
||||
p := platforms.DefaultSpec()
|
||||
if p.Architecture == "amd64" {
|
||||
p.Variant = archvariant.AMD64Variant()
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/docker/docker/daemon/images"
|
||||
dlogger "github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/daemon/snapshotter"
|
||||
"github.com/docker/docker/daemon/stats"
|
||||
"github.com/docker/docker/distribution"
|
||||
dmetadata "github.com/docker/docker/distribution/metadata"
|
||||
@@ -129,6 +130,13 @@ type Daemon struct {
|
||||
// It stores metadata for the content store (used for manifest caching)
|
||||
// This needs to be closed on daemon exit
|
||||
mdDB *bbolt.DB
|
||||
|
||||
usesSnapshotter bool
|
||||
}
|
||||
|
||||
// ID returns the daemon id
|
||||
func (daemon *Daemon) ID() string {
|
||||
return daemon.id
|
||||
}
|
||||
|
||||
// StoreHosts stores the addresses the daemon is listening on
|
||||
@@ -153,16 +161,7 @@ func (daemon *Daemon) Features() *map[string]bool {
|
||||
|
||||
// UsesSnapshotter returns true if feature flag to use containerd snapshotter is enabled
|
||||
func (daemon *Daemon) UsesSnapshotter() bool {
|
||||
// TEST_INTEGRATION_USE_SNAPSHOTTER is used for integration tests only.
|
||||
if os.Getenv("TEST_INTEGRATION_USE_SNAPSHOTTER") != "" {
|
||||
return true
|
||||
}
|
||||
if daemon.configStore.Features != nil {
|
||||
if b, ok := daemon.configStore.Features["containerd-snapshotter"]; ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
return daemon.usesSnapshotter
|
||||
}
|
||||
|
||||
// RegistryHosts returns registry configuration in containerd resolvers format
|
||||
@@ -421,6 +420,8 @@ func (daemon *Daemon) restore() error {
|
||||
if es != nil {
|
||||
ces.ExitCode = int(es.ExitCode())
|
||||
ces.ExitedAt = es.ExitTime()
|
||||
} else {
|
||||
ces.ExitCode = 255
|
||||
}
|
||||
c.SetStopped(&ces)
|
||||
daemon.Cleanup(c)
|
||||
@@ -796,6 +797,13 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
|
||||
startupDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
// TEST_INTEGRATION_USE_SNAPSHOTTER is used for integration tests only.
|
||||
if os.Getenv("TEST_INTEGRATION_USE_SNAPSHOTTER") != "" {
|
||||
d.usesSnapshotter = true
|
||||
} else {
|
||||
d.usesSnapshotter = config.Features["containerd-snapshotter"]
|
||||
}
|
||||
|
||||
// Ensure the daemon is properly shutdown if there is a failure during
|
||||
// initialization
|
||||
defer func() {
|
||||
@@ -1018,12 +1026,13 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
|
||||
return nil, err
|
||||
}
|
||||
d.imageService = ctrd.NewService(ctrd.ImageServiceConfig{
|
||||
Client: d.containerdCli,
|
||||
Containers: d.containers,
|
||||
Snapshotter: driverName,
|
||||
HostsProvider: d,
|
||||
Registry: d.registryService,
|
||||
EventsService: d.EventsService,
|
||||
Client: d.containerdCli,
|
||||
Containers: d.containers,
|
||||
Snapshotter: driverName,
|
||||
HostsProvider: d,
|
||||
Registry: d.registryService,
|
||||
EventsService: d.EventsService,
|
||||
RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idMapping),
|
||||
})
|
||||
} else {
|
||||
layerStore, err := layer.NewStoreFromOptions(layer.StoreOptions{
|
||||
|
||||
@@ -105,7 +105,10 @@ func getMemoryResources(config containertypes.Resources) *specs.LinuxMemory {
|
||||
memory.KernelTCP = &config.KernelMemoryTCP
|
||||
}
|
||||
|
||||
return &memory
|
||||
if memory != (specs.LinuxMemory{}) {
|
||||
return &memory
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPidsLimit(config containertypes.Resources) *specs.LinuxPids {
|
||||
@@ -127,7 +130,7 @@ func getCPUResources(config containertypes.Resources) (*specs.LinuxCPU, error) {
|
||||
if config.CPUShares < 0 {
|
||||
return nil, fmt.Errorf("shares: invalid argument")
|
||||
}
|
||||
if config.CPUShares >= 0 {
|
||||
if config.CPUShares > 0 {
|
||||
shares := uint64(config.CPUShares)
|
||||
cpu.Shares = &shares
|
||||
}
|
||||
@@ -168,7 +171,10 @@ func getCPUResources(config containertypes.Resources) (*specs.LinuxCPU, error) {
|
||||
cpu.RealtimeRuntime = &c
|
||||
}
|
||||
|
||||
return &cpu, nil
|
||||
if cpu != (specs.LinuxCPU{}) {
|
||||
return &cpu, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlkioWeightDevices(config containertypes.Resources) ([]specs.LinuxWeightDevice, error) {
|
||||
@@ -1394,19 +1400,13 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
|
||||
// conditionalMountOnStart is a platform specific helper function during the
|
||||
// container start to call mount.
|
||||
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
|
||||
if !daemon.UsesSnapshotter() {
|
||||
return daemon.Mount(container)
|
||||
}
|
||||
return nil
|
||||
return daemon.Mount(container)
|
||||
}
|
||||
|
||||
// conditionalUnmountOnCleanup is a platform specific helper function called
|
||||
// during the cleanup of a container to unmount.
|
||||
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
|
||||
if !daemon.UsesSnapshotter() {
|
||||
return daemon.Unmount(container)
|
||||
}
|
||||
return nil
|
||||
return daemon.Unmount(container)
|
||||
}
|
||||
|
||||
// setDefaultIsolation determines the default isolation mode for the
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -17,8 +16,6 @@ func init() {
|
||||
// errors or hangs to be debugged directly from the test process.
|
||||
untar = archive.UntarUncompressed
|
||||
graphdriver.ApplyUncompressedLayer = archive.ApplyUncompressedLayer
|
||||
|
||||
reexec.Init()
|
||||
}
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -18,8 +17,6 @@ func init() {
|
||||
// errors or hangs to be debugged directly from the test process.
|
||||
untar = archive.UntarUncompressed
|
||||
graphdriver.ApplyUncompressedLayer = archive.ApplyUncompressedLayer
|
||||
|
||||
reexec.Init()
|
||||
}
|
||||
|
||||
func skipIfNaive(t *testing.T) {
|
||||
|
||||
@@ -7,14 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Init()
|
||||
}
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
// Make sure to put new tests between TestVfsSetup and TestVfsTeardown
|
||||
func TestVfsSetup(t *testing.T) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
zfs "github.com/mistifyio/go-zfs"
|
||||
zfs "github.com/mistifyio/go-zfs/v3"
|
||||
"github.com/moby/locker"
|
||||
"github.com/moby/sys/mount"
|
||||
"github.com/moby/sys/mountinfo"
|
||||
|
||||
@@ -160,7 +160,6 @@ func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container
|
||||
info.Lock()
|
||||
defer info.Unlock()
|
||||
if info.ExitCode == nil {
|
||||
info.Unlock()
|
||||
return 0, fmt.Errorf("healthcheck for container %s has no exit code", cntr.ID)
|
||||
}
|
||||
return *info.ExitCode, nil
|
||||
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ImageService is a temporary interface to assist in the migration to the
|
||||
@@ -25,9 +26,9 @@ import (
|
||||
type ImageService interface {
|
||||
// Images
|
||||
|
||||
PullImage(ctx context.Context, name, tag string, platform *v1.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PullImage(ctx context.Context, name, tag string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
CreateImage(config []byte, parent string) (builder.Image, error)
|
||||
CreateImage(ctx context.Context, config []byte, parent string, contentStoreDigest digest.Digest) (builder.Image, error)
|
||||
ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error)
|
||||
ExportImage(ctx context.Context, names []string, outStream io.Writer) error
|
||||
PerformWithBaseFS(ctx context.Context, c *container.Container, fn func(string) error) error
|
||||
@@ -36,7 +37,7 @@ type ImageService interface {
|
||||
LogImageEvent(imageID, refName, action string)
|
||||
CountImages() int
|
||||
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error)
|
||||
ImportImage(ctx context.Context, ref reference.Named, platform *v1.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error)
|
||||
ImportImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error)
|
||||
TagImage(ctx context.Context, imageID image.ID, newTag reference.Named) error
|
||||
GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error)
|
||||
ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error)
|
||||
@@ -45,8 +46,8 @@ type ImageService interface {
|
||||
|
||||
// Containerd related methods
|
||||
|
||||
PrepareSnapshot(ctx context.Context, id string, image string, platform *v1.Platform) error
|
||||
GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*v1.Descriptor, error)
|
||||
PrepareSnapshot(ctx context.Context, id string, image string, platform *ocispec.Platform) error
|
||||
GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error)
|
||||
|
||||
// Layers
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ import (
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -40,19 +39,19 @@ func (e ErrImageDoesNotExist) Error() string {
|
||||
func (e ErrImageDoesNotExist) NotFound() {}
|
||||
|
||||
type manifestList struct {
|
||||
Manifests []specs.Descriptor `json:"manifests"`
|
||||
Manifests []ocispec.Descriptor `json:"manifests"`
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
Config specs.Descriptor `json:"config"`
|
||||
Config ocispec.Descriptor `json:"config"`
|
||||
}
|
||||
|
||||
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, image string, platform *v1.Platform) error {
|
||||
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, image string, platform *ocispec.Platform) error {
|
||||
// Only makes sense when conatinerd image store is used
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.Image, platform specs.Platform) (bool, error) {
|
||||
func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.Image, platform ocispec.Platform) (bool, error) {
|
||||
logger := logrus.WithField("image", img.ID).WithField("desiredPlatform", platforms.Format(platform))
|
||||
|
||||
ls, leaseErr := i.leases.ListResources(ctx, leases.Lease{ID: imageKey(img.ID().String())})
|
||||
@@ -81,7 +80,7 @@ func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.I
|
||||
continue
|
||||
}
|
||||
|
||||
ra, err := i.content.ReaderAt(ctx, specs.Descriptor{Digest: digest.Digest(r.ID)})
|
||||
ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: digest.Digest(r.ID)})
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
continue
|
||||
@@ -107,12 +106,12 @@ func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.I
|
||||
|
||||
for _, md := range ml.Manifests {
|
||||
switch md.MediaType {
|
||||
case specs.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
|
||||
case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
p := specs.Platform{
|
||||
p := ocispec.Platform{
|
||||
Architecture: md.Platform.Architecture,
|
||||
OS: md.Platform.OS,
|
||||
Variant: md.Platform.Variant,
|
||||
@@ -124,7 +123,7 @@ func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.I
|
||||
|
||||
// Here we have a platform match for the referenced manifest, let's make sure the manifest is actually for the image config we are using.
|
||||
|
||||
ra, err := i.content.ReaderAt(ctx, specs.Descriptor{Digest: md.Digest})
|
||||
ra, err := i.content.ReaderAt(ctx, ocispec.Descriptor{Digest: md.Digest})
|
||||
if err != nil {
|
||||
logger.WithField("otherDigest", md.Digest).WithError(err).Error("Could not get reader for manifest")
|
||||
continue
|
||||
@@ -192,7 +191,7 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*v1.Descriptor, error) {
|
||||
func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*ocispec.Descriptor, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
@@ -202,7 +201,7 @@ func (i *ImageService) getImage(ctx context.Context, refOrID string, options ima
|
||||
return
|
||||
}
|
||||
|
||||
imgPlat := specs.Platform{
|
||||
imgPlat := ocispec.Platform{
|
||||
OS: retImg.OS,
|
||||
Architecture: retImg.Architecture,
|
||||
Variant: retImg.Variant,
|
||||
@@ -272,16 +271,16 @@ func (i *ImageService) getImage(ctx context.Context, refOrID string, options ima
|
||||
// The reason for this is that CPU variant is not even if the official image config spec as of this writing.
|
||||
// See: https://github.com/opencontainers/image-spec/pull/809
|
||||
// Since Docker tends to compare platforms from the image config, we need to handle this case.
|
||||
func OnlyPlatformWithFallback(p specs.Platform) platforms.Matcher {
|
||||
func OnlyPlatformWithFallback(p ocispec.Platform) platforms.Matcher {
|
||||
return &onlyFallbackMatcher{only: platforms.Only(p), p: platforms.Normalize(p)}
|
||||
}
|
||||
|
||||
type onlyFallbackMatcher struct {
|
||||
only platforms.Matcher
|
||||
p specs.Platform
|
||||
p ocispec.Platform
|
||||
}
|
||||
|
||||
func (m *onlyFallbackMatcher) Match(other specs.Platform) bool {
|
||||
func (m *onlyFallbackMatcher) Match(other ocispec.Platform) bool {
|
||||
if m.only.Match(other) {
|
||||
// It matches, no reason to fallback
|
||||
return true
|
||||
|
||||
@@ -19,7 +19,8 @@ import (
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
registrypkg "github.com/docker/docker/registry"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -30,6 +31,10 @@ type roLayer struct {
|
||||
roLayer layer.Layer
|
||||
}
|
||||
|
||||
func (l *roLayer) ContentStoreDigest() digest.Digest {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *roLayer) DiffID() layer.DiffID {
|
||||
if l.roLayer == nil {
|
||||
return layer.DigestSHA256EmptyTar
|
||||
@@ -144,7 +149,7 @@ func newROLayerForImage(img *image.Image, layerStore layer.Store) (builder.ROLay
|
||||
}
|
||||
|
||||
// TODO: could this use the regular daemon PullImage ?
|
||||
func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.AuthConfig, output io.Writer, platform *specs.Platform) (*image.Image, error) {
|
||||
func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.AuthConfig, output io.Writer, platform *ocispec.Platform) (*image.Image, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -169,7 +174,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
|
||||
|
||||
img, err := i.GetImage(ctx, name, imagetypes.GetImageOpts{Platform: platform})
|
||||
if errdefs.IsNotFound(err) && img != nil && platform != nil {
|
||||
imgPlat := specs.Platform{
|
||||
imgPlat := ocispec.Platform{
|
||||
OS: img.OS,
|
||||
Architecture: img.BaseImgArch(),
|
||||
Variant: img.BaseImgVariant(),
|
||||
@@ -241,7 +246,7 @@ func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID s
|
||||
// CreateImage creates a new image by adding a config and ID to the image store.
|
||||
// This is similar to LoadImage() except that it receives JSON encoded bytes of
|
||||
// an image instead of a tar archive.
|
||||
func (i *ImageService) CreateImage(config []byte, parent string) (builder.Image, error) {
|
||||
func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent string, _ digest.Digest) (builder.Image, error) {
|
||||
id, err := i.imageStore.Create(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create image")
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ImportImage imports an image, getting the archived layer data from layerReader.
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
// If the platform is nil, the default host platform is used.
|
||||
// Message is used as the image's history comment.
|
||||
// Image configuration is derived from the dockerfile instructions in changes.
|
||||
func (i *ImageService) ImportImage(ctx context.Context, newRef reference.Named, platform *specs.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error) {
|
||||
func (i *ImageService) ImportImage(ctx context.Context, newRef reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error) {
|
||||
if platform == nil {
|
||||
def := platforms.DefaultSpec()
|
||||
platform = &def
|
||||
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PullImage initiates a pull operation. image is the repository name to pull, and
|
||||
// tag may be either empty, or indicate a specific tag to pull.
|
||||
func (i *ImageService) PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
func (i *ImageService) PullImage(ctx context.Context, image, tag string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
start := time.Now()
|
||||
// Special case: "pull -a" may send an image name with a
|
||||
// trailing :. This is ugly, but let's not break API
|
||||
@@ -63,7 +63,7 @@ func (i *ImageService) PullImage(ctx context.Context, image, tag string, platfor
|
||||
// we allow the image to have a non-matching architecture. The code
|
||||
// below checks for this situation, and returns a warning to the client,
|
||||
// as well as logging it to the daemon logs.
|
||||
img, err := i.GetImage(ctx, image, imagetypes.GetImageOpts{Platform: platform})
|
||||
img, err := i.GetImage(ctx, ref.String(), imagetypes.GetImageOpts{Platform: platform})
|
||||
|
||||
// Note that this is a special case where GetImage returns both an image
|
||||
// and an error: https://github.com/docker/docker/blob/v20.10.7/daemon/images/image.go#L175-L183
|
||||
@@ -79,7 +79,7 @@ func (i *ImageService) PullImage(ctx context.Context, image, tag string, platfor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
|
||||
// Include a buffer so that slow client connections don't affect
|
||||
// transfer performance.
|
||||
progressChan := make(chan progress.Progress, 100)
|
||||
|
||||
@@ -3,30 +3,30 @@ package images
|
||||
import (
|
||||
"testing"
|
||||
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestOnlyPlatformWithFallback(t *testing.T) {
|
||||
p := specs.Platform{
|
||||
p := ocispec.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "arm",
|
||||
Variant: "v8",
|
||||
}
|
||||
|
||||
// Check no variant
|
||||
assert.Assert(t, OnlyPlatformWithFallback(p).Match(specs.Platform{
|
||||
assert.Assert(t, OnlyPlatformWithFallback(p).Match(ocispec.Platform{
|
||||
OS: p.OS,
|
||||
Architecture: p.Architecture,
|
||||
}))
|
||||
// check with variant
|
||||
assert.Assert(t, OnlyPlatformWithFallback(p).Match(specs.Platform{
|
||||
assert.Assert(t, OnlyPlatformWithFallback(p).Match(ocispec.Platform{
|
||||
OS: p.OS,
|
||||
Architecture: p.Architecture,
|
||||
Variant: p.Variant,
|
||||
}))
|
||||
// Make sure non-matches are false.
|
||||
assert.Assert(t, !OnlyPlatformWithFallback(p).Match(specs.Platform{
|
||||
assert.Assert(t, !OnlyPlatformWithFallback(p).Match(ocispec.Platform{
|
||||
OS: p.OS,
|
||||
Architecture: "amd64",
|
||||
}))
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"go.etcd.io/bbolt"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -96,7 +96,7 @@ func TestContentStoreForPull(t *testing.T) {
|
||||
}
|
||||
|
||||
data := []byte(`{}`)
|
||||
desc := v1.Descriptor{
|
||||
desc := ocispec.Descriptor{
|
||||
Digest: digest.Canonical.FromBytes(data),
|
||||
Size: int64(len(data)),
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontaine
|
||||
es, err := tsk.Delete(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("container", c.ID).Warnf("failed to delete container from containerd")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"container": c.ID,
|
||||
}).Warn("failed to delete container from containerd")
|
||||
} else {
|
||||
exitStatus = container.ExitStatus{
|
||||
ExitCode: int(es.ExitCode()),
|
||||
@@ -66,29 +69,32 @@ func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontaine
|
||||
execDuration := time.Since(c.StartedAt)
|
||||
restart, wait, err := c.RestartManager().ShouldRestart(uint32(exitStatus.ExitCode), daemonShutdown || c.HasBeenManuallyStopped, execDuration)
|
||||
if err != nil {
|
||||
logrus.WithError(err).
|
||||
WithField("container", c.ID).
|
||||
WithField("restartCount", c.RestartCount).
|
||||
WithField("exitStatus", exitStatus).
|
||||
WithField("daemonShuttingDown", daemonShutdown).
|
||||
WithField("hasBeenManuallyStopped", c.HasBeenManuallyStopped).
|
||||
WithField("execDuration", execDuration).
|
||||
Warn("ShouldRestart failed, container will not be restarted")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"container": c.ID,
|
||||
"restartCount": c.RestartCount,
|
||||
"exitStatus": exitStatus,
|
||||
"daemonShuttingDown": daemonShutdown,
|
||||
"hasBeenManuallyStopped": c.HasBeenManuallyStopped,
|
||||
"execDuration": execDuration,
|
||||
}).Warn("ShouldRestart failed, container will not be restarted")
|
||||
restart = false
|
||||
}
|
||||
|
||||
attributes := map[string]string{
|
||||
"exitCode": strconv.Itoa(exitStatus.ExitCode),
|
||||
"exitCode": strconv.Itoa(exitStatus.ExitCode),
|
||||
"execDuration": strconv.Itoa(int(execDuration.Seconds())),
|
||||
}
|
||||
daemon.Cleanup(c)
|
||||
|
||||
if restart {
|
||||
c.RestartCount++
|
||||
logrus.WithField("container", c.ID).
|
||||
WithField("restartCount", c.RestartCount).
|
||||
WithField("exitStatus", exitStatus).
|
||||
WithField("manualRestart", c.HasBeenManuallyRestarted).
|
||||
Debug("Restarting container")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"container": c.ID,
|
||||
"restartCount": c.RestartCount,
|
||||
"exitStatus": exitStatus,
|
||||
"manualRestart": c.HasBeenManuallyRestarted,
|
||||
}).Debug("Restarting container")
|
||||
c.SetRestarting(&exitStatus)
|
||||
} else {
|
||||
c.SetStopped(&exitStatus)
|
||||
@@ -169,7 +175,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei
|
||||
// Remove the exec command from the container's store only and not the
|
||||
// daemon's store so that the exec command can be inspected. Remove it
|
||||
// before mutating execConfig to maintain the invariant that
|
||||
// c.ExecCommands only contain execs in the Running state.
|
||||
// c.ExecCommands only contains execs that have not exited.
|
||||
c.ExecCommands.Delete(execConfig.ID)
|
||||
|
||||
execConfig.ExitCode = &ec
|
||||
@@ -185,14 +191,25 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei
|
||||
|
||||
exitCode = ec
|
||||
|
||||
go func() {
|
||||
if _, err := execConfig.Process.Delete(context.Background()); err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"container": ei.ContainerID,
|
||||
"process": ei.ProcessID,
|
||||
}).Warn("failed to delete process")
|
||||
}
|
||||
}()
|
||||
// If the exec failed at start in such a way that containerd
|
||||
// publishes an exit event for it, we will race processing the event
|
||||
// with daemon.ContainerExecStart() removing the exec from
|
||||
// c.ExecCommands. If we win the race, we will find that there is no
|
||||
// process to clean up. (And ContainerExecStart will clobber the
|
||||
// exit code we set.) Prevent a nil-dereferenc panic in that
|
||||
// situation to restore the status quo where this is merely a
|
||||
// logical race condition.
|
||||
if execConfig.Process != nil {
|
||||
go func() {
|
||||
if _, err := execConfig.Process.Delete(context.Background()); err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"container": ei.ContainerID,
|
||||
"process": ei.ProcessID,
|
||||
}).Warn("failed to delete process")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
attributes := map[string]string{
|
||||
"execID": ei.ProcessID,
|
||||
@@ -210,8 +227,10 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei
|
||||
if errdefs.IsNotFound(err) {
|
||||
// The container was started by not-docker and so could have been deleted by
|
||||
// not-docker before we got around to loading it from containerd.
|
||||
logrus.WithField("container", c.ID).WithError(err).
|
||||
Debug("could not load containerd container for start event")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"container": c.ID,
|
||||
}).Debug("could not load containerd container for start event")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -219,8 +238,10 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei
|
||||
tsk, err := ctr.Task(context.Background())
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
logrus.WithField("container", c.ID).WithError(err).
|
||||
Debug("failed to load task for externally-started container")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err,
|
||||
"container": c.ID,
|
||||
}).Debug("failed to load task for externally-started container")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -285,5 +306,5 @@ func (daemon *Daemon) autoRemove(c *container.Container) {
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithError(err).WithField("container", c.ID).Error("error removing container")
|
||||
logrus.WithFields(logrus.Fields{logrus.ErrorKey: err, "container": c.ID}).Error("error removing container")
|
||||
}
|
||||
|
||||
@@ -5,16 +5,32 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/container"
|
||||
volumesservice "github.com/docker/docker/volume/service"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
|
||||
alive := container.IsRunning()
|
||||
for _, config := range container.MountPoints {
|
||||
if err := daemon.lazyInitializeVolume(container.ID, config); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Volume == nil {
|
||||
// FIXME(thaJeztah): should we check for config.Type here as well? (i.e., skip bind-mounts etc)
|
||||
continue
|
||||
}
|
||||
if alive {
|
||||
log.G(context.TODO()).WithFields(logrus.Fields{
|
||||
"container": container.ID,
|
||||
"volume": config.Volume.Name(),
|
||||
}).Debug("Live-restoring volume for alive container")
|
||||
if err := config.LiveRestore(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,11 +26,12 @@ func (daemon *Daemon) registerName(container *container.Container) error {
|
||||
return err
|
||||
}
|
||||
if container.Name == "" {
|
||||
name, err := daemon.generateNewName(container.ID)
|
||||
name, err := daemon.generateAndReserveName(container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Name = name
|
||||
return nil
|
||||
}
|
||||
return daemon.containersReplica.ReserveName(container.Name, container.ID)
|
||||
}
|
||||
@@ -42,7 +43,7 @@ func (daemon *Daemon) generateIDAndName(name string) (string, string, error) {
|
||||
)
|
||||
|
||||
if name == "" {
|
||||
if name, err = daemon.generateNewName(id); err != nil {
|
||||
if name, err = daemon.generateAndReserveName(id); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return id, name, nil
|
||||
@@ -81,7 +82,7 @@ func (daemon *Daemon) releaseName(name string) {
|
||||
daemon.containersReplica.ReleaseName(name)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) generateNewName(id string) (string, error) {
|
||||
func (daemon *Daemon) generateAndReserveName(id string) (string, error) {
|
||||
var name string
|
||||
for i := 0; i < 6; i++ {
|
||||
name = namesgenerator.GetRandomName(i)
|
||||
|
||||
@@ -53,6 +53,9 @@ func WithRlimits(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
})
|
||||
}
|
||||
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
s.Process.Rlimits = rlimits
|
||||
return nil
|
||||
}
|
||||
@@ -113,6 +116,9 @@ func WithRootless(daemon *Daemon) coci.SpecOpts {
|
||||
// WithOOMScore sets the oom score
|
||||
func WithOOMScore(score *int) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
s.Process.OOMScoreAdj = score
|
||||
return nil
|
||||
}
|
||||
@@ -121,6 +127,12 @@ func WithOOMScore(score *int) coci.SpecOpts {
|
||||
// WithSelinux sets the selinux labels
|
||||
func WithSelinux(c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
s.Process.SelinuxLabel = c.GetProcessLabel()
|
||||
s.Linux.MountLabel = c.MountLabel
|
||||
return nil
|
||||
@@ -151,6 +163,9 @@ func WithApparmor(c *container.Container) coci.SpecOpts {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
s.Process.ApparmorProfile = appArmorProfile
|
||||
}
|
||||
return nil
|
||||
@@ -213,6 +228,10 @@ func getUser(c *container.Container, username string) (specs.User, error) {
|
||||
}
|
||||
|
||||
func setNamespace(s *specs.Spec, ns specs.LinuxNamespace) {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
|
||||
for i, n := range s.Linux.Namespaces {
|
||||
if n.Type == ns.Type {
|
||||
s.Linux.Namespaces[i] = ns
|
||||
@@ -606,6 +625,9 @@ func WithMounts(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
}
|
||||
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.SHARED]
|
||||
}
|
||||
case mount.SLAVE, mount.RSLAVE:
|
||||
@@ -634,6 +656,9 @@ func WithMounts(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
if !fallback {
|
||||
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
|
||||
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE]
|
||||
}
|
||||
}
|
||||
@@ -689,8 +714,10 @@ func WithMounts(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
clearReadOnly(&s.Mounts[i])
|
||||
}
|
||||
}
|
||||
s.Linux.ReadonlyPaths = nil
|
||||
s.Linux.MaskedPaths = nil
|
||||
if s.Linux != nil {
|
||||
s.Linux.ReadonlyPaths = nil
|
||||
s.Linux.MaskedPaths = nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
|
||||
@@ -718,26 +745,27 @@ func sysctlExists(s string) bool {
|
||||
// WithCommonOptions sets common docker options
|
||||
func WithCommonOptions(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if c.BaseFS == "" && !daemon.UsesSnapshotter() {
|
||||
if c.BaseFS == "" {
|
||||
return errors.New("populateCommonSpec: BaseFS of container " + c.ID + " is unexpectedly empty")
|
||||
}
|
||||
linkedEnv, err := daemon.setupLinkedContainers(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !daemon.UsesSnapshotter() {
|
||||
s.Root = &specs.Root{
|
||||
Path: c.BaseFS,
|
||||
Readonly: c.HostConfig.ReadonlyRootfs,
|
||||
}
|
||||
if err := c.SetupWorkingDirectory(daemon.idMapping.RootPair()); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Root = &specs.Root{
|
||||
Path: c.BaseFS,
|
||||
Readonly: c.HostConfig.ReadonlyRootfs,
|
||||
}
|
||||
if err := c.SetupWorkingDirectory(daemon.idMapping.RootPair()); err != nil {
|
||||
return err
|
||||
}
|
||||
cwd := c.Config.WorkingDir
|
||||
if len(cwd) == 0 {
|
||||
cwd = "/"
|
||||
}
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
s.Process.Args = append([]string{c.Path}, c.Args...)
|
||||
|
||||
// only add the custom init if it is specified and the container is running in its
|
||||
@@ -814,6 +842,9 @@ func WithCgroups(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
} else {
|
||||
cgroupsPath = filepath.Join(parent, c.ID)
|
||||
}
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
s.Linux.CgroupsPath = cgroupsPath
|
||||
|
||||
// the rest is only needed for CPU RT controller
|
||||
@@ -914,8 +945,14 @@ func WithDevices(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
}
|
||||
}
|
||||
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
s.Linux.Devices = append(s.Linux.Devices, devs...)
|
||||
s.Linux.Resources.Devices = devPermissions
|
||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, devPermissions...)
|
||||
|
||||
for _, req := range c.HostConfig.DeviceRequests {
|
||||
if err := daemon.handleDevice(req, s); err != nil {
|
||||
@@ -956,27 +993,28 @@ func WithResources(c *container.Container) coci.SpecOpts {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blkioWeight := r.BlkioWeight
|
||||
|
||||
specResources := &specs.LinuxResources{
|
||||
Memory: memoryRes,
|
||||
CPU: cpuRes,
|
||||
BlockIO: &specs.LinuxBlockIO{
|
||||
Weight: &blkioWeight,
|
||||
WeightDevice: weightDevices,
|
||||
ThrottleReadBpsDevice: readBpsDevice,
|
||||
ThrottleWriteBpsDevice: writeBpsDevice,
|
||||
ThrottleReadIOPSDevice: readIOpsDevice,
|
||||
ThrottleWriteIOPSDevice: writeIOpsDevice,
|
||||
},
|
||||
Pids: getPidsLimit(r),
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
|
||||
if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 {
|
||||
specResources.Devices = s.Linux.Resources.Devices
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
s.Linux.Resources.Memory = memoryRes
|
||||
s.Linux.Resources.CPU = cpuRes
|
||||
s.Linux.Resources.BlockIO = &specs.LinuxBlockIO{
|
||||
WeightDevice: weightDevices,
|
||||
ThrottleReadBpsDevice: readBpsDevice,
|
||||
ThrottleWriteBpsDevice: writeBpsDevice,
|
||||
ThrottleReadIOPSDevice: readIOpsDevice,
|
||||
ThrottleWriteIOPSDevice: writeIOpsDevice,
|
||||
}
|
||||
if r.BlkioWeight != 0 {
|
||||
w := r.BlkioWeight
|
||||
s.Linux.Resources.BlockIO.Weight = &w
|
||||
}
|
||||
s.Linux.Resources.Pids = getPidsLimit(r)
|
||||
|
||||
s.Linux.Resources = specResources
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -984,6 +1022,15 @@ func WithResources(c *container.Container) coci.SpecOpts {
|
||||
// WithSysctls sets the container's sysctls
|
||||
func WithSysctls(c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if len(c.HostConfig.Sysctls) == 0 {
|
||||
return nil
|
||||
}
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
if s.Linux.Sysctl == nil {
|
||||
s.Linux.Sysctl = make(map[string]string)
|
||||
}
|
||||
// We merge the sysctls injected above with the HostConfig (latter takes
|
||||
// precedence for backwards-compatibility reasons).
|
||||
for k, v := range c.HostConfig.Sysctls {
|
||||
@@ -996,6 +1043,9 @@ func WithSysctls(c *container.Container) coci.SpecOpts {
|
||||
// WithUser sets the container's user
|
||||
func WithUser(c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
var err error
|
||||
s.Process.User, err = getUser(c, c.Config.User)
|
||||
return err
|
||||
@@ -1023,20 +1073,8 @@ func (daemon *Daemon) createSpec(ctx context.Context, c *container.Container) (r
|
||||
WithSelinux(c),
|
||||
WithOOMScore(&c.HostConfig.OomScoreAdj),
|
||||
coci.WithAnnotations(c.HostConfig.Annotations),
|
||||
WithUser(c),
|
||||
)
|
||||
if daemon.UsesSnapshotter() {
|
||||
s.Root = &specs.Root{
|
||||
Path: "rootfs",
|
||||
}
|
||||
if c.Config.User != "" {
|
||||
opts = append(opts, coci.WithUser(c.Config.User))
|
||||
}
|
||||
if c.Config.WorkingDir != "" {
|
||||
opts = append(opts, coci.WithProcessCwd(c.Config.WorkingDir))
|
||||
}
|
||||
} else {
|
||||
opts = append(opts, WithUser(c))
|
||||
}
|
||||
|
||||
if c.NoNewPrivileges {
|
||||
opts = append(opts, coci.WithNoNewPrivileges)
|
||||
|
||||
@@ -11,17 +11,20 @@ import (
|
||||
"github.com/docker/docker/daemon/config"
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/libnetwork"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func setupFakeDaemon(t *testing.T, c *container.Container) *Daemon {
|
||||
root, err := os.MkdirTemp("", "oci_linux_test-root")
|
||||
assert.NilError(t, err)
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
|
||||
rootfs := filepath.Join(root, "rootfs")
|
||||
err = os.MkdirAll(rootfs, 0755)
|
||||
err := os.MkdirAll(rootfs, 0755)
|
||||
assert.NilError(t, err)
|
||||
|
||||
netController, err := libnetwork.New()
|
||||
@@ -49,6 +52,18 @@ func setupFakeDaemon(t *testing.T, c *container.Container) *Daemon {
|
||||
c.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)}
|
||||
}
|
||||
|
||||
// HORRIBLE HACK: clean up shm mounts leaked by some tests. Otherwise the
|
||||
// offending tests would fail due to the mounts blocking the temporary
|
||||
// directory from being cleaned up.
|
||||
t.Cleanup(func() {
|
||||
if c.ShmPath != "" {
|
||||
var err error
|
||||
for err == nil { // Some tests over-mount over the same path multiple times.
|
||||
err = unix.Unmount(c.ShmPath, unix.MNT_DETACH)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -60,10 +75,6 @@ func (i *fakeImageService) StorageDriver() string {
|
||||
return "overlay"
|
||||
}
|
||||
|
||||
func cleanupFakeContainer(c *container.Container) {
|
||||
_ = os.RemoveAll(c.Root)
|
||||
}
|
||||
|
||||
// TestTmpfsDevShmNoDupMount checks that a user-specified /dev/shm tmpfs
|
||||
// mount (as in "docker run --tmpfs /dev/shm:rw,size=NNN") does not result
|
||||
// in "Duplicate mount point" error from the engine.
|
||||
@@ -81,7 +92,6 @@ func TestTmpfsDevShmNoDupMount(t *testing.T) {
|
||||
},
|
||||
}
|
||||
d := setupFakeDaemon(t, c)
|
||||
defer cleanupFakeContainer(c)
|
||||
|
||||
_, err := d.createSpec(context.TODO(), c)
|
||||
assert.Check(t, err)
|
||||
@@ -100,7 +110,6 @@ func TestIpcPrivateVsReadonly(t *testing.T) {
|
||||
},
|
||||
}
|
||||
d := setupFakeDaemon(t, c)
|
||||
defer cleanupFakeContainer(c)
|
||||
|
||||
s, err := d.createSpec(context.TODO(), c)
|
||||
assert.Check(t, err)
|
||||
@@ -129,7 +138,6 @@ func TestSysctlOverride(t *testing.T) {
|
||||
},
|
||||
}
|
||||
d := setupFakeDaemon(t, c)
|
||||
defer cleanupFakeContainer(c)
|
||||
|
||||
// Ensure that the implicit sysctl is set correctly.
|
||||
s, err := d.createSpec(context.TODO(), c)
|
||||
@@ -181,7 +189,6 @@ func TestSysctlOverrideHost(t *testing.T) {
|
||||
},
|
||||
}
|
||||
d := setupFakeDaemon(t, c)
|
||||
defer cleanupFakeContainer(c)
|
||||
|
||||
// Ensure that the implicit sysctl is not set
|
||||
s, err := d.createSpec(context.TODO(), c)
|
||||
@@ -209,3 +216,38 @@ func TestGetSourceMount(t *testing.T) {
|
||||
_, _, err = getSourceMount(cwd)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestDefaultResources(t *testing.T) {
|
||||
skip.If(t, os.Getuid() != 0, "skipping test that requires root") // TODO: is this actually true? I'm guilty of following the cargo cult here.
|
||||
|
||||
c := &container.Container{
|
||||
HostConfig: &containertypes.HostConfig{
|
||||
IpcMode: containertypes.IPCModeNone,
|
||||
},
|
||||
}
|
||||
d := setupFakeDaemon(t, c)
|
||||
|
||||
s, err := d.createSpec(context.Background(), c)
|
||||
assert.NilError(t, err)
|
||||
checkResourcesAreUnset(t, s.Linux.Resources)
|
||||
}
|
||||
|
||||
func checkResourcesAreUnset(t *testing.T, r *specs.LinuxResources) {
|
||||
t.Helper()
|
||||
|
||||
if r != nil {
|
||||
if r.Memory != nil {
|
||||
assert.Check(t, is.DeepEqual(r.Memory, &specs.LinuxMemory{}))
|
||||
}
|
||||
if r.CPU != nil {
|
||||
assert.Check(t, is.DeepEqual(r.CPU, &specs.LinuxCPU{}))
|
||||
}
|
||||
assert.Check(t, is.Nil(r.Pids))
|
||||
if r.BlockIO != nil {
|
||||
assert.Check(t, is.DeepEqual(r.BlockIO, &specs.LinuxBlockIO{}, cmpopts.EquateEmpty()))
|
||||
}
|
||||
if r.Network != nil {
|
||||
assert.Check(t, is.DeepEqual(r.Network, &specs.LinuxNetwork{}, cmpopts.EquateEmpty()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
func WithConsoleSize(c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if c.HostConfig.ConsoleSize[0] > 0 || c.HostConfig.ConsoleSize[1] > 0 {
|
||||
if s.Process == nil {
|
||||
s.Process = &specs.Process{}
|
||||
}
|
||||
s.Process.ConsoleSize = &specs.Box{
|
||||
Height: c.HostConfig.ConsoleSize[0],
|
||||
Width: c.HostConfig.ConsoleSize[1],
|
||||
|
||||
@@ -9,7 +9,12 @@ func setLinuxDomainname(c *container.Container, s *specs.Spec) {
|
||||
// There isn't a field in the OCI for the NIS domainname, but luckily there
|
||||
// is a sysctl which has an identical effect to setdomainname(2) so there's
|
||||
// no explicit need for runtime support.
|
||||
s.Linux.Sysctl = make(map[string]string)
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
if s.Linux.Sysctl == nil {
|
||||
s.Linux.Sysctl = make(map[string]string)
|
||||
}
|
||||
if c.Config.Domainname != "" {
|
||||
s.Linux.Sysctl["kernel.domainname"] = c.Config.Domainname
|
||||
}
|
||||
|
||||
@@ -63,10 +63,10 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
|
||||
if err := daemon.reloadAllowNondistributableArtifacts(conf, attributes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
|
||||
if err := daemon.reloadRegistryMirrors(conf, attributes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.reloadRegistryMirrors(conf, attributes); err != nil {
|
||||
if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
|
||||
|
||||
@@ -238,13 +238,19 @@ func TestDaemonReloadInsecureRegistries(t *testing.T) {
|
||||
"docker3.example.com", // this will be newly added
|
||||
}
|
||||
|
||||
mirrors := []string{
|
||||
"https://mirror.test.example.com",
|
||||
}
|
||||
|
||||
valuesSets := make(map[string]interface{})
|
||||
valuesSets["insecure-registries"] = insecureRegistries
|
||||
valuesSets["registry-mirrors"] = mirrors
|
||||
|
||||
newConfig := &config.Config{
|
||||
CommonConfig: config.CommonConfig{
|
||||
ServiceOptions: registry.ServiceOptions{
|
||||
InsecureRegistries: insecureRegistries,
|
||||
Mirrors: mirrors,
|
||||
},
|
||||
ValuesSet: valuesSets,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
)
|
||||
|
||||
// ContainerResize changes the size of the TTY of the process running
|
||||
@@ -48,6 +50,10 @@ func (daemon *Daemon) ContainerExecResize(name string, height, width int) error
|
||||
|
||||
select {
|
||||
case <-ec.Started:
|
||||
// An error may have occurred, so ec.Process may be nil.
|
||||
if ec.Process == nil {
|
||||
return errdefs.InvalidParameter(errors.New("exec process is not started"))
|
||||
}
|
||||
return ec.Process.Resize(context.Background(), uint32(width), uint32(height))
|
||||
case <-timeout.C:
|
||||
return errors.New("timeout waiting for exec session ready")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/docker/docker/container"
|
||||
dconfig "github.com/docker/docker/daemon/config"
|
||||
"github.com/docker/docker/profiles/seccomp"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -31,6 +32,9 @@ func WithSeccomp(daemon *Daemon, c *container.Container) coci.SpecOpts {
|
||||
c.SeccompProfile = dconfig.SeccompProfileUnconfined
|
||||
return nil
|
||||
}
|
||||
if s.Linux == nil {
|
||||
s.Linux = &specs.Linux{}
|
||||
}
|
||||
var err error
|
||||
switch {
|
||||
case c.SeccompProfile == dconfig.SeccompProfileDefault:
|
||||
|
||||
141
daemon/snapshotter/mount.go
Normal file
141
daemon/snapshotter/mount.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package snapshotter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/moby/locker"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const mountsDir = "rootfs"
|
||||
|
||||
// List of known filesystems that can't be re-mounted or have shared layers
|
||||
var refCountedFileSystems = []string{"fuse-overlayfs", "overlayfs", "stargz", "zfs"}
|
||||
|
||||
// Mounter handles mounting/unmounting things coming in from a snapshotter
|
||||
// with optional reference counting if needed by the filesystem
|
||||
type Mounter interface {
|
||||
// Mount mounts the rootfs for a container and returns the mount point
|
||||
Mount(mounts []mount.Mount, containerID string) (string, error)
|
||||
// Unmount unmounts the container rootfs
|
||||
Unmount(target string) error
|
||||
}
|
||||
|
||||
// inSlice tests whether a string is contained in a slice of strings or not.
|
||||
// Comparison is case sensitive
|
||||
func inSlice(slice []string, s string) bool {
|
||||
for _, ss := range slice {
|
||||
if s == ss {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewMounter creates a new mounter for the provided snapshotter
|
||||
func NewMounter(home string, snapshotter string, idMap idtools.IdentityMapping) Mounter {
|
||||
if inSlice(refCountedFileSystems, snapshotter) {
|
||||
return &refCountMounter{
|
||||
home: home,
|
||||
snapshotter: snapshotter,
|
||||
rc: graphdriver.NewRefCounter(checker()),
|
||||
locker: locker.New(),
|
||||
idMap: idMap,
|
||||
}
|
||||
}
|
||||
|
||||
return mounter{
|
||||
home: home,
|
||||
snapshotter: snapshotter,
|
||||
idMap: idMap,
|
||||
}
|
||||
}
|
||||
|
||||
type refCountMounter struct {
|
||||
home string
|
||||
snapshotter string
|
||||
rc *graphdriver.RefCounter
|
||||
locker *locker.Locker
|
||||
idMap idtools.IdentityMapping
|
||||
}
|
||||
|
||||
func (m *refCountMounter) Mount(mounts []mount.Mount, containerID string) (target string, retErr error) {
|
||||
target = filepath.Join(m.home, mountsDir, m.snapshotter, containerID)
|
||||
|
||||
_, err := os.Stat(target)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if count := m.rc.Increment(target); count > 1 {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
m.locker.Lock(target)
|
||||
defer m.locker.Unlock(target)
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if c := m.rc.Decrement(target); c <= 0 {
|
||||
if mntErr := unmount(target); mntErr != nil {
|
||||
logrus.Errorf("error unmounting %s: %v", target, mntErr)
|
||||
}
|
||||
if rmErr := os.Remove(target); rmErr != nil && !os.IsNotExist(rmErr) {
|
||||
logrus.Debugf("Failed to remove %s: %v: %v", target, rmErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
root := m.idMap.RootPair()
|
||||
if err := idtools.MkdirAllAndChown(target, 0700, root); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return target, mount.All(mounts, target)
|
||||
}
|
||||
|
||||
func (m *refCountMounter) Unmount(target string) error {
|
||||
if count := m.rc.Decrement(target); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.locker.Lock(target)
|
||||
defer m.locker.Unlock(target)
|
||||
|
||||
if err := unmount(target); err != nil {
|
||||
logrus.Debugf("Failed to unmount %s: %v", target, err)
|
||||
}
|
||||
|
||||
if err := os.Remove(target); err != nil {
|
||||
logrus.WithError(err).WithField("dir", target).Error("failed to remove mount temp dir")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type mounter struct {
|
||||
home string
|
||||
snapshotter string
|
||||
idMap idtools.IdentityMapping
|
||||
}
|
||||
|
||||
func (m mounter) Mount(mounts []mount.Mount, containerID string) (string, error) {
|
||||
target := filepath.Join(m.home, mountsDir, m.snapshotter, containerID)
|
||||
|
||||
root := m.idMap.RootPair()
|
||||
if err := idtools.MkdirAndChown(target, 0700, root); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return target, mount.All(mounts, target)
|
||||
}
|
||||
|
||||
func (m mounter) Unmount(target string) error {
|
||||
return unmount(target)
|
||||
|
||||
}
|
||||
17
daemon/snapshotter/mount_default.go
Normal file
17
daemon/snapshotter/mount_default.go
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build !windows
|
||||
|
||||
package snapshotter
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checker() graphdriver.Checker {
|
||||
return graphdriver.NewDefaultChecker()
|
||||
}
|
||||
|
||||
func unmount(target string) error {
|
||||
return mount.Unmount(target, unix.MNT_DETACH)
|
||||
}
|
||||
18
daemon/snapshotter/mount_windows.go
Normal file
18
daemon/snapshotter/mount_windows.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package snapshotter
|
||||
|
||||
import "github.com/containerd/containerd/mount"
|
||||
|
||||
type winChecker struct {
|
||||
}
|
||||
|
||||
func (c *winChecker) IsMounted(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func checker() *winChecker {
|
||||
return &winChecker{}
|
||||
}
|
||||
|
||||
func unmount(target string) error {
|
||||
return mount.Unmount(target, 0)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/docker/docker/api/types"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/container"
|
||||
@@ -178,13 +177,7 @@ func (daemon *Daemon) containerStart(ctx context.Context, container *container.C
|
||||
return err
|
||||
}
|
||||
|
||||
newContainerOpts := []containerd.NewContainerOpts{}
|
||||
if daemon.UsesSnapshotter() {
|
||||
newContainerOpts = append(newContainerOpts, containerd.WithSnapshotter(container.Driver))
|
||||
newContainerOpts = append(newContainerOpts, containerd.WithSnapshot(container.ID))
|
||||
}
|
||||
|
||||
ctr, err := libcontainerd.ReplaceContainer(ctx, daemon.containerd, container.ID, spec, shim, createOptions, newContainerOpts...)
|
||||
ctr, err := libcontainerd.ReplaceContainer(ctx, daemon.containerd, container.ID, spec, shim, createOptions)
|
||||
if err != nil {
|
||||
return setExitCodeFromError(container.SetExitCode, err)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,13 @@ func (daemon *Daemon) ContainerStop(ctx context.Context, name string, options co
|
||||
}
|
||||
|
||||
// containerStop sends a stop signal, waits, sends a kill signal.
|
||||
func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) {
|
||||
func (daemon *Daemon) containerStop(_ context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) {
|
||||
// Deliberately using a local context here, because cancelling the
|
||||
// request should not cancel the stop.
|
||||
//
|
||||
// TODO(thaJeztah): pass context, and use context.WithoutCancel() once available: https://github.com/golang/go/issues/40221
|
||||
ctx := context.Background()
|
||||
|
||||
if !ctr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,15 +11,19 @@ import (
|
||||
func toContainerdResources(resources container.Resources) *libcontainerdtypes.Resources {
|
||||
var r libcontainerdtypes.Resources
|
||||
|
||||
r.BlockIO = &specs.LinuxBlockIO{
|
||||
Weight: &resources.BlkioWeight,
|
||||
if resources.BlkioWeight != 0 {
|
||||
r.BlockIO = &specs.LinuxBlockIO{
|
||||
Weight: &resources.BlkioWeight,
|
||||
}
|
||||
}
|
||||
|
||||
shares := uint64(resources.CPUShares)
|
||||
r.CPU = &specs.LinuxCPU{
|
||||
Shares: &shares,
|
||||
Cpus: resources.CpusetCpus,
|
||||
Mems: resources.CpusetMems,
|
||||
cpu := specs.LinuxCPU{
|
||||
Cpus: resources.CpusetCpus,
|
||||
Mems: resources.CpusetMems,
|
||||
}
|
||||
if resources.CPUShares != 0 {
|
||||
shares := uint64(resources.CPUShares)
|
||||
cpu.Shares = &shares
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -37,17 +41,33 @@ func toContainerdResources(resources container.Resources) *libcontainerdtypes.Re
|
||||
period = uint64(resources.CPUPeriod)
|
||||
}
|
||||
|
||||
r.CPU.Period = &period
|
||||
r.CPU.Quota = "a
|
||||
|
||||
r.Memory = &specs.LinuxMemory{
|
||||
Limit: &resources.Memory,
|
||||
Reservation: &resources.MemoryReservation,
|
||||
Kernel: &resources.KernelMemory,
|
||||
if period != 0 {
|
||||
cpu.Period = &period
|
||||
}
|
||||
if quota != 0 {
|
||||
cpu.Quota = "a
|
||||
}
|
||||
|
||||
if cpu != (specs.LinuxCPU{}) {
|
||||
r.CPU = &cpu
|
||||
}
|
||||
|
||||
var memory specs.LinuxMemory
|
||||
if resources.Memory != 0 {
|
||||
memory.Limit = &resources.Memory
|
||||
}
|
||||
if resources.MemoryReservation != 0 {
|
||||
memory.Reservation = &resources.MemoryReservation
|
||||
}
|
||||
if resources.KernelMemory != 0 {
|
||||
memory.Kernel = &resources.KernelMemory
|
||||
}
|
||||
if resources.MemorySwap > 0 {
|
||||
r.Memory.Swap = &resources.MemorySwap
|
||||
memory.Swap = &resources.MemorySwap
|
||||
}
|
||||
|
||||
if memory != (specs.LinuxMemory{}) {
|
||||
r.Memory = &memory
|
||||
}
|
||||
|
||||
r.Pids = getPidsLimit(resources)
|
||||
|
||||
11
daemon/update_linux_test.go
Normal file
11
daemon/update_linux_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package daemon // import "github.com/docker/docker/daemon"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
func TestToContainerdResources_Defaults(t *testing.T) {
|
||||
checkResourcesAreUnset(t, toContainerdResources(container.Resources{}))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user