Compare commits

..

No commits in common. "main" and "v2.1.1" have entirely different histories.
main ... v2.1.1

269 changed files with 2717 additions and 8551 deletions

View File

@ -38,7 +38,7 @@ jobs:
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
@ -61,12 +61,10 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
secrets: |
github_token=${{ secrets.GITHUB_TOKEN }}

View File

@ -70,8 +70,6 @@ jobs:
local goarm="${3:-}"
local result
GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" go build ./examples/...
github::timer::begin
GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" make binaries \
@ -99,12 +97,3 @@ jobs:
build linux s390x
[ ! "$failure" ] || exit 1
- if: ${{ env.GO_VERSION != '' }}
name: "Run: make binaries with custom BUILDTAGS"
run: |
set -eux
# no_ipfs: make sure it does not incur any IPFS-related dependency
go mod vendor
rm -rf vendor/github.com/ipfs vendor/github.com/multiformats
BUILDTAGS=no_ipfs make binaries

View File

@ -39,8 +39,6 @@ jobs:
uses: crazy-max/ghaction-github-runtime@3cb05d89e1f492524af3d41a1c98c83bc3025124 # v3.1.0
- name: "Run: build dependencies for the integration test environment image"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Cache is sharded per-architecture
arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }}
@ -51,7 +49,6 @@ jobs:
args=(--build-arg CONTAINERD_VERSION=${{ inputs.containerd-version }})
fi
docker buildx build \
--secret id=github_token,env=GITHUB_TOKEN \
--cache-to type=gha,compression=zstd,mode=max,scope=test-integration-dependencies-"$arch" \
--cache-from type=gha,scope=test-integration-dependencies-"$arch" \
--target build-dependencies "${args[@]}" .

View File

@ -81,15 +81,11 @@ jobs:
docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7
- if: ${{ inputs.canary }}
name: "Init (canary): prepare updated test image"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
. ./hack/build-integration-canary.sh
canary::build::integration
- if: ${{ ! inputs.canary }}
name: "Init: prepare test image"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
buildargs=()
# If the runner is old, use old ubuntu inside the container as well
@ -108,7 +104,6 @@ jobs:
arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }}
docker buildx create --name with-gha --use
docker buildx build \
--secret id=github_token,env=GITHUB_TOKEN \
--output=type=docker \
--cache-from type=gha,scope=test-integration-dependencies-"$arch" \
-t "$target" --target "$target" \

View File

@ -31,7 +31,7 @@ jobs:
fetch-depth: 1
- name: "Init: lima"
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
uses: lima-vm/lima-actions/setup@be564a1408f84557d067b099a475652288074b2e # v1.0.0
id: lima-actions-setup
- name: "Init: Cache"
@ -79,8 +79,6 @@ jobs:
uses: crazy-max/ghaction-github-runtime@3cb05d89e1f492524af3d41a1c98c83bc3025124 # v3.1.0
- name: "Init: prepare integration tests"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eux
@ -90,7 +88,6 @@ jobs:
[ "$TARGET" = "rootless" ] && TARGET=test-integration-rootless || TARGET=test-integration
docker buildx create --name with-gha --use
docker buildx build \
--secret id=github_token,env=GITHUB_TOKEN \
--output=type=docker \
--cache-from type=gha,scope=test-integration-dependencies-amd64 \
-t test-integration --target "${TARGET}" \

View File

@ -33,8 +33,6 @@ jobs:
go-version: "1.24"
check-latest: true
- name: "Compile binaries"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: make artifacts
- name: "SHA256SUMS"
run: |
@ -56,7 +54,7 @@ jobs:
Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE])
EOF
- name: "Generate artifact attestation"
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
with:
subject-path: _output/*

View File

@ -102,7 +102,7 @@ jobs:
canary: true
with:
timeout: 60
timeout: 45
runner: ${{ matrix.runner }}
target: ${{ matrix.target }}
binary: ${{ matrix.binary && matrix.binary || 'nerdctl' }}
@ -141,9 +141,9 @@ jobs:
go-version: 1.24
windows-cni-version: v0.3.1
docker-version: 5:28.0.4-1~ubuntu.24.04~noble
containerd-version: 2.1.3
containerd-version: 2.1.0
# Note: these as for amd64
containerd-sha: 436cc160c33b37ec25b89fb5c72fc879ab2b3416df5d7af240c3e9c2f4065d3c
containerd-sha: 0e5359e957b66b679be807563a543c7416e305e3aafcf56bad90ef87a917014d
containerd-service-sha: 1941362cbaa89dd591b99c32b050d82c583d3cd2e5fa63085d7017457ec5fca8
linux-cni-version: v1.7.1
linux-cni-sha: 1a28a0506bfe5bcdc981caf1a49eeab7e72da8321f1119b7be85f22621013098

View File

@ -58,20 +58,16 @@ jobs:
brew install yamllint shellcheck
fi
echo "::endgroup::"
- if: ${{ env.GO_VERSION != '' && matrix.goos == '' }}
- if: ${{ env.GO_VERSION != '' && env.RUNNER_OS == 'Linux' && matrix.goos == '' }}
name: "lint"
env:
NO_COLOR: true
run: |
if [ "$RUNNER_OS" == Linux ]; then
echo "::group:: lint"
cd mod/tigron
export LINT_COMMIT_RANGE="$(jq -r '.after + "..HEAD"' ${GITHUB_EVENT_PATH})"
make lint
echo "::endgroup::"
else
echo "Lint is disabled on $RUNNER_OS"
fi
echo "::group:: lint"
cd mod/tigron
export LINT_COMMIT_RANGE="$(jq -r '.after + "..HEAD"' ${GITHUB_EVENT_PATH})"
make lint
echo "::endgroup::"
- if: ${{ env.GO_VERSION != '' }}
name: "test-unit"
run: |

View File

@ -31,7 +31,6 @@ linters:
- revive
# Gocritic
- gocritic
- forbidigo
# 3. We used to use these, but have now removed them
@ -42,15 +41,6 @@ linters:
# - nakedret
settings:
forbidigo:
forbid:
# FIXME: there are still calls to os.WriteFile in tests under `cmd`
- pattern: ^os\.WriteFile.*$
pkg: github.com/containerd/nerdctl/v2/pkg
msg: os.WriteFile is neither atomic nor durable - use nerdctl filesystem.WriteFile instead
- pattern: ^os\.ReadFile.*$
pkg: github.com/containerd/nerdctl/v2/pkg
msg: use filesystem.ReadFile instead of os.ReadFile
staticcheck:
checks:
# Below is the default set
@ -63,6 +53,9 @@ linters:
- "-ST1022"
##### TODO: fix and enable these
# 4 occurrences.
# Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...)) https://staticcheck.dev/docs/checks#QF1012
- "-QF1012"
# 6 occurrences.
# Apply De Morgans law https://staticcheck.dev/docs/checks#QF1001
- "-QF1001"
@ -121,7 +114,7 @@ linters:
arguments: [7]
- name: function-length
# 155 occurrences (at default 0, 75). Really long functions should really be broken up in most cases.
arguments: [0, 500]
arguments: [0, 450]
- name: cyclomatic
# 204 occurrences (at default 10)
arguments: [100]
@ -129,7 +122,7 @@ linters:
# 222 occurrences. Could indicate failure to handle broken conditions.
disabled: true
- name: cognitive-complexity
arguments: [205]
arguments: [197]
# 441 occurrences (at default 7). We should try to lower it (involves significant refactoring).
##### P2: nice to have.

View File

@ -1,22 +0,0 @@
# Building nerdctl
To build nerdctl, use `make`:
```bash
make
sudo make install
```
Alternatively, nerdctl can be also built with `go build ./cmd/nerdctl`.
However, this is not recommended as it does not populate the version string (`nerdctl -v`).
## Customization
To specify build tags, set the `BUILDTAGS` variable as follows:
```bash
BUILDTAGS=no_ipfs make
```
The following build tags are supported:
* `no_ipfs` (since v2.1.3): Disable IPFS

View File

@ -17,28 +17,28 @@
# Basic deps
# @BINARY: the binary checksums are verified via Dockerfile.d/SHA256SUMS.d/<COMPONENT>-<VERSION>
ARG CONTAINERD_VERSION=v2.1.3@c787fb98911740dd3ff2d0e45ce88cdf01410486
ARG CONTAINERD_VERSION=v2.1.0@061792f0ecf3684fb30a3a0eb006799b8c6638a7
ARG RUNC_VERSION=v1.3.0@4ca628d1d4c974f92d24daccb901aa078aad748e
ARG CNI_PLUGINS_VERSION=v1.7.1@BINARY
# Extra deps: Build
ARG BUILDKIT_VERSION=v0.23.2@BINARY
ARG BUILDKIT_VERSION=v0.21.1@BINARY
# Extra deps: Lazy-pulling
ARG STARGZ_SNAPSHOTTER_VERSION=v0.16.3@BINARY
# Extra deps: Encryption
ARG IMGCRYPT_VERSION=v2.0.1@c377ec98ff79ec9205eabf555ebd2ea784738c6c
# Extra deps: Rootless
ARG ROOTLESSKIT_VERSION=v2.3.5@BINARY
ARG SLIRP4NETNS_VERSION=v1.3.3@BINARY
ARG SLIRP4NETNS_VERSION=v1.3.2@BINARY
# Extra deps: bypass4netns
ARG BYPASS4NETNS_VERSION=v0.4.2@aa04bd3dcc48c6dae6d7327ba219bda8fe2a4634
# Extra deps: FUSE-OverlayFS
ARG FUSE_OVERLAYFS_VERSION=v1.15@BINARY
ARG CONTAINERD_FUSE_OVERLAYFS_VERSION=v2.1.6@BINARY
ARG CONTAINERD_FUSE_OVERLAYFS_VERSION=v2.1.5@BINARY
# Extra deps: Init
ARG TINI_VERSION=v0.19.0@BINARY
# Extra deps: Debug
ARG BUILDG_VERSION=v0.5.3@BINARY
ARG BUILDG_VERSION=v0.5.2@BINARY
# Extra deps: gomodjail
ARG GOMODJAIL_VERSION=v0.1.2@0a86b34442a491fa8f5e4565e9c846fce310239c
@ -47,22 +47,19 @@ ARG GOMODJAIL_VERSION=v0.1.2@0a86b34442a491fa8f5e4565e9c846fce310239c
ARG GO_VERSION=1.24
ARG UBUNTU_VERSION=24.04
ARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1
ARG GOTESTSUM_VERSION=v1.12.3
ARG NYDUS_VERSION=v2.3.2
ARG SOCI_SNAPSHOTTER_VERSION=0.11.1
ARG KUBO_VERSION=v0.35.0
ARG GOTESTSUM_VERSION=v1.12.2
ARG NYDUS_VERSION=v2.3.1
ARG SOCI_SNAPSHOTTER_VERSION=0.9.0
ARG KUBO_VERSION=v0.34.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1@sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3 AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS build-base
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS build-base-debian
COPY --from=xx / /
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
make \
git \
jq \
curl \
dpkg-dev
ARG TARGETARCH
# libbtrfs: for containerd
@ -76,12 +73,11 @@ RUN xx-apt-get update -qq && xx-apt-get install -qq --no-install-recommends \
pkg-config
RUN git config --global advice.detachedHead false
ADD hack/git-checkout-tag-with-hash.sh /usr/local/bin/
ADD hack/scripts/lib.sh /usr/local/bin/http::helper
FROM build-base AS build-containerd
FROM build-base-debian AS build-containerd
ARG TARGETARCH
ARG CONTAINERD_VERSION
RUN git clone --quiet --depth 1 --branch "${CONTAINERD_VERSION%%@*}" https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd
RUN git clone --quiet --depth 1 --branch "${CONTAINERD_VERSION%@*}" https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd
WORKDIR /go/src/github.com/containerd/containerd
RUN git-checkout-tag-with-hash.sh ${CONTAINERD_VERSION} && \
mkdir -p /out /out/$TARGETARCH && \
@ -89,10 +85,10 @@ RUN git-checkout-tag-with-hash.sh ${CONTAINERD_VERSION} && \
RUN GO=xx-go make STATIC=1 && \
cp -a bin/containerd bin/containerd-shim-runc-v2 bin/ctr /out/$TARGETARCH
FROM build-base AS build-runc
FROM build-base-debian AS build-runc
ARG RUNC_VERSION
ARG TARGETARCH
RUN git clone --quiet --depth 1 --branch "${RUNC_VERSION%%@*}" https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc
RUN git clone --quiet --depth 1 --branch "${RUNC_VERSION%@*}" https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc
WORKDIR /go/src/github.com/opencontainers/runc
RUN git-checkout-tag-with-hash.sh ${RUNC_VERSION} && \
mkdir -p /out
@ -100,10 +96,10 @@ ENV CGO_ENABLED=1
RUN GO=xx-go CC=$(xx-info)-gcc STRIP=$(xx-info)-strip make static && \
xx-verify --static runc && cp -v -a runc /out/runc.${TARGETARCH}
FROM build-base AS build-bypass4netns
FROM build-base-debian AS build-bypass4netns
ARG BYPASS4NETNS_VERSION
ARG TARGETARCH
RUN git clone --quiet --depth 1 --branch "${BYPASS4NETNS_VERSION%%@*}" https://github.com/rootless-containers/bypass4netns.git /go/src/github.com/rootless-containers/bypass4netns
RUN git clone --quiet --depth 1 --branch "${BYPASS4NETNS_VERSION%@*}" https://github.com/rootless-containers/bypass4netns.git /go/src/github.com/rootless-containers/bypass4netns
WORKDIR /go/src/github.com/rootless-containers/bypass4netns
RUN git-checkout-tag-with-hash.sh ${BYPASS4NETNS_VERSION} && \
mkdir -p /out/${TARGETARCH}
@ -111,20 +107,20 @@ ENV CGO_ENABLED=1
RUN GO=xx-go make static && \
xx-verify --static bypass4netns && cp -a bypass4netns bypass4netnsd /out/${TARGETARCH}
FROM build-base AS build-gomodjail
FROM build-base-debian AS build-gomodjail
ARG GOMODJAIL_VERSION
ARG TARGETARCH
RUN git clone --quiet --depth 1 --branch "${GOMODJAIL_VERSION%%@*}" https://github.com/AkihiroSuda/gomodjail.git /go/src/github.com/AkihiroSuda/gomodjail
RUN git clone --quiet --depth 1 --branch "${GOMODJAIL_VERSION%@*}" https://github.com/AkihiroSuda/gomodjail.git /go/src/github.com/AkihiroSuda/gomodjail
WORKDIR /go/src/github.com/AkihiroSuda/gomodjail
RUN git-checkout-tag-with-hash.sh ${GOMODJAIL_VERSION} && \
mkdir -p /out/${TARGETARCH}
RUN GO=xx-go make STATIC=1 && \
xx-verify --static _output/bin/gomodjail && cp -a _output/bin/gomodjail /out/${TARGETARCH}
FROM build-base AS build-kubo
FROM build-base-debian AS build-kubo
ARG KUBO_VERSION
ARG TARGETARCH
RUN git clone --quiet --depth 1 --branch "${KUBO_VERSION%%@*}" https://github.com/ipfs/kubo.git /go/src/github.com/ipfs/kubo
RUN git clone --quiet --depth 1 --branch "${KUBO_VERSION%@*}" https://github.com/ipfs/kubo.git /go/src/github.com/ipfs/kubo
WORKDIR /go/src/github.com/ipfs/kubo
RUN git-checkout-tag-with-hash.sh ${KUBO_VERSION} && \
mkdir -p /out/${TARGETARCH}
@ -133,6 +129,11 @@ RUN xx-go --wrap && \
make build && \
xx-verify --static cmd/ipfs/ipfs && cp -a cmd/ipfs/ipfs /out/${TARGETARCH}
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS build-base
RUN apk add --no-cache make git curl
RUN git config --global advice.detachedHead false
ADD hack/git-checkout-tag-with-hash.sh /usr/local/bin/
FROM build-base AS build-minimal
RUN BINDIR=/out/bin make binaries install
# We do not set CMD to `go test` here, because it requires systemd
@ -147,12 +148,12 @@ RUN mkdir -p /out/share/doc/nerdctl-full && touch /out/share/doc/nerdctl-full/RE
ARG CONTAINERD_VERSION
COPY --from=build-containerd /out/${TARGETARCH:-amd64}/* /out/bin/
COPY --from=build-containerd /out/containerd.service /out/lib/systemd/system/containerd.service
RUN echo "- containerd: ${CONTAINERD_VERSION%%@*}" >> /out/share/doc/nerdctl-full/README.md
RUN echo "- containerd: ${CONTAINERD_VERSION/@*}" >> /out/share/doc/nerdctl-full/README.md
ARG RUNC_VERSION
COPY --from=build-runc /out/runc.${TARGETARCH:-amd64} /out/bin/runc
RUN echo "- runc: ${RUNC_VERSION%%@*}" >> /out/share/doc/nerdctl-full/README.md
RUN echo "- runc: ${RUNC_VERSION/@*}" >> /out/share/doc/nerdctl-full/README.md
ARG CNI_PLUGINS_VERSION
RUN CNI_PLUGINS_VERSION=${CNI_PLUGINS_VERSION%%@*}; \
RUN CNI_PLUGINS_VERSION=${CNI_PLUGINS_VERSION/@BINARY}; \
fname="cni-plugins-${TARGETOS:-linux}-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/cni-plugins-${CNI_PLUGINS_VERSION}" | sha256sum -c && \
@ -161,7 +162,7 @@ RUN CNI_PLUGINS_VERSION=${CNI_PLUGINS_VERSION%%@*}; \
rm -f "${fname}" && \
echo "- CNI plugins: ${CNI_PLUGINS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BUILDKIT_VERSION
RUN BUILDKIT_VERSION=${BUILDKIT_VERSION%%@*}; \
RUN BUILDKIT_VERSION=${BUILDKIT_VERSION/@BINARY}; \
fname="buildkit-${BUILDKIT_VERSION}.${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/buildkit-${BUILDKIT_VERSION}" | sha256sum -c && \
@ -176,11 +177,10 @@ RUN cd /out/lib/systemd/system && \
echo "" >> buildkit.service && \
echo "# This file was converted from containerd.service, with \`sed -E '${sedcomm}'\`" >> buildkit.service
ARG STARGZ_SNAPSHOTTER_VERSION
RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \
STARGZ_SNAPSHOTTER_VERSION=${STARGZ_SNAPSHOTTER_VERSION%%@*}; \
RUN STARGZ_SNAPSHOTTER_VERSION=${STARGZ_SNAPSHOTTER_VERSION/@BINARY}; \
fname="stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/containerd/stargz-snapshotter/releases/download/${STARGZ_SNAPSHOTTER_VERSION}/${fname}" && \
http::helper github::file containerd/stargz-snapshotter script/config/etc/systemd/system/stargz-snapshotter.service "${STARGZ_SNAPSHOTTER_VERSION}" > "stargz-snapshotter.service" && \
curl -o "stargz-snapshotter.service" -fsSL --proto '=https' --tlsv1.2 "https://raw.githubusercontent.com/containerd/stargz-snapshotter/${STARGZ_SNAPSHOTTER_VERSION}/script/config/etc/systemd/system/stargz-snapshotter.service" && \
grep "${fname}" "/SHA256SUMS.d/stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}" | sha256sum -c - && \
grep "stargz-snapshotter.service" "/SHA256SUMS.d/stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}" | sha256sum -c - && \
tar xzf "${fname}" -C /out/bin && \
@ -188,13 +188,13 @@ RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \
mv stargz-snapshotter.service /out/lib/systemd/system/stargz-snapshotter.service && \
echo "- Stargz Snapshotter: ${STARGZ_SNAPSHOTTER_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG IMGCRYPT_VERSION
RUN git clone --quiet --depth 1 --branch "${IMGCRYPT_VERSION%%@*}" https://github.com/containerd/imgcrypt.git /go/src/github.com/containerd/imgcrypt && \
RUN git clone --quiet --depth 1 --branch "${IMGCRYPT_VERSION%@*}" https://github.com/containerd/imgcrypt.git /go/src/github.com/containerd/imgcrypt && \
cd /go/src/github.com/containerd/imgcrypt && \
git-checkout-tag-with-hash.sh "${IMGCRYPT_VERSION}" && \
CGO_ENABLED=0 make && DESTDIR=/out make install && \
echo "- imgcrypt: ${IMGCRYPT_VERSION%%@*}" >> /out/share/doc/nerdctl-full/README.md
echo "- imgcrypt: ${IMGCRYPT_VERSION/@*}" >> /out/share/doc/nerdctl-full/README.md
ARG SLIRP4NETNS_VERSION
RUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION%%@*}; \
RUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION/@BINARY}; \
fname="slirp4netns-$(cat /target_uname_m)" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/rootless-containers/slirp4netns/releases/download/${SLIRP4NETNS_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/slirp4netns-${SLIRP4NETNS_VERSION}" | sha256sum -c && \
@ -203,9 +203,9 @@ RUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION%%@*}; \
echo "- slirp4netns: ${SLIRP4NETNS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BYPASS4NETNS_VERSION
COPY --from=build-bypass4netns /out/${TARGETARCH:-amd64}/* /out/bin/
RUN echo "- bypass4netns: ${BYPASS4NETNS_VERSION%%@*}" >> /out/share/doc/nerdctl-full/README.md
RUN echo "- bypass4netns: ${BYPASS4NETNS_VERSION/@*}" >> /out/share/doc/nerdctl-full/README.md
ARG FUSE_OVERLAYFS_VERSION
RUN FUSE_OVERLAYFS_VERSION=${FUSE_OVERLAYFS_VERSION%%@*}; \
RUN FUSE_OVERLAYFS_VERSION=${FUSE_OVERLAYFS_VERSION/@BINARY}; \
fname="fuse-overlayfs-$(cat /target_uname_m)" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/containers/fuse-overlayfs/releases/download/${FUSE_OVERLAYFS_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/fuse-overlayfs-${FUSE_OVERLAYFS_VERSION}" | sha256sum -c && \
@ -213,24 +213,22 @@ RUN FUSE_OVERLAYFS_VERSION=${FUSE_OVERLAYFS_VERSION%%@*}; \
chmod +x /out/bin/fuse-overlayfs && \
echo "- fuse-overlayfs: ${FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG CONTAINERD_FUSE_OVERLAYFS_VERSION
RUN CONTAINERD_FUSE_OVERLAYFS_VERSION=${CONTAINERD_FUSE_OVERLAYFS_VERSION%%@*}; \
fname="containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION##*v}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
RUN CONTAINERD_FUSE_OVERLAYFS_VERSION=${CONTAINERD_FUSE_OVERLAYFS_VERSION/@BINARY}; \
fname="containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION/v}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/containerd/fuse-overlayfs-snapshotter/releases/download/${CONTAINERD_FUSE_OVERLAYFS_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION}" | sha256sum -c && \
tar xzf "${fname}" -C /out/bin && \
rm -f "${fname}" && \
echo "- containerd-fuse-overlayfs: ${CONTAINERD_FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG TINI_VERSION
RUN TINI_VERSION=${TINI_VERSION%%@*}; \
RUN TINI_VERSION=${TINI_VERSION/@BINARY}; \
fname="tini-static-${TARGETARCH:-amd64}" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/tini-${TINI_VERSION}" | sha256sum -c && \
cp -a "${fname}" /out/bin/tini && chmod +x /out/bin/tini && \
echo "- Tini: ${TINI_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BUILDG_VERSION
# FIXME: this is a mildly-confusing approach. Buildkit will perform some "smart" replacement at build time and output
# confusing debugging information, eg: BUILDG_VERSION will appear as if the original ARG value was used.
RUN BUILDG_VERSION=${BUILDG_VERSION%%@*}; \
RUN BUILDG_VERSION=${BUILDG_VERSION/@BINARY}; \
fname="buildg-${BUILDG_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/ktock/buildg/releases/download/${BUILDG_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/buildg-${BUILDG_VERSION}" | sha256sum -c && \
@ -238,7 +236,7 @@ RUN BUILDG_VERSION=${BUILDG_VERSION%%@*}; \
rm -f "${fname}" && \
echo "- buildg: ${BUILDG_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG ROOTLESSKIT_VERSION
RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION%%@*}; \
RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION/@BINARY}; \
fname="rootlesskit-$(cat /target_uname_m).tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/rootless-containers/rootlesskit/releases/download/${ROOTLESSKIT_VERSION}/${fname}" && \
grep "${fname}" "/SHA256SUMS.d/rootlesskit-${ROOTLESSKIT_VERSION}" | sha256sum -c && \
@ -248,17 +246,13 @@ RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION%%@*}; \
ARG GOMODJAIL_VERSION
COPY --from=build-gomodjail /out/${TARGETARCH:-amd64}/* /out/bin/
RUN echo "- gomodjail: ${GOMODJAIL_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG CONTAINERIZED_SYSTEMD_VERSION
RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \
http::helper github::file AkihiroSuda/containerized-systemd docker-entrypoint.sh "${CONTAINERIZED_SYSTEMD_VERSION}" > /docker-entrypoint.sh && \
chmod +x /docker-entrypoint.sh
RUN echo "" >> /out/share/doc/nerdctl-full/README.md && \
echo "## License" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/slirp4netns: [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/rootless-containers/slirp4netns/blob/${SLIRP4NETNS_VERSION%%@*}/COPYING)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/fuse-overlayfs: [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/containers/fuse-overlayfs/blob/${FUSE_OVERLAYFS_VERSION%%@*}/COPYING)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/slirp4netns: [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/rootless-containers/slirp4netns/blob/${SLIRP4NETNS_VERSION/@*}/COPYING)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/fuse-overlayfs: [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/containers/fuse-overlayfs/blob/${FUSE_OVERLAYFS_VERSION/@*}/COPYING)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/{runc,bypass4netns,bypass4netnsd}: Apache License 2.0, statically linked with libseccomp ([LGPL 2.1](https://github.com/seccomp/libseccomp/blob/main/LICENSE), source code available at https://github.com/seccomp/libseccomp/)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/tini: [MIT License](https://github.com/krallin/tini/blob/${TINI_VERSION%%@*}/LICENSE)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- bin/tini: [MIT License](https://github.com/krallin/tini/blob/${TINI_VERSION/@*}/LICENSE)" >> /out/share/doc/nerdctl-full/README.md && \
echo "- Other files: [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)" >> /out/share/doc/nerdctl-full/README.md
FROM build-dependencies AS build-full
@ -288,7 +282,9 @@ RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \
iproute2 iptables \
dbus dbus-user-session systemd systemd-sysv \
fuse3
COPY --from=build-full /docker-entrypoint.sh /docker-entrypoint.sh
ARG CONTAINERIZED_SYSTEMD_VERSION
RUN curl -o /docker-entrypoint.sh -fsSL --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/AkihiroSuda/containerized-systemd/${CONTAINERIZED_SYSTEMD_VERSION}/docker-entrypoint.sh && \
chmod +x /docker-entrypoint.sh
COPY --from=out-full / /usr/local/
RUN perl -pi -e 's/multi-user.target/docker-entrypoint.target/g' /usr/local/lib/systemd/system/*.service && \
systemctl enable containerd buildkit stargz-snapshotter && \
@ -314,7 +310,7 @@ RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
git \
make
# We wouldn't need this if Docker Hub could have "golang:${GO_VERSION}-ubuntu"
COPY --from=build-base /usr/local/go /usr/local/go
COPY --from=build-base-debian /usr/local/go /usr/local/go
ARG TARGETARCH
ENV PATH=/usr/local/go/bin:$PATH
ARG GOTESTSUM_VERSION
@ -329,10 +325,7 @@ COPY --from=ghcr.io/sigstore/cosign/cosign:v2.2.3@sha256:8fc9cad121611e8479f65f7
ARG SOCI_SNAPSHOTTER_VERSION
RUN fname="soci-snapshotter-${SOCI_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/awslabs/soci-snapshotter/releases/download/v${SOCI_SNAPSHOTTER_VERSION}/${fname}" && \
tar -C /usr/local/bin -xvf "${fname}" soci soci-snapshotter-grpc && \
mkdir -p /etc/soci-snapshotter-grpc && \
touch /etc/soci-snapshotter-grpc/config.toml && \
echo "\n[pull_modes]\n [pull_modes.soci_v1]\n enable = true" >> /etc/soci-snapshotter-grpc/config.toml
tar -C /usr/local/bin -xvf "${fname}" soci soci-snapshotter-grpc
# enable offline ipfs for integration test
COPY --from=build-kubo /out/${TARGETARCH:-amd64}/* /usr/local/bin/
COPY ./Dockerfile.d/test-integration-etc_containerd-stargz-grpc_config.toml /etc/containerd-stargz-grpc/config.toml

View File

@ -0,0 +1,2 @@
70371949ac56d118e55306091640e63537069a538a97c151eb7475c07cb5a8a4 buildg-v0.5.2-linux-amd64.tar.gz
9c44a5f8ecc3035998a07e1c564338205700cf5287c723e8ccba1da2815168cc buildg-v0.5.2-linux-arm64.tar.gz

View File

@ -1,4 +0,0 @@
cf4c40c58ca795eeb6e75e2c6a0e5bb3a6a9c0623d51bc3b85163e5d483eeade buildg-full-v0.5.3-linux-amd64.tar.gz
47c479f2e5150c9c76294fa93a03ad20e5928f4315bf52ca8432bfb6707d4276 buildg-full-v0.5.3-linux-arm64.tar.gz
c289a454ae8673ff99acf56dec9ba97274c20d2015e80f7ac3b8eb8e4f77888f buildg-v0.5.3-linux-amd64.tar.gz
b2e244250ce7ea5c090388f2025a9c546557861d25bba7b0666aa512f01fa6cd buildg-v0.5.3-linux-arm64.tar.gz

View File

@ -0,0 +1,2 @@
e0d83a631a48f13232fcee71cbd913e6b11dbde0a45985fa1b99af27ab97086e buildkit-v0.21.1.linux-amd64.tar.gz
7652a05f2961c386ea6e65c4701daa0e5a899a20c77596cd5f0eca02851dc1f6 buildkit-v0.21.1.linux-arm64.tar.gz

View File

@ -1,2 +0,0 @@
2771c3403e3a1f75a83cde387a05365794d3b900c355e864772a36c3ce541f82 buildkit-v0.23.2.linux-amd64.tar.gz
6385ff70b2fb4134b50ac3183eea3a0b06c6f6129173940d73178ae0477368f1 buildkit-v0.23.2.linux-arm64.tar.gz

View File

@ -0,0 +1,6 @@
acc149d60e2fad0cff480852c82f39bdaae2eb6faa265b2028c944ec572014f9 containerd-fuse-overlayfs-2.1.5-linux-amd64.tar.gz
2c1c12a99ac16e6ad137c474517d04cc7864d26d9045f50f99a6d6e887b9c425 containerd-fuse-overlayfs-2.1.5-linux-arm-v7.tar.gz
17759de9588cda1499877cc9587189eb24731ae41edda201087fd74658ddc127 containerd-fuse-overlayfs-2.1.5-linux-arm64.tar.gz
ce0310573fd667a2fa348588b12f1867a1bad5befc79d7d39e6419a7d4687ea8 containerd-fuse-overlayfs-2.1.5-linux-ppc64le.tar.gz
e9bbb9835346d8007a6429151eb7c7b23fa1f20b85aa6d20dd3702cb5a4c038a containerd-fuse-overlayfs-2.1.5-linux-riscv64.tar.gz
c088a7eee9b75f0a759e52d1ae2c8d69d21265594070f41021a94523d1c7bab1 containerd-fuse-overlayfs-2.1.5-linux-s390x.tar.gz

View File

@ -1,6 +0,0 @@
8a768e4c953251d32b5e5d748d17593f7150834caaba403b483cf83f5856fea3 containerd-fuse-overlayfs-2.1.6-linux-amd64.tar.gz
a3af866a12e913cd1d4dda8e41c08345eca928a15ac1d466fdb2b00b013e14ee containerd-fuse-overlayfs-2.1.6-linux-arm-v7.tar.gz
417ca0c838e43e446f498b384d73f7caaeb00dc4c1c0fe4b0ecfdd36fd355daa containerd-fuse-overlayfs-2.1.6-linux-arm64.tar.gz
5fdebd9fb7b50473318f0410bc3ab46f3388ac8aa586b45c91a314af9ce6569c containerd-fuse-overlayfs-2.1.6-linux-ppc64le.tar.gz
7e1a9d2ba68ff31a8dfb53bf6e71b2879063b13c759922c8cff3013893829bca containerd-fuse-overlayfs-2.1.6-linux-riscv64.tar.gz
3c022651cdaff666e88996d5d9c7e776bf59419a03d7d718a28aa708036419f9 containerd-fuse-overlayfs-2.1.6-linux-s390x.tar.gz

View File

@ -0,0 +1,7 @@
b4162d27bbbd3683ca8ee57b51a1b270c0054b3a15fcc1830a5d7c10b77ad045 SOURCE_DATE_EPOCH
c55117faa5e18345a3ee1515267f056822ff0c1897999ae5422b0114ee48df85 slirp4netns-aarch64
f55a6c9e3ec8280e9c3cec083f07dc124e2846ce8139a9281c35013e968d7e95 slirp4netns-armv7l
7b388a9cacbd89821f7f7a6457470fcae8f51aa846162521589feb4634ec7586 slirp4netns-ppc64le
041f9fe507510de1fbb802933a6add093ff19f941185965295c81f2ba4fc9cec slirp4netns-riscv64
aa39cf14414ae53dbff6b79dfdfa55b5ff8ac5250e2261804863cd365b33a818 slirp4netns-s390x
4d55a3658ae259e3e74bb75cf058eb05d6e39ad6bbe170ca8e94c2462bea0eb1 slirp4netns-x86_64

View File

@ -1,7 +0,0 @@
d0e6a13342efbedb8b7454629a0e9ce9b7a937c261034c85f46ed81af76307d8 SOURCE_DATE_EPOCH
1ca9d2f5f1fb4beb91f354653e5dad35b95c049afb264268d99a96ff2a10d903 slirp4netns-aarch64
3e209d1c56fccbe627a038d311b233c15e8d914b30f9b981b5ed78b98e836859 slirp4netns-armv7l
4d1003a98103ee170c0fcd4aad8a5e0ba7aa2e70fbca883cbb6a39f40447c8da slirp4netns-ppc64le
06a13b398d88120097b20dace966d7dd5e2fbfd284b95a086347808df392200e slirp4netns-riscv64
23d4a206edd6d3fc9c86f8b05c0881ff77a607b8d471f20964ad9f9c3f3176b1 slirp4netns-s390x
5618887b671a30a2f7548f2bdf7fba98a53981abc80cfd3183cd28b4dc8b2b97 slirp4netns-x86_64

View File

@ -22,7 +22,6 @@
# GitHub ID, Name, Email address, GPG fingerprint
"jsturtevant","James Sturtevant","jstur@microsoft.com",""
"manugupt1", "Manu Gupta", "manugupt1@gmail.com","FCA9 504A 4118 EA5C F466 CC30 A5C3 A8F4 E7FE 9E10"
"Shubhranshu153","Shubharanshu Mahapatra","shubhum@amazon.com",""
# EMERITUS
# See EMERITUS.md

View File

@ -46,9 +46,6 @@ LINT_COMMIT_RANGE ?= main..HEAD
GO_BUILD_LDFLAGS ?= -s -w
GO_BUILD_FLAGS ?=
BUILDTAGS ?=
GO_TAGS=$(if $(BUILDTAGS),-tags "$(strip $(BUILDTAGS))",)
##########################
# Helpers
##########################
@ -57,7 +54,7 @@ ifdef VERBOSE
VERBOSE_FLAG_LONG := --verbose
endif
export GO_BUILD=CGO_ENABLED=0 GOOS=$(GOOS) $(GO) -C $(MAKEFILE_DIR) build $(GO_TAGS) -ldflags "$(GO_BUILD_LDFLAGS) $(VERBOSE_FLAG) -X $(PACKAGE)/pkg/version.Version=$(VERSION) -X $(PACKAGE)/pkg/version.Revision=$(REVISION)"
export GO_BUILD=CGO_ENABLED=0 GOOS=$(GOOS) $(GO) -C $(MAKEFILE_DIR) build -ldflags "$(GO_BUILD_LDFLAGS) $(VERBOSE_FLAG) -X $(PACKAGE)/pkg/version.Version=$(VERSION) -X $(PACKAGE)/pkg/version.Revision=$(REVISION)"
ifndef NO_COLOR
NC := \033[0m
@ -185,7 +182,7 @@ lint-licenses-all:
&& GOOS=linux make lint-licenses \
&& GOOS=windows make lint-licenses \
&& GOOS=freebsd make lint-licenses \
&& GOOS=darwin make lint-licenses
&& GOOS=darwin make lint-go
$(call footer, $@)
##########################
@ -203,7 +200,7 @@ fix-go-all:
&& GOOS=linux make fix-go \
&& GOOS=windows make fix-go \
&& GOOS=freebsd make fix-go \
&& GOOS=darwin make fix-go
&& GOOS=darwin make lint-go
$(call footer, $@)
fix-mod:
@ -221,14 +218,12 @@ install-dev-tools:
# git-validation: main (2025-02-25)
# ltag: main (2025-03-04)
# go-licenses: v2.0.0-alpha.1 (2024-06-27)
# stubbing go-licenses with dependency upgrade due to non-compatibility with golang 1.25rc1
# Issue: https://github.com/google/go-licenses/issues/312
@cd $(MAKEFILE_DIR) \
&& go install github.com/Shubhranshu153/go-licenses/v2@f8c503d1357dffb6c97ed3b94e912ab294dde24a \
&& go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@2b224c2cf4c9f261c22a16af7f8ca6408467f338 \
&& go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \
&& go install github.com/containerd/ltag@66e6a514664ee2d11a470735519fa22b1a9eaabd \
&& go install gotest.tools/gotestsum@0d9599e513d70e5792bb9334869f82f6e8b53d4d
&& go install github.com/google/go-licenses/v2@d01822334fba5896920a060f762ea7ecdbd086e8 \
&& go install gotest.tools/gotestsum@ac6dad9c7d87b969004f7749d1942938526c9716
@echo "Remember to add \$$HOME/go/bin to your path"
$(call footer, $@)
@ -258,7 +253,7 @@ TAR_OWNER0_FLAGS=--owner=0 --group=0
TAR_FLATTEN_FLAGS=--transform 's/.*\///g'
define make_artifact_full_linux
$(DOCKER) build --secret id=github_token,env=GITHUB_TOKEN --output type=tar,dest=$(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar --target out-full --platform $(1) --build-arg GO_VERSION -f $(MAKEFILE_DIR)/Dockerfile $(MAKEFILE_DIR)
$(DOCKER) build --output type=tar,dest=$(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar --target out-full --platform $(1) --build-arg GO_VERSION -f $(MAKEFILE_DIR)/Dockerfile $(MAKEFILE_DIR)
gzip -9 $(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar
endef

View File

@ -19,6 +19,7 @@ package builder
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
@ -262,6 +263,13 @@ func GetBuildkitHost(cmd *cobra.Command, namespace string) (string, error) {
return buildkitHost, nil
}
if buildkitHost := os.Getenv("BUILDKIT_HOST"); buildkitHost != "" {
if err := buildkitutil.PingBKDaemon(buildkitHost); err != nil {
return "", err
}
return buildkitHost, nil
}
return buildkitutil.GetBuildkitHost(namespace)
}

View File

@ -27,7 +27,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -101,13 +100,14 @@ CMD ["echo", "test-nerdctl-build-context-oci-layout"]`
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
assert.Assert(
t,
strings.Contains(
helpers.Capture("run", "--rm", data.Identifier("child")),
"test-nerdctl-build-context-oci-layout",
),
info,
)
},
}

View File

@ -19,9 +19,7 @@ package builder
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
@ -31,7 +29,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
"github.com/containerd/nerdctl/v2/pkg/platformutil"
@ -113,7 +110,6 @@ CMD ["echo", "nerdctl-build-test-string"]`, testutil.CommonImage)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rmi", "-f", data.Identifier("ignored"))
helpers.Anyhow("rmi", "-f", data.Identifier())
},
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
},
@ -343,7 +339,7 @@ COPY %s /`, testFileName)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
// Expecting testFileName to exist inside the output target directory
assert.Equal(t, data.Temp().Load(testFileName), testContent, "file content is identical")
},
@ -357,7 +353,7 @@ COPY %s /`, testFileName)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
assert.Equal(t, data.Temp().Load(testFileName), testContent, "file content is identical")
},
}
@ -854,9 +850,8 @@ RUN curl -I http://google.com
func TestBuildAttestation(t *testing.T) {
nerdtest.Setup()
// Using regex patterns to match SBOM and provenance files with optional platform suffix
const testSBOMFilePattern = `sbom\.spdx(?:\.[a-z0-9_]+)?\.json`
const testProvenanceFilePattern = `provenance(?:\.[a-z0-9_]+)?\.json`
const testSBOMFileName = "sbom.spdx.json"
const testProvenanceFileName = "provenance.json"
dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)
@ -895,18 +890,8 @@ func TestBuildAttestation(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
files, err := os.ReadDir(data.Temp().Path("dir-for-bom"))
assert.NilError(t, err, "failed to read directory")
found := false
for _, file := range files {
if !file.IsDir() && regexp.MustCompile(testSBOMFilePattern).MatchString(file.Name()) {
found = true
break
}
}
assert.Assert(t, found, "no SBOM file matching pattern %s found", testSBOMFilePattern)
Output: func(stdout, info string, t *testing.T) {
data.Temp().Exists("dir-for-bom", testSBOMFileName)
},
}
},
@ -927,18 +912,8 @@ func TestBuildAttestation(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
files, err := os.ReadDir(data.Temp().Path("dir-for-prov"))
assert.NilError(t, err, "failed to read directory")
found := false
for _, file := range files {
if !file.IsDir() && regexp.MustCompile(testProvenanceFilePattern).MatchString(file.Name()) {
found = true
break
}
}
assert.Assert(t, found, "no provenance file matching pattern %s found", testProvenanceFilePattern)
Output: func(stdout, info string, t *testing.T) {
data.Temp().Exists("dir-for-prov", testProvenanceFileName)
},
}
},
@ -960,29 +935,9 @@ func TestBuildAttestation(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
// Check if any file in the directory matches the SBOM file pattern
files, err := os.ReadDir(data.Temp().Path("dir-for-attest"))
assert.NilError(t, err, "failed to read directory")
sbomFound := false
for _, file := range files {
if !file.IsDir() && regexp.MustCompile(testSBOMFilePattern).MatchString(file.Name()) {
sbomFound = true
break
}
}
assert.Assert(t, sbomFound, "no SBOM file matching pattern %s found", testSBOMFilePattern)
// Check if any file in the directory matches the provenance file pattern
provenanceFound := false
for _, file := range files {
if !file.IsDir() && regexp.MustCompile(testProvenanceFilePattern).MatchString(file.Name()) {
provenanceFound = true
break
}
}
assert.Assert(t, provenanceFound, "no provenance file matching pattern %s found", testProvenanceFilePattern)
Output: func(stdout, info string, t *testing.T) {
data.Temp().Exists("dir-for-attest", testSBOMFileName)
data.Temp().Exists("dir-for-attest", testProvenanceFileName)
},
}
},

View File

@ -30,7 +30,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
@ -153,19 +152,14 @@ CMD ["echo", "nerdctl-builder-debug-test-string"]`, testutil.CommonImage)
// FIXME: this test should be rewritten to dynamically retrieve the ids, and use images
// available on all platforms
oldImage := testutil.BusyboxImage
parsedOldImage, err := referenceutil.Parse(oldImage)
assert.NilError(helpers.T(), err)
oldImageSha := parsedOldImage.Digest.String()
oldImageSha := "7b3ccabffc97de872a30dfd234fd972a66d247c8cfc69b0550f276481852627c"
newImage := testutil.AlpineImage
parsedNewImage, err := referenceutil.Parse(newImage)
assert.NilError(helpers.T(), err)
newImageSha := parsedNewImage.Digest.String()
newImageSha := "ec14c7992a97fc11425907e908340c6c3d6ff602f5f13d899e6b7027c9b4133a"
helpers.Ensure("pull", "--quiet", oldImage)
helpers.Ensure("tag", oldImage, parsedNewImage.Domain+"/"+parsedNewImage.Path+":"+parsedNewImage.Tag)
helpers.Ensure("tag", oldImage, newImage)
dockerfile := fmt.Sprintf(`FROM %s`, parsedNewImage.Domain+"/"+parsedNewImage.Path+":"+parsedNewImage.Tag)
dockerfile := fmt.Sprintf(`FROM %s`, newImage)
data.Temp().Save(dockerfile, "Dockerfile")
data.Labels().Set("oldImageSha", oldImageSha)
data.Labels().Set("newImageSha", newImageSha)

View File

@ -38,49 +38,6 @@ func IPAMDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string
return []string{"default", "host-local", "dhcp"}, cobra.ShellCompDirectiveNoFileComp
}
func NetworkOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
driver, _ := cmd.Flags().GetString("driver")
if driver == "" {
driver = "bridge"
}
var candidates []string
switch driver {
case "bridge":
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
"ip-masq=",
"com.docker.network.bridge.enable_ip_masquerade=",
}
case "macvlan":
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
"mode=bridge",
"macvlan_mode=bridge",
"parent=",
}
case "ipvlan":
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
"mode=l2",
"mode=l3",
"ipvlan_mode=l2",
"ipvlan_mode=l3",
"parent=",
}
default:
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
"parent=",
}
}
return candidates, cobra.ShellCompDirectiveNoSpace
}
func NamespaceNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
if err != nil {

View File

@ -38,25 +38,3 @@ func NetworkDrivers(cmd *cobra.Command, args []string, toComplete string) ([]str
func IPAMDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"default"}, cobra.ShellCompDirectiveNoFileComp
}
func NetworkOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
driver, _ := cmd.Flags().GetString("driver")
if driver == "" {
driver = "nat"
}
var candidates []string
switch driver {
case "nat":
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
}
default:
candidates = []string{
"mtu=",
"com.docker.network.driver.mtu=",
}
}
return candidates, cobra.ShellCompDirectiveNoSpace
}

View File

@ -29,7 +29,7 @@ import (
)
func TestComposeBuild(t *testing.T) {
dockerfile := "FROM " + testutil.CommonImage
dockerfile := "FROM " + testutil.AlpineImage
testCase := nerdtest.Setup()
@ -46,11 +46,15 @@ services:
svc0:
build: .
image: %s
ports:
- 8080:80
depends_on:
- svc1
svc1:
build: .
image: %s
ports:
- 8081:80
`, imageSvc0, imageSvc1)
data.Temp().Save(dockerComposeYAML, "compose.yaml")

View File

@ -24,19 +24,16 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeConfig(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(`
const dockerComposeYAML = `
services:
hello:
image: %s
`, testutil.CommonImage)
image: alpine:3.13
`
testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
@ -114,7 +111,7 @@ services:
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
assert.Assert(t, data.Labels().Get("hash") != stdout, "hash should be different")
},
}

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -32,6 +31,8 @@ import (
func TestComposeCopy(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -78,7 +79,7 @@ services:
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
copied := data.Temp().Load("test-file2")
assert.Equal(t, copied, testFileContent)
},

View File

@ -25,7 +25,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -33,10 +32,12 @@ import (
func TestComposeCreate(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
`, testutil.CommonImage)
`, testutil.AlpineImage)
testCase := nerdtest.Setup()
@ -65,7 +66,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) {
assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`")
@ -86,6 +87,8 @@ services:
func TestComposeCreateDependency(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -122,7 +125,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) {
assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`")
@ -134,7 +137,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc1", "-a")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) {
assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`")
@ -149,10 +152,12 @@ func TestComposeCreatePull(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -162,12 +167,12 @@ services:
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
// `compose create --pull never` should fail: no such image
base.Cmd("rmi", "-f", testutil.CommonImage).Run()
base.Cmd("rmi", "-f", testutil.AlpineImage).Run()
base.ComposeCmd("-f", comp.YAMLFullPath(), "create", "--pull", "never").AssertFail()
// `compose create --pull missing(default)|always` should succeed: image is pulled and container is created
base.Cmd("rmi", "-f", testutil.CommonImage).Run()
base.Cmd("rmi", "-f", testutil.AlpineImage).Run()
base.ComposeCmd("-f", comp.YAMLFullPath(), "create").AssertOK()
base.Cmd("rmi", "-f", testutil.CommonImage).Run()
base.Cmd("rmi", "-f", testutil.AlpineImage).Run()
base.ComposeCmd("-f", comp.YAMLFullPath(), "create", "--pull", "always").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created")
}
@ -182,7 +187,7 @@ services:
image: %s
`, imageSvc0)
dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)
dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage)
testutil.RequiresBuild(t)
testutil.RegisterBuildCacheCleanup(t)

View File

@ -30,18 +30,20 @@ func TestComposeDownRemoveUsedNetwork(t *testing.T) {
var (
dockerComposeYAMLOrphan = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
`, testutil.AlpineImage)
dockerComposeYAMLFull = fmt.Sprintf(`
%s
orphan:
image: %s
command: "sleep infinity"
`, dockerComposeYAMLOrphan, testutil.CommonImage)
`, dockerComposeYAMLOrphan, testutil.AlpineImage)
)
compOrphan := testutil.NewComposeDir(t, dockerComposeYAMLOrphan)
@ -64,18 +66,20 @@ func TestComposeDownRemoveOrphans(t *testing.T) {
var (
dockerComposeYAMLOrphan = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
`, testutil.AlpineImage)
dockerComposeYAMLFull = fmt.Sprintf(`
%s
orphan:
image: %s
command: "sleep infinity"
`, dockerComposeYAMLOrphan, testutil.CommonImage)
`, dockerComposeYAMLOrphan, testutil.AlpineImage)
)
compOrphan := testutil.NewComposeDir(t, dockerComposeYAMLOrphan)

View File

@ -34,6 +34,8 @@ import (
func TestComposeExec(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -177,6 +179,8 @@ services:
func TestComposeExecTTY(t *testing.T) {
const expectedOutput = "speed 38400 baud"
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -263,6 +267,8 @@ services:
func TestComposeExecWithIndex(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -279,11 +285,6 @@ services:
data.Labels().Set("projectName", strings.ToLower(filepath.Base(data.Temp().Dir())))
helpers.Ensure("compose", "-f", yamlPath, "up", "-d", "svc0")
// Make sure all containers are started so that /etc/hosts is consistent.
for _, index := range []string{"1", "2", "3"} {
nerdtest.EnsureContainerStarted(helpers, fmt.Sprintf("%s-svc0-%s", data.Labels().Get("projectName"), index))
}
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {

View File

@ -17,26 +17,77 @@
package compose
import (
"encoding/json"
"fmt"
"strings"
"testing"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeImages(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- wordpress:/var/www/html
db:
image: %s
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db:/var/lib/mysql
volumes:
wordpress:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
wordpressImageName := strings.Split(testutil.WordpressImage, ":")[0]
dbImageName := strings.Split(testutil.MariaDBImage, ":")[0]
// check one service image
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "db").AssertOutContains(dbImageName)
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "db").AssertOutNotContains(wordpressImageName)
// check all service images
base.ComposeCmd("-f", comp.YAMLFullPath(), "images").AssertOutContains(dbImageName)
base.ComposeCmd("-f", comp.YAMLFullPath(), "images").AssertOutContains(wordpressImageName)
}
func TestComposeImagesJson(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
container_name: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
@ -60,71 +111,41 @@ volumes:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)
wordpressImageName, _ := referenceutil.Parse(testutil.WordpressImage)
dbImageName, _ := referenceutil.Parse(testutil.MariaDBImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase := nerdtest.Setup()
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml"))
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
assertHandler := func(svc string, count int, fields ...string) func(stdout string) error {
return func(stdout string) error {
// 1. check json output can be unmarshalled back to printables.
var printables []composeContainerPrintable
if err := json.Unmarshal([]byte(stdout), &printables); err != nil {
return fmt.Errorf("[service: %s]failed to unmarshal json output from `compose images`: %s", svc, stdout)
}
// 2. check #printables matches expected count.
if len(printables) != count {
return fmt.Errorf("[service: %s]unmarshal generates %d printables, expected %d: %s", svc, len(printables), count, stdout)
}
// 3. check marshalled json string has all expected substrings.
for _, field := range fields {
if !strings.Contains(stdout, field) {
return fmt.Errorf("[service: %s]marshalled json output doesn't have expected string (%s): %s", svc, field, stdout)
}
}
return nil
}
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
// check other formats are not supported
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "--format", "yaml").AssertFail()
// check all services are up (can be marshalled and unmarshalled)
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "--format", "json").
AssertOutWithFunc(assertHandler("all", 2, `"ContainerName":"wordpress"`, `"ContainerName":"db"`))
testCase.SubTests = []*test.Case{
{
Description: "images db",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "images", "db")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains(dbImageName.Name()),
expect.DoesNotContain(wordpressImageName.Name()),
)),
},
{
Description: "images",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "images")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(dbImageName.Name(), wordpressImageName.Name())),
},
{
Description: "images --format yaml",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "images", "--format", "yaml")
},
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
},
{
Description: "images --format json",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "images", "--format", "json")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.JSON([]composeContainerPrintable{}, func(printables []composeContainerPrintable, t tig.T) {
assert.Equal(t, len(printables), 2)
}),
expect.Contains(`"ContainerName":"wordpress"`, `"ContainerName":"db"`),
)),
},
{
Description: "images --format json wordpress",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "images", "--format", "json", "wordpress")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.JSON([]composeContainerPrintable{}, func(printables []composeContainerPrintable, t tig.T) {
assert.Equal(t, len(printables), 1)
}),
expect.Contains(`"ContainerName":"wordpress"`),
)),
},
}
testCase.Run(t)
base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "--format", "json", "wordpress").
AssertOutWithFunc(assertHandler("wordpress", 1, `"ContainerName":"wordpress"`))
}

View File

@ -27,10 +27,14 @@ import (
func TestComposeKill(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser

View File

@ -31,6 +31,8 @@ func TestComposePauseAndUnpause(t *testing.T) {
}
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s

View File

@ -88,18 +88,11 @@ func portAction(cmd *cobra.Command, args []string) error {
return err
}
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
if err != nil {
return err
}
po := composer.PortOptions{
ServiceName: args[0],
Index: index,
Port: port,
Protocol: protocol,
DataStore: dataStore,
Namespace: globalOptions.Namespace,
}
return c.Port(ctx, cmd.OutOrStdout(), po)

View File

@ -20,17 +20,15 @@ import (
"fmt"
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposePort(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -57,6 +55,8 @@ func TestComposePortFailure(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -79,42 +79,3 @@ services:
base.ComposeCmd("-f", comp.YAMLFullPath(), "port", "--protocol", "udp", "svc0", "10000").AssertFail()
base.ComposeCmd("-f", comp.YAMLFullPath(), "port", "--protocol", "tcp", "svc0", "10001").AssertFail()
}
// TestComposeMultiplePorts tests whether it is possible to allocate a large
// number of ports. (https://github.com/containerd/nerdctl/issues/4027)
func TestComposeMultiplePorts(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(`
services:
svc0:
image: %s
command: "sleep infinity"
ports:
- '32000-32060:32000-32060'
`, testutil.AlpineImage)
testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
compYamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml")
data.Labels().Set("composeYaml", compYamlPath)
helpers.Ensure("compose", "-f", compYamlPath, "up", "-d")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v")
}
testCase.SubTests = []*test.Case{
{
Description: "Issue #4027 - Allocate a large number of ports.",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "port", "svc0", "32000")
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("0.0.0.0:32000")),
},
}
testCase.Run(t)
}

View File

@ -29,9 +29,9 @@ import (
"github.com/containerd/containerd/v2/core/runtime/restart"
"github.com/containerd/errdefs"
"github.com/containerd/go-cni"
"github.com/containerd/log"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/api/types"
"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/cmd/compose"
"github.com/containerd/nerdctl/v2/pkg/containerutil"
@ -183,9 +183,9 @@ func psAction(cmd *cobra.Command, args []string) error {
var p composeContainerPrintable
var err error
if format == "json" {
p, err = composeContainerPrintableJSON(ctx, container, globalOptions)
p, err = composeContainerPrintableJSON(ctx, container)
} else {
p, err = composeContainerPrintableTab(ctx, container, globalOptions)
p, err = composeContainerPrintableTab(ctx, container)
}
if err != nil {
return err
@ -234,7 +234,7 @@ func psAction(cmd *cobra.Command, args []string) error {
// composeContainerPrintableTab constructs composeContainerPrintable with fields
// only for console output.
func composeContainerPrintableTab(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
func composeContainerPrintableTab(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return composeContainerPrintable{}, err
@ -251,18 +251,6 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
if err != nil {
return composeContainerPrintable{}, err
}
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
if err != nil {
return composeContainerPrintable{}, err
}
containerLabels, err := container.Labels(ctx)
if err != nil {
return composeContainerPrintable{}, err
}
ports, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)
if err != nil {
return composeContainerPrintable{}, err
}
return composeContainerPrintable{
Name: info.Labels[labels.Name],
@ -270,13 +258,13 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
Command: formatter.InspectContainerCommandTrunc(spec),
Service: info.Labels[labels.ComposeService],
State: status,
Ports: formatter.FormatPorts(ports),
Ports: formatter.FormatPorts(info.Labels),
}, nil
}
// composeContainerPrintableJSON constructs composeContainerPrintable with fields
// only for json output and compatible docker output.
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {
func composeContainerPrintableJSON(ctx context.Context, container containerd.Container) (composeContainerPrintable, error) {
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return composeContainerPrintable{}, err
@ -306,18 +294,6 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
if err != nil {
return composeContainerPrintable{}, err
}
dataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)
if err != nil {
return composeContainerPrintable{}, err
}
containerLabels, err := container.Labels(ctx)
if err != nil {
return composeContainerPrintable{}, err
}
portMappings, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)
if err != nil {
return composeContainerPrintable{}, err
}
return composeContainerPrintable{
ID: container.ID(),
@ -329,7 +305,7 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
State: state,
Health: "",
ExitCode: exitCode,
Publishers: formatPublishers(portMappings),
Publishers: formatPublishers(info.Labels),
}, nil
}
@ -345,7 +321,7 @@ type PortPublisher struct {
// formatPublishers parses and returns docker-compatible []PortPublisher from
// label map. If an error happens, an empty slice is returned.
func formatPublishers(portMappings []cni.PortMapping) []PortPublisher {
func formatPublishers(labelMap map[string]string) []PortPublisher {
mapper := func(pm cni.PortMapping) PortPublisher {
return PortPublisher{
URL: pm.HostIP,
@ -356,8 +332,12 @@ func formatPublishers(portMappings []cni.PortMapping) []PortPublisher {
}
var dockerPorts []PortPublisher
for _, p := range portMappings {
dockerPorts = append(dockerPorts, mapper(p))
if portMappings, err := portutil.ParsePortsLabel(labelMap); err == nil {
for _, p := range portMappings {
dockerPorts = append(dockerPorts, mapper(p))
}
} else {
log.L.Error(err.Error())
}
return dockerPorts
}

View File

@ -32,10 +32,14 @@ import (
func TestComposePs(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
container_name: wordpress_container
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
@ -60,7 +64,7 @@ services:
volumes:
wordpress:
db:
`, testutil.WordpressImage, testutil.MariaDBImage, testutil.CommonImage)
`, testutil.WordpressImage, testutil.MariaDBImage, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
@ -96,9 +100,9 @@ volumes:
time.Sleep(3 * time.Second)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutWithFunc(assertHandler("wordpress_container", testutil.WordpressImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutWithFunc(assertHandler("db_container", testutil.MariaDBImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps").AssertOutNotContains(testutil.CommonImage)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "alpine", "-a").AssertOutWithFunc(assertHandler("alpine_container", testutil.CommonImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "-a", "--filter", "status=exited").AssertOutWithFunc(assertHandler("alpine_container", testutil.CommonImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps").AssertOutNotContains(testutil.AlpineImage)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "alpine", "-a").AssertOutWithFunc(assertHandler("alpine_container", testutil.AlpineImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "-a", "--filter", "status=exited").AssertOutWithFunc(assertHandler("alpine_container", testutil.AlpineImage))
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "--services", "-a").AssertOutContainsAll("wordpress\n", "db\n", "alpine\n")
}
@ -108,6 +112,8 @@ func TestComposePsJSON(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s

View File

@ -26,10 +26,14 @@ import (
func TestComposePullWithService(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser

View File

@ -26,9 +26,13 @@ import (
func TestComposeRestart(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser

View File

@ -18,23 +18,23 @@ package compose
import (
"fmt"
"regexp"
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"time"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeRemove(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
@ -58,71 +58,27 @@ volumes:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)
testCase := nerdtest.Setup()
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
data.Labels().Set("yamlPath", data.Temp().Path("compose.yaml"))
}
testCase.SubTests = []*test.Case{
{
Description: "All services are still up",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "rm", "-f")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
wp := helpers.Capture("compose", "-f", data.Labels().Get("yamlPath"), "ps", "wordpress")
db := helpers.Capture("compose", "-f", data.Labels().Get("yamlPath"), "ps", "db")
comp := expect.Match(regexp.MustCompile("Up|running"))
comp(wp, t)
comp(db, t)
},
}
},
},
{
Description: "Remove stopped service",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure("compose", "-f", data.Labels().Get("yamlPath"), "stop", "wordpress")
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "rm", "-f")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
wp := helpers.Capture("compose", "-f", data.Labels().Get("yamlPath"), "ps", "wordpress")
db := helpers.Capture("compose", "-f", data.Labels().Get("yamlPath"), "ps", "db")
expect.DoesNotContain("wordpress")(wp, t)
expect.Match(regexp.MustCompile("Up|running"))(db, t)
},
}
},
},
{
Description: "Remove all services with stop",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "rm", "-f", "-s")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
db := helpers.Capture("compose", "-f", data.Labels().Get("yamlPath"), "ps", "db")
expect.DoesNotContain("db")(db, t)
},
}
},
},
}
testCase.Run(t)
// no stopped containers
base.ComposeCmd("-f", comp.YAMLFullPath(), "rm", "-f").AssertOK()
time.Sleep(3 * time.Second)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutContainsAny("Up", "running")
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutContainsAny("Up", "running")
// remove one stopped service
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "wordpress").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "rm", "-f", "wordpress").AssertOK()
time.Sleep(3 * time.Second)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutNotContains("wordpress")
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutContainsAny("Up", "running")
// remove all services with `--stop`
base.ComposeCmd("-f", comp.YAMLFullPath(), "rm", "-f", "-s").AssertOK()
time.Sleep(3 * time.Second)
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db").AssertOutNotContains("db")
}

View File

@ -40,12 +40,13 @@ func TestComposeRun(t *testing.T) {
const expectedOutput = "speed 38400 baud"
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
entrypoint:
- stty
`, testutil.CommonImage)
`, testutil.AlpineImage)
testCase := nerdtest.Setup()
@ -119,6 +120,7 @@ func TestComposeRunWithServicePorts(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
web:
image: %s
@ -142,7 +144,7 @@ services:
}()
checkNginx := func() error {
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false)
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 10, false)
if err != nil {
return err
}
@ -180,6 +182,7 @@ func TestComposeRunWithPublish(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
web:
image: %s
@ -201,7 +204,7 @@ services:
}()
checkNginx := func() error {
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false)
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 10, false)
if err != nil {
return err
}
@ -239,6 +242,7 @@ func TestComposeRunWithEnv(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
@ -246,7 +250,7 @@ services:
- sh
- -c
- "echo $$FOO"
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -270,13 +274,14 @@ func TestComposeRunWithUser(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
entrypoint:
- id
- -u
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -298,6 +303,7 @@ func TestComposeRunWithLabel(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
@ -306,7 +312,7 @@ services:
- "dummy log"
labels:
- "foo=bar"
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -335,12 +341,13 @@ func TestComposeRunWithArgs(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
entrypoint:
- echo
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -364,12 +371,13 @@ func TestComposeRunWithEntrypoint(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
entrypoint:
- stty # should be changed
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -391,12 +399,13 @@ func TestComposeRunWithVolume(t *testing.T) {
containerName := testutil.Identifier(t)
dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services:
alpine:
image: %s
entrypoint:
- stty # no meaning, just put any command
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -480,7 +489,7 @@ services:
`, imageSvc0, keyPair.PublicKey, keyPair.PrivateKey,
imageSvc1, keyPair.PrivateKey, imageSvc2)
dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)
dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()

View File

@ -18,19 +18,16 @@ package compose
import (
"fmt"
"regexp"
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeStart(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -40,68 +37,50 @@ services:
command: "sleep infinity"
`, testutil.CommonImage, testutil.CommonImage)
testCase := nerdtest.Setup()
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "start")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "stop", "--timeout", "1", "svc0")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "kill", "svc1")
}
// calling `compose start` after all services up has no effect.
base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertOK()
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "start")
}
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Errors: nil,
Output: func(stdout string, t tig.T) {
svc0 := helpers.Capture("compose", "-f", data.Temp().Path("compose.yaml"), "ps", "svc0")
svc1 := helpers.Capture("compose", "-f", data.Temp().Path("compose.yaml"), "ps", "svc1")
comp := expect.Match(regexp.MustCompile("Up|running"))
comp(svc0, t)
comp(svc1, t)
},
}
}
testCase.Run(t)
// `compose start`` can start a stopped/killed service container
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "--timeout", "1", "svc0").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "kill", "svc1").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0").AssertOutContainsAny("Up", "running")
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc1").AssertOutContainsAny("Up", "running")
}
func TestComposeStartFailWhenServicePause(t *testing.T) {
base := testutil.NewBase(t)
switch base.Info().CgroupDriver {
case "none", "":
t.Skip("requires cgroup (for pausing)")
}
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
testCase := nerdtest.Setup()
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Require = nerdtest.CGroup
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "pause", "svc0")
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "start")
}
testCase.Expected = test.Expects(expect.ExitCodeGenericFail, nil, nil)
testCase.Run(t)
// `compose start` cannot start a paused service container
base.ComposeCmd("-f", comp.YAMLFullPath(), "pause", "svc0").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertFail()
}

View File

@ -18,22 +18,22 @@ package compose
import (
"fmt"
"regexp"
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeStop(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
wordpress:
image: %s
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
@ -57,50 +57,21 @@ volumes:
db:
`, testutil.WordpressImage, testutil.MariaDBImage)
testCase := nerdtest.Setup()
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
data.Labels().Set("yamlPath", data.Temp().Path("compose.yaml"))
}
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
// stop should (only) stop the given service.
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "db").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "db", "-a").AssertOutContainsAny("Exit", "exited")
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress").AssertOutContainsAny("Up", "running")
testCase.SubTests = []*test.Case{
{
Description: "stop db",
NoParallel: true,
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("compose", "-f", data.Labels().Get("yamlPath"), "stop", "db")
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "ps", "db", "-a")
},
Expected: test.Expects(0, nil, expect.Match(regexp.MustCompile("Exit|exited"))),
},
{
Description: "wordpress is still running",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "ps", "wordpress")
},
Expected: test.Expects(0, nil, expect.Match(regexp.MustCompile("Up|running"))),
},
{
Description: "stop wordpress",
NoParallel: true,
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("compose", "-f", data.Labels().Get("yamlPath"), "stop", "--timeout", "5", "wordpress")
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "ps", "wordpress", "-a")
},
Expected: test.Expects(0, nil, expect.Match(regexp.MustCompile("Exit|exited"))),
},
}
// `--timeout` arg should work properly.
base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "--timeout", "5", "wordpress").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress", "-a").AssertOutContainsAny("Exit", "exited")
testCase.Run(t)
}

View File

@ -20,16 +20,20 @@ import (
"fmt"
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/infoutil"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestComposeTop(t *testing.T) {
if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" {
t.Skip("test skipped for rootless containers on cgroup v1")
}
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -38,36 +42,15 @@ services:
image: %s
`, testutil.CommonImage, testutil.NginxAlpineImage)
testCase := nerdtest.Setup()
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
testCase.Require = require.All(nerdtest.CgroupsAccessible)
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
data.Labels().Set("yamlPath", data.Temp().Path("compose.yaml"))
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
testCase.SubTests = []*test.Case{
{
Description: "svc0 contains sleep infinity",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "top", "svc0")
},
Expected: test.Expects(0, nil, expect.Contains("sleep infinity")),
},
{
Description: "svc1 contains sleep nginx",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("yamlPath"), "top", "svc1")
},
Expected: test.Expects(0, nil, expect.Contains("nginx")),
},
}
testCase.Run(t)
// a running container should have the process command in output
base.ComposeCmd("-f", comp.YAMLFullPath(), "top", "svc0").AssertOutContains("sleep infinity")
base.ComposeCmd("-f", comp.YAMLFullPath(), "top", "svc1").AssertOutContains("nginx")
}

View File

@ -29,20 +29,19 @@ import (
"gotest.tools/v3/icmd"
"github.com/containerd/log"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
)
func TestComposeUp(t *testing.T) {
base := testutil.NewBase(t)
helpers.ComposeUp(t, base, fmt.Sprintf(`
version: '3.1'
services:
wordpress:
@ -103,7 +102,7 @@ COPY index.html /usr/share/nginx/html/index.html
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "--build").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false)
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 50, false)
assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body)
assert.NilError(t, err)
@ -118,6 +117,8 @@ func TestComposeUpNetWithStaticIP(t *testing.T) {
base := testutil.NewBase(t)
staticIP := "172.20.0.12"
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -154,6 +155,8 @@ func TestComposeUpMultiNet(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc0:
image: %s
@ -201,6 +204,8 @@ func TestComposeUpOsEnvVar(t *testing.T) {
base := testutil.NewBase(t)
const containerName = "nginxAlpine"
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
svc1:
image: %s
@ -232,6 +237,8 @@ func TestComposeUpDotEnvFile(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = `
version: '3.1'
services:
svc3:
image: ghcr.io/stargz-containers/nginx:$TAG
@ -253,6 +260,8 @@ func TestComposeUpEnvFileNotFoundError(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = `
version: '3.1'
services:
svc4:
image: ghcr.io/stargz-containers/nginx:$TAG
@ -275,11 +284,13 @@ func TestComposeUpWithScale(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -296,6 +307,8 @@ func TestComposeIPAMConfig(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
foo:
image: %s
@ -306,7 +319,7 @@ networks:
ipam:
config:
- subnet: 10.1.100.0/24
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -324,18 +337,20 @@ func TestComposeUpRemoveOrphans(t *testing.T) {
var (
dockerComposeYAMLOrphan = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
`, testutil.AlpineImage)
dockerComposeYAMLFull = fmt.Sprintf(`
%s
orphan:
image: %s
command: "sleep infinity"
`, dockerComposeYAMLOrphan, testutil.CommonImage)
`, dockerComposeYAMLOrphan, testutil.AlpineImage)
)
compOrphan := testutil.NewComposeDir(t, dockerComposeYAMLOrphan)
@ -360,11 +375,13 @@ func TestComposeUpIdempotent(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.CommonImage)
`, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
@ -378,10 +395,11 @@ services:
}
func TestComposeUpWithExternalNetwork(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
var dockerComposeYaml1 = fmt.Sprintf(`
containerName1 := testutil.Identifier(t) + "-1"
containerName2 := testutil.Identifier(t) + "-2"
networkName := testutil.Identifier(t) + "-network"
var dockerComposeYaml1 = fmt.Sprintf(`
version: "3"
services:
%s:
image: %s
@ -393,8 +411,9 @@ services:
networks:
%s:
external: true
`, data.Identifier("con-1"), testutil.NginxAlpineImage, data.Identifier("con-1"), data.Identifier("network"), data.Identifier("network"))
var dockerComposeYaml2 = fmt.Sprintf(`
`, containerName1, testutil.NginxAlpineImage, containerName1, networkName, networkName)
var dockerComposeYaml2 = fmt.Sprintf(`
version: "3"
services:
%s:
image: %s
@ -406,34 +425,26 @@ services:
networks:
%s:
external: true
`, data.Identifier("con-2"), testutil.NginxAlpineImage, data.Identifier("con-2"), data.Identifier("network"), data.Identifier("network"))
tmp := data.Temp()
tmp.Save(dockerComposeYaml1, "project-1", "compose.yaml")
tmp.Save(dockerComposeYaml2, "project-2", "compose.yaml")
helpers.Ensure("network", "create", data.Identifier("network"))
helpers.Ensure("compose", "-f", tmp.Path("project-1", "compose.yaml"), "up", "-d")
helpers.Ensure("compose", "-f", tmp.Path("project-2", "compose.yaml"), "up", "-d")
helpers.Ensure("compose", "-f", tmp.Path("project-2", "compose.yaml"), "down", "-v")
helpers.Ensure("compose", "-f", tmp.Path("project-2", "compose.yaml"), "up", "-d")
nerdtest.EnsureContainerStarted(helpers, data.Identifier("con-2"))
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure("exec", data.Identifier("con-1"), "cat", "/etc/hosts")
return helpers.Command("exec", data.Identifier("con-1"), "wget", "-qO-", "http://"+data.Identifier("con-2"))
}
testCase.Expected = test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet))
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("project-1", "compose.yaml"), "down", "-v")
helpers.Anyhow("compose", "-f", data.Temp().Path("project-2", "compose.yaml"), "down", "-v")
helpers.Anyhow("network", "rm", data.Identifier("network"))
}
testCase.Run(t)
`, containerName2, testutil.NginxAlpineImage, containerName2, networkName, networkName)
comp1 := testutil.NewComposeDir(t, dockerComposeYaml1)
defer comp1.CleanUp()
comp2 := testutil.NewComposeDir(t, dockerComposeYaml2)
defer comp2.CleanUp()
base := testutil.NewBase(t)
// Create the test network
base.Cmd("network", "create", networkName).AssertOK()
defer base.Cmd("network", "rm", networkName).Run()
// Run the first compose
base.ComposeCmd("-f", comp1.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp1.YAMLFullPath(), "down", "-v").Run()
// Run the second compose
base.ComposeCmd("-f", comp2.YAMLFullPath(), "up", "-d").AssertOK()
defer base.ComposeCmd("-f", comp2.YAMLFullPath(), "down", "-v").Run()
// Down the second compose
base.ComposeCmd("-f", comp2.YAMLFullPath(), "down", "-v").AssertOK()
// Run the second compose again
base.ComposeCmd("-f", comp2.YAMLFullPath(), "up", "-d").AssertOK()
base.Cmd("exec", containerName1, "wget", "-qO-", "http://"+containerName2).AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet)
}
func TestComposeUpWithBypass4netns(t *testing.T) {
@ -446,6 +457,8 @@ func TestComposeUpWithBypass4netns(t *testing.T) {
testutil.RequireSystemService(t, "bypass4netnsd")
base := testutil.NewBase(t)
helpers.ComposeUp(t, base, fmt.Sprintf(`
version: '3.1'
services:
wordpress:
@ -544,6 +557,8 @@ func TestComposeUpAbortOnContainerExit(t *testing.T) {
services:
%s:
image: %s
ports:
- 8080:80
%s:
image: %s
entrypoint: /bin/sh -c "exit 1"

View File

@ -17,15 +17,14 @@
package compose
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"gotest.tools/v3/icmd"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -33,73 +32,56 @@ import (
// https://github.com/containerd/nerdctl/issues/1942
func TestComposeUpDetailedError(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("FIXME: test does not work on Windows yet (runtime \"io.containerd.runc.v2\" binary not installed \"containerd-shim-runc-v2.exe\": file does not exist)")
}
base := testutil.NewBase(t)
dockerComposeYAML := fmt.Sprintf(`
services:
foo:
image: %s
runtime: invalid
`, testutil.CommonImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
testCase := nerdtest.Setup()
// "FIXME: test does not work on Windows yet (runtime \"io.containerd.runc.v2\" binary not installed \"containerd-shim-runc-v2.exe\": file does not exist)
testCase.Require = require.Not(require.Windows)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
c := base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d")
expected := icmd.Expected{
ExitCode: 1,
Err: `exec: \"invalid\": executable file not found in $PATH`,
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d")
// Docker expected err is different
if nerdtest.IsDocker() {
expected.Err = `unknown or invalid runtime name: invalid`
}
testCase.Expected = test.Expects(
1,
[]error{errors.New(`invalid runtime name`)},
nil,
)
testCase.Run(t)
c.Assert(expected)
}
// https://github.com/containerd/nerdctl/issues/1652
func TestComposeUpBindCreateHostPath(t *testing.T) {
testCase := nerdtest.Setup()
if runtime.GOOS == "windows" {
t.Skip(`FIXME: no support for Windows path: (error: "volume target must be an absolute path, got \"/mnt\")`)
}
// `FIXME: no support for Windows path: (error: "volume target must be an absolute path, got \"/mnt\")`
testCase.Require = require.Not(require.Windows)
base := testutil.NewBase(t)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
var dockerComposeYAML = fmt.Sprintf(`
var dockerComposeYAML = fmt.Sprintf(`
services:
test:
image: %s
command: sh -euxc "echo hi >/mnt/test"
volumes:
# tempdir/foo should be automatically created
- %s:/mnt
`, testutil.CommonImage, data.Temp().Path("foo"))
# ./foo should be automatically created
- ./foo:/mnt
`, testutil.CommonImage)
data.Temp().Save(dockerComposeYAML, "compose.yaml")
}
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "up")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down")
}
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Errors: nil,
Output: func(stdout string, t tig.T) {
assert.Equal(t, data.Temp().Load("foo", "test"), "hi\n")
},
}
}
testCase.Run(t)
base.ComposeCmd("-f", comp.YAMLFullPath(), "up").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK()
testFile := filepath.Join(comp.Dir(), "foo", "test")
testB, err := os.ReadFile(testFile)
assert.NilError(t, err)
assert.Equal(t, "hi\n", string(testB))
}

View File

@ -19,29 +19,20 @@ package compose
import (
"testing"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil"
)
func TestComposeVersion(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Command = test.Command("compose", "version")
testCase.Expected = test.Expects(0, nil, expect.Contains("Compose version "))
testCase.Run(t)
base := testutil.NewBase(t)
base.ComposeCmd("version").AssertOutContains("Compose version ")
}
func TestComposeVersionShort(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Command = test.Command("compose", "version", "--short")
testCase.Expected = test.Expects(0, nil, nil)
testCase.Run(t)
base := testutil.NewBase(t)
base.ComposeCmd("version", "--short").AssertOK()
}
func TestComposeVersionJson(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Command = test.Command("compose", "version", "--format", "json")
testCase.Expected = test.Expects(0, nil, expect.Contains("{\"version\":\""))
testCase.Run(t)
base := testutil.NewBase(t)
base.ComposeCmd("version", "--format", "json").AssertOutContains("{\"version\":\"")
}

View File

@ -54,7 +54,6 @@ func Command() *cobra.Command {
pruneCommand(),
StatsCommand(),
AttachCommand(),
HealthCheckCommand(),
)
AddCpCommand(cmd)
return cmd

View File

@ -17,8 +17,6 @@
package container
import (
"io"
"github.com/spf13/cobra"
containerd "github.com/containerd/containerd/v2/client"
@ -58,7 +56,6 @@ Caveats:
SilenceErrors: true,
}
cmd.Flags().String("detach-keys", consoleutil.DefaultDetachKeys, "Override the default detach keys")
cmd.Flags().Bool("no-stdin", false, "Do not attach STDIN")
return cmd
}
@ -71,18 +68,9 @@ func attachOptions(cmd *cobra.Command) (types.ContainerAttachOptions, error) {
if err != nil {
return types.ContainerAttachOptions{}, err
}
noStdin, err := cmd.Flags().GetBool("no-stdin")
if err != nil {
return types.ContainerAttachOptions{}, err
}
var stdin io.Reader
if !noStdin {
stdin = cmd.InOrStdin()
}
return types.ContainerAttachOptions{
GOptions: globalOptions,
Stdin: stdin,
Stdin: cmd.InOrStdin(),
Stdout: cmd.OutOrStdout(),
Stderr: cmd.ErrOrStderr(),
DetachKeys: detachKeys,

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -65,7 +64,7 @@ func TestAttach(t *testing.T) {
cmd.Run(&test.Expected{
ExitCode: 0,
Errors: []error{errors.New("read detach keys")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
})
@ -94,7 +93,7 @@ func TestAttach(t *testing.T) {
Errors: []error{errors.New("read detach keys")},
Output: expect.All(
expect.Contains("markmark"),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
),
@ -126,7 +125,7 @@ func TestAttachDetachKeys(t *testing.T) {
cmd.Run(&test.Expected{
ExitCode: 0,
Errors: []error{errors.New("read detach keys")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
})
@ -154,7 +153,7 @@ func TestAttachDetachKeys(t *testing.T) {
Errors: []error{errors.New("read detach keys")},
Output: expect.All(
expect.Contains("markmark"),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
),
@ -183,8 +182,8 @@ func TestAttachForAutoRemovedContainer(t *testing.T) {
cmd.Run(&test.Expected{
ExitCode: 0,
Errors: []error{errors.New("read detach keys")},
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
Output: func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"), info)
},
})
}
@ -203,7 +202,7 @@ func TestAttachForAutoRemovedContainer(t *testing.T) {
ExitCode: 42,
Output: expect.All(
expect.Contains("markmark"),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(helpers.Capture("ps", "-a"), data.Identifier()))
},
),
@ -212,44 +211,3 @@ func TestAttachForAutoRemovedContainer(t *testing.T) {
testCase.Run(t)
}
func TestAttachNoStdin(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
cmd := helpers.Command("run", "-it", "--detach-keys=ctrl-p,ctrl-q", "--name", data.Identifier(),
testutil.CommonImage, "sleep", "5")
cmd.WithPseudoTTY()
cmd.Feed(bytes.NewReader([]byte{16, 17})) // Ctrl-p, Ctrl-q to detach (https://en.wikipedia.org/wiki/C0_and_C1_control_codes)
cmd.Run(&test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "{{.State.Running}}", data.Identifier()), "true"))
},
})
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.Command("attach", "--no-stdin", data.Identifier())
cmd.WithPseudoTTY()
cmd.Feed(strings.NewReader("should-not-appear\n"))
cmd.Feed(bytes.NewReader([]byte{16, 17}))
return cmd
}
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0, // Since it's a normal exit and not detach.
Output: func(stdout string, t tig.T) {
logs := helpers.Capture("logs", data.Identifier())
assert.Assert(t, !strings.Contains(logs, "should-not-appear"))
},
}
}
testCase.Run(t)
}

View File

@ -17,8 +17,6 @@
package container
import (
"errors"
"github.com/spf13/cobra"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
@ -42,15 +40,6 @@ func CommitCommand() *cobra.Command {
cmd.Flags().StringP("message", "m", "", "Commit message")
cmd.Flags().StringArrayP("change", "c", nil, "Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])")
cmd.Flags().BoolP("pause", "p", true, "Pause container during commit")
cmd.Flags().StringP("compression", "", "gzip", "commit compression algorithm (zstd or gzip)")
cmd.Flags().String("format", "docker", "Format of the committed image (docker or oci)")
cmd.Flags().Bool("estargz", false, "Convert the committed layer to eStargz for lazy pulling")
cmd.Flags().Int("estargz-compression-level", 9, "eStargz compression level (1-9)")
cmd.Flags().Int("estargz-chunk-size", 0, "eStargz chunk size")
cmd.Flags().Int("estargz-min-chunk-size", 0, "The minimal number of bytes of data must be written in one gzip stream")
cmd.Flags().Bool("zstdchunked", false, "Convert the committed layer to zstd:chunked for lazy pulling")
cmd.Flags().Int("zstdchunked-compression-level", 3, "zstd:chunked compression level")
cmd.Flags().Int("zstdchunked-chunk-size", 0, "zstd:chunked chunk size")
return cmd
}
@ -77,78 +66,15 @@ func commitOptions(cmd *cobra.Command) (types.ContainerCommitOptions, error) {
return types.ContainerCommitOptions{}, err
}
com, err := cmd.Flags().GetString("compression")
if err != nil {
return types.ContainerCommitOptions{}, err
}
if com != string(types.Zstd) && com != string(types.Gzip) {
return types.ContainerCommitOptions{}, errors.New("--compression param only supports zstd or gzip")
}
format, err := cmd.Flags().GetString("format")
if err != nil {
return types.ContainerCommitOptions{}, err
}
if format != string(types.ImageFormatDocker) && format != string(types.ImageFormatOCI) {
return types.ContainerCommitOptions{}, errors.New("--format param only supports docker or oci")
}
estargz, err := cmd.Flags().GetBool("estargz")
if err != nil {
return types.ContainerCommitOptions{}, err
}
estargzCompressionLevel, err := cmd.Flags().GetInt("estargz-compression-level")
if err != nil {
return types.ContainerCommitOptions{}, err
}
estargzChunkSize, err := cmd.Flags().GetInt("estargz-chunk-size")
if err != nil {
return types.ContainerCommitOptions{}, err
}
estargzMinChunkSize, err := cmd.Flags().GetInt("estargz-min-chunk-size")
if err != nil {
return types.ContainerCommitOptions{}, err
}
zstdchunked, err := cmd.Flags().GetBool("zstdchunked")
if err != nil {
return types.ContainerCommitOptions{}, err
}
zstdchunkedCompressionLevel, err := cmd.Flags().GetInt("zstdchunked-compression-level")
if err != nil {
return types.ContainerCommitOptions{}, err
}
zstdchunkedChunkSize, err := cmd.Flags().GetInt("zstdchunked-chunk-size")
if err != nil {
return types.ContainerCommitOptions{}, err
}
// estargz and zstdchunked are mutually exclusive
if estargz && zstdchunked {
return types.ContainerCommitOptions{}, errors.New("options --estargz and --zstdchunked lead to conflict, only one of them can be used")
}
return types.ContainerCommitOptions{
Stdout: cmd.OutOrStdout(),
GOptions: globalOptions,
Author: author,
Message: message,
Pause: pause,
Change: change,
Compression: types.CompressionType(com),
Format: types.ImageFormat(format),
EstargzOptions: types.EstargzOptions{
Estargz: estargz,
EstargzCompressionLevel: estargzCompressionLevel,
EstargzChunkSize: estargzChunkSize,
EstargzMinChunkSize: estargzMinChunkSize,
},
ZstdChunkedOptions: types.ZstdChunkedOptions{
ZstdChunked: zstdchunked,
ZstdChunkedCompressionLevel: zstdchunkedCompressionLevel,
ZstdChunkedChunkSize: zstdchunkedChunkSize,
},
Stdout: cmd.OutOrStdout(),
GOptions: globalOptions,
Author: author,
Message: message,
Pause: pause,
Change: change,
}, nil
}
func commitAction(cmd *cobra.Command, args []string) error {
@ -156,6 +82,7 @@ func commitAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
if err != nil {
return err

View File

@ -19,10 +19,8 @@ package container
import (
"strings"
"testing"
"time"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -43,7 +41,7 @@ func TestKubeCommitSave(t *testing.T) {
nerdtest.KubeCtlCommand(helpers, "wait", "pod", identifier, "--for=condition=ready", "--timeout=1m").Run(&test.Expected{})
nerdtest.KubeCtlCommand(helpers, "exec", identifier, "--", "mkdir", "-p", "/tmp/whatever").Run(&test.Expected{})
nerdtest.KubeCtlCommand(helpers, "get", "pods", identifier, "-o", "jsonpath={ .status.containerStatuses[0].containerID }").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
containerID = strings.TrimPrefix(stdout, "containerd://")
},
})
@ -55,22 +53,8 @@ func TestKubeCommitSave(t *testing.T) {
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure("commit", data.Labels().Get("containerID"), data.Identifier("testcommitsave"))
// Wait for the image to show up
for range 5 {
found := false
cmd := helpers.Command("images", data.Identifier("testcommitsave"), "--format", "json")
cmd.Run(&test.Expected{
Output: func(stdout string, t tig.T) {
found = strings.TrimSpace(stdout) != ""
},
})
if found {
break
}
time.Sleep(1 * time.Second)
}
return helpers.Command("save", data.Identifier("testcommitsave"))
helpers.Ensure("commit", data.Labels().Get("containerID"), "testcommitsave")
return helpers.Command("save", "testcommitsave")
}
testCase.Expected = test.Expects(0, nil, nil)
@ -89,7 +73,7 @@ func TestKubeCommitSave(t *testing.T) {
cmd = nerdtest.KubeCtlCommand(helpers, "get", "pods", tID, "-o", "jsonpath={ .status.hostIPs[0].ip }")
cmd.Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
registryIP = stdout
},
})

View File

@ -19,14 +19,10 @@ package container
import (
"testing"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
@ -90,52 +86,3 @@ func TestCommit(t *testing.T) {
testCase.Run(t)
}
func TestZstdCommit(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Require = require.All(
// FIXME: Docker does not support compression
require.Not(nerdtest.Docker),
nerdtest.ContainerdVersion("2.0.0"),
nerdtest.CGroup,
)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
helpers.Anyhow("rmi", "-f", data.Identifier("image"))
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
identifier := data.Identifier()
helpers.Ensure("run", "-d", "--name", identifier, testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, identifier)
helpers.Ensure("exec", identifier, "sh", "-euxc", `echo hello-test-commit > /foo`)
helpers.Ensure("commit", identifier, data.Identifier("image"), "--compression=zstd")
data.Labels().Set("image", data.Identifier("image"))
}
testCase.SubTests = []*test.Case{
{
Description: "verify zstd has been used",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("image", "inspect", "--mode=native", data.Labels().Get("image"))
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.JSON([]native.Image{}, func(images []native.Image, t tig.T) {
assert.Equal(t, len(images), 1)
assert.Equal(helpers.T(), images[0].Manifest.Layers[len(images[0].Manifest.Layers)-1].MediaType, "application/vnd.docker.image.rootfs.diff.tar.zstd")
}),
}
},
},
{
Description: "verify the image is working",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", data.Labels().Get("image"), "sh", "-c", "--", "cat /foo")
},
Expected: test.Expects(0, nil, expect.Equals("hello-test-commit\n")),
},
}
testCase.Run(t)
}

View File

@ -258,40 +258,6 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
}
// #endregion
// #region for healthcheck flags
opt.HealthCmd, err = cmd.Flags().GetString("health-cmd")
if err != nil {
return opt, err
}
opt.HealthInterval, err = cmd.Flags().GetDuration("health-interval")
if err != nil {
return opt, err
}
opt.HealthTimeout, err = cmd.Flags().GetDuration("health-timeout")
if err != nil {
return opt, err
}
opt.HealthRetries, err = cmd.Flags().GetInt("health-retries")
if err != nil {
return opt, err
}
opt.HealthStartPeriod, err = cmd.Flags().GetDuration("health-start-period")
if err != nil {
return opt, err
}
opt.HealthStartInterval, err = cmd.Flags().GetDuration("health-start-interval")
if err != nil {
return opt, err
}
opt.NoHealthcheck, err = cmd.Flags().GetBool("no-healthcheck")
if err != nil {
return opt, err
}
if err := helpers.ValidateHealthcheckFlags(opt); err != nil {
return opt, err
}
// #endregion
// #region for intel RDT flags
opt.RDTClass, err = cmd.Flags().GetString("rdt-class")
if err != nil {
@ -405,6 +371,7 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
// #endregion
// #region for metadata flags
opt.NameChanged = cmd.Flags().Changed("name")
opt.Name, err = cmd.Flags().GetString("name")
if err != nil {
return opt, err
@ -539,7 +506,7 @@ func createAction(cmd *cobra.Command, args []string) error {
}
defer cancel()
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)
netFlags, err := loadNetworkFlags(cmd)
if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err)
}

View File

@ -34,7 +34,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -236,7 +235,7 @@ func TestIssue2993(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("is already used by ID")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))
assert.NilError(t, err)
assert.Equal(t, len(containersDirs), 1)
@ -283,7 +282,7 @@ func TestIssue2993(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))
assert.NilError(t, err)
assert.Equal(t, len(containersDirs), 0)
@ -364,10 +363,10 @@ func TestUsernsMappingCreateCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
assert.NilError(t, err, "Failed to get container host UID")
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
},
}
},

View File

@ -26,7 +26,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -97,13 +96,13 @@ func TestCreateHyperVContainer(t *testing.T) {
helpers.Command("container", "inspect", data.Labels().Get("cID")).
Run(&test.Expected{
ExitCode: expect.ExitCodeNoCheck,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Container
err := json.Unmarshal([]byte(stdout), &dc)
if err != nil || len(dc) == 0 {
return
}
assert.Equal(t, len(dc), 1, "Unexpectedly got multiple results\n")
assert.Equal(t, len(dc), 1, "Unexpectedly got multiple results\n"+info)
ran = dc[0].State.Status == "exited"
},
})

View File

@ -62,27 +62,27 @@ func execOptions(cmd *cobra.Command) (types.ContainerExecOptions, error) {
return types.ContainerExecOptions{}, err
}
isInteractive, err := cmd.Flags().GetBool("interactive")
flagI, err := cmd.Flags().GetBool("interactive")
if err != nil {
return types.ContainerExecOptions{}, err
}
isTerminal, err := cmd.Flags().GetBool("tty")
flagT, err := cmd.Flags().GetBool("tty")
if err != nil {
return types.ContainerExecOptions{}, err
}
isDetach, err := cmd.Flags().GetBool("detach")
flagD, err := cmd.Flags().GetBool("detach")
if err != nil {
return types.ContainerExecOptions{}, err
}
if isInteractive {
if isDetach {
if flagI {
if flagD {
return types.ContainerExecOptions{}, errors.New("currently flag -i and -d cannot be specified together (FIXME)")
}
}
if isTerminal {
if isDetach {
if flagT {
if flagD {
return types.ContainerExecOptions{}, errors.New("currently flag -t and -d cannot be specified together (FIXME)")
}
}
@ -111,9 +111,9 @@ func execOptions(cmd *cobra.Command) (types.ContainerExecOptions, error) {
return types.ContainerExecOptions{
GOptions: globalOptions,
TTY: isTerminal,
Interactive: isInteractive,
Detach: isDetach,
TTY: flagT,
Interactive: flagI,
Detach: flagD,
Workdir: workdir,
Env: env,
EnvFile: envFile,

View File

@ -65,9 +65,6 @@ func TestExecTTY(t *testing.T) {
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
data.Labels().Set("container_name", data.Identifier())
}

View File

@ -1,85 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"context"
"fmt"
"github.com/spf13/cobra"
containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/cmd/container"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
)
// HealthCheckCommand returns a cobra command for `nerdctl container healthcheck`
func HealthCheckCommand() *cobra.Command {
var healthCheckCommand = &cobra.Command{
Use: "healthcheck [flags] CONTAINER",
Short: "Execute the health check command in a container",
Args: cobra.ExactArgs(1),
RunE: healthCheckAction,
ValidArgsFunction: healthCheckShellComplete,
SilenceUsage: true,
SilenceErrors: true,
}
return healthCheckCommand
}
func healthCheckAction(cmd *cobra.Command, args []string) error {
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
if err != nil {
return err
}
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)
if err != nil {
return err
}
defer cancel()
containerID := args[0]
walker := &containerwalker.ContainerWalker{
Client: client,
OnFound: func(ctx context.Context, found containerwalker.Found) error {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
}
return container.HealthCheck(ctx, client, found.Container)
},
}
n, err := walker.Walk(ctx, containerID)
if err != nil {
return err
} else if n == 0 {
return fmt.Errorf("no such container %s", containerID)
}
return nil
}
func healthCheckShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completion.ContainerNames(cmd, func(status containerd.ProcessStatus) bool {
return status == containerd.Running
})
}

View File

@ -1,604 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestContainerHealthCheckBasic(t *testing.T) {
testCase := nerdtest.Setup()
// Docker CLI does not provide a standalone healthcheck command.
testCase.Require = require.Not(nerdtest.Docker)
testCase.SubTests = []*test.Case{
{
Description: "Container does not exist",
Command: test.Command("container", "healthcheck", "non-existent"),
Expected: test.Expects(1, []error{errors.New("no such container non-existent")}, nil),
},
{
Description: "Missing health check config",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: test.Expects(1, []error{errors.New("container has no health check configured")}, nil),
},
{
Description: "Basic health check success",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "echo healthy",
"--health-interval", "45s",
"--health-timeout", "30s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state to be present")
assert.Equal(t, healthcheck.Healthy, h.Status)
assert.Equal(t, 0, h.FailingStreak)
assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
}),
}
},
},
{
Description: "Health check on stopped container",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "echo healthy",
"--health-interval", "3s",
testutil.CommonImage, "sleep", "2")
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
helpers.Ensure("stop", data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: test.Expects(1, []error{errors.New("container is not running (status: stopped)")}, nil),
},
{
Description: "Health check without task",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("create", "--name", data.Identifier(),
"--health-cmd", "echo healthy",
testutil.CommonImage, "sleep", nerdtest.Infinity)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: test.Expects(1, []error{errors.New("failed to get container task: no running task found")}, nil),
},
}
testCase.Run(t)
}
func TestContainerHealthCheckAdvance(t *testing.T) {
testCase := nerdtest.Setup()
// Docker CLI does not provide a standalone healthcheck command.
testCase.Require = require.Not(nerdtest.Docker)
testCase.SubTests = []*test.Case{
{
Description: "Health check timeout scenario",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "sleep 10",
"--health-timeout", "2s",
"--health-interval", "1s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.FailingStreak, 1)
assert.Assert(t, len(inspect.State.Health.Log) > 0, "expected health log to have entries")
last := inspect.State.Health.Log[0]
assert.Equal(t, -1, last.ExitCode)
}),
}
},
},
{
Description: "Health check failing streak behavior",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "exit 1",
"--health-interval", "1s",
"--health-retries", "2",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
// Run healthcheck twice to ensure failing streak
for i := 0; i < 2; i++ {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(2 * time.Second)
}
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Unhealthy)
assert.Equal(t, h.FailingStreak, 2)
}),
}
},
},
{
Description: "Health check with start period",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "exit 1",
"--health-interval", "1s",
"--health-start-period", "60s",
"--health-retries", "2",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Starting)
assert.Equal(t, h.FailingStreak, 0)
}),
}
},
},
{
Description: "Health check with invalid command",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "not-a-real-cmd",
"--health-interval", "1s",
"--health-retries", "1",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Unhealthy)
assert.Equal(t, h.FailingStreak, 1)
}),
}
},
},
{
Description: "No healthcheck flag disables health status",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--no-healthcheck", testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
assert.Assert(t, inspect.State.Health == nil, "expected health to be nil with --no-healthcheck")
}),
}
},
},
{
Description: "Healthcheck using CMD-SHELL format",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "echo shell-format", "--health-interval", "1s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(_ string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy)
assert.Assert(t, len(h.Log) > 0)
assert.Assert(t, strings.Contains(h.Log[0].Output, "shell-format"))
}),
}
},
},
{
Description: "Health check uses container environment variables",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--env", "MYVAR=test-value",
"--health-cmd", "echo $MYVAR",
"--health-interval", "1s",
"--health-timeout", "1s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy)
assert.Assert(t, h.FailingStreak == 0)
assert.Assert(t, strings.Contains(h.Log[0].Output, "test"), "expected health log output to contain 'test'")
}),
}
},
},
{
Description: "Health check respects container WorkingDir",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--workdir", "/tmp",
"--health-cmd", "pwd",
"--health-interval", "1s",
"--health-timeout", "1s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy)
assert.Equal(t, h.FailingStreak, 0)
assert.Assert(t, strings.Contains(h.Log[0].Output, "/tmp"), "expected health log output to contain '/tmp'")
}),
}
},
},
{
Description: "Healthcheck emits large output repeatedly",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "yes X | head -c 60000",
"--health-interval", "1s", "--health-timeout", "2s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
for i := 0; i < 3; i++ {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(2 * time.Second)
}
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(_ string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy)
assert.Assert(t, len(h.Log) >= 3, "expected at least 3 health log entries")
for _, log := range h.Log {
assert.Assert(t, len(log.Output) >= 1024, fmt.Sprintf("each output should be >= 1024 bytes, was: %s", log.Output))
}
}),
}
},
},
{
Description: "Health log in inspect keeps only the latest 5 entries",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "exit 1",
"--health-interval", "1s",
"--health-retries", "1",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
for i := 0; i < 7; i++ {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(1 * time.Second)
}
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(_ string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Unhealthy)
assert.Assert(t, len(h.Log) <= 5, "expected health log to contain at most 5 entries")
}),
}
},
},
{
Description: "Healthcheck with large output gets truncated in health log",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "yes X | head -c 1048576", // 1MB output
"--health-interval", "1s", "--health-timeout", "2s",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("container", "healthcheck", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(_ string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy)
assert.Equal(t, h.FailingStreak, 0)
assert.Assert(t, len(h.Log) == 1, "expected one log entry")
output := h.Log[0].Output
assert.Assert(t, strings.HasSuffix(output, "[truncated]"), "expected output to be truncated with '[truncated]'")
}),
}
},
},
{
Description: "Health status transitions from healthy to unhealthy after retries",
Setup: func(data test.Data, helpers test.Helpers) {
containerName := data.Identifier()
helpers.Ensure("run", "-d", "--name", containerName,
"--health-cmd", "exit 1",
"--health-timeout", "10s",
"--health-retries", "3",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
for i := 0; i < 4; i++ {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(2 * time.Second)
}
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Unhealthy)
assert.Assert(t, h.FailingStreak >= 3)
}),
}
},
},
{
Description: "Failed healthchecks in start-period do not change status",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "ls /foo || exit 1", "--health-retries", "2",
"--health-start-period", "30s", // long enough to stay in "starting"
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
// Run healthcheck 3 times (should still be in start period)
for i := 0; i < 3; i++ {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(1 * time.Second)
}
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Starting)
assert.Equal(t, h.FailingStreak, 0, "failing streak should not increase during start period")
}),
}
},
},
{
Description: "Successful healthcheck in start-period sets status to healthy",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(),
"--health-cmd", "ls || exit 1", "--health-retries", "2",
testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure("container", "healthcheck", data.Identifier())
time.Sleep(1 * time.Second)
return helpers.Command("inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
h := inspect.State.Health
debug, _ := json.MarshalIndent(h, "", " ")
t.Log(string(debug))
assert.Assert(t, h != nil, "expected health state")
assert.Equal(t, h.Status, healthcheck.Healthy, "expected healthy status even during start-period")
assert.Equal(t, h.FailingStreak, 0)
}),
}
},
},
}
testCase.Run(t)
}

View File

@ -20,6 +20,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"testing"
@ -534,7 +535,8 @@ RUN groupadd -r test && useradd -r -g test test
USER test
`, testutil.UbuntuImage)
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(data.Temp().Path(), "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
helpers.Ensure("build", "-t", data.Identifier(), data.Temp().Path())
helpers.Ensure("create", "--name", data.Identifier(), "--user", "test", data.Identifier())

View File

@ -27,7 +27,6 @@ import (
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/formatter"
"github.com/containerd/nerdctl/v2/pkg/strutil"
@ -305,42 +304,6 @@ func TestContainerListWithFilter(t *testing.T) {
return nil
})
// should support regexp
base.Cmd("ps", "--filter", "name=.*"+testContainerA.name+".*").AssertOutWithFunc(func(stdout string) error {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) < 2 {
return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
}
tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
err := tab.ParseHeader(lines[0])
if err != nil {
return fmt.Errorf("failed to parse header: %v", err)
}
containerName, _ := tab.ReadRow(lines[1], "NAMES")
assert.Equal(t, containerName, testContainerA.name)
return nil
})
// fully anchored regexp
base.Cmd("ps", "--filter", "name=^"+testContainerA.name+"$").AssertOutWithFunc(func(stdout string) error {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) < 2 {
return fmt.Errorf("expected at least 2 lines, got %d", len(lines))
}
tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
err := tab.ParseHeader(lines[0])
if err != nil {
return fmt.Errorf("failed to parse header: %v", err)
}
containerName, _ := tab.ReadRow(lines[1], "NAMES")
assert.Equal(t, containerName, testContainerA.name)
return nil
})
base.Cmd("ps", "-q", "--filter", "name="+testContainerA.name+testContainerA.name).AssertOutWithFunc(func(stdout string) error {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) > 0 {
@ -689,7 +652,7 @@ func TestContainerListStatusFilter(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
assert.Assert(t, strings.Contains(stdout, data.Labels().Get("cID")), "No container found with status created")
},
}

View File

@ -19,7 +19,8 @@ package container
import (
"errors"
"fmt"
"regexp"
"io"
"os/exec"
"runtime"
"strconv"
"strings"
@ -27,97 +28,52 @@ import (
"time"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
func TestLogs(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
const expected = `foo
bar
`
bar`
testCase := nerdtest.Setup()
defer base.Cmd("rm", containerName).Run()
base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar").AssertOK()
if runtime.GOOS == "windows" {
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237")
}
//test since / until flag
time.Sleep(3 * time.Second)
base.Cmd("logs", "--since", "1s", containerName).AssertOutNotContains(expected)
base.Cmd("logs", "--since", "10s", containerName).AssertOutContains(expected)
base.Cmd("logs", "--until", "10s", containerName).AssertOutNotContains(expected)
base.Cmd("logs", "--until", "1s", containerName).AssertOutContains(expected)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
// Ensure follow flag works as expected:
base.Cmd("logs", "-f", containerName).AssertOutContains("bar")
base.Cmd("logs", "-f", containerName).AssertOutContains("foo")
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--quiet", "--name", data.Identifier(), testutil.CommonImage, "sh", "-euxc", "echo foo; echo bar;")
data.Labels().Set("cID", data.Identifier())
}
//test timestamps flag
base.Cmd("logs", "-t", containerName).AssertOutContains(time.Now().UTC().Format("2006-01-02"))
testCase.SubTests = []*test.Case{
{
Description: "since 1s",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "--since", "1s", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.DoesNotContain(expected)),
},
{
Description: "since 60s",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "--since", "60s", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals(expected)),
},
{
Description: "until 60s",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "--until", "60s", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.DoesNotContain(expected)),
},
{
Description: "until 1s",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "--until", "1s", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals(expected)),
},
{
Description: "follow",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-f", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals(expected)),
},
{
Description: "timestamp",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-t", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Contains(time.Now().UTC().Format("2006-01-02"))),
},
{
Description: "tail flag",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-n", "all", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals(expected)),
},
{
Description: "tail flag",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-n", "1", data.Labels().Get("cID"))
},
// FIXME: why?
Expected: test.Expects(0, nil, expect.Match(regexp.MustCompile("^(?:bar\n|)$"))),
},
}
//test tail flag
base.Cmd("logs", "-n", "all", containerName).AssertOutContains(expected)
testCase.Run(t)
base.Cmd("logs", "-n", "1", containerName).AssertOutWithFunc(func(stdout string) error {
if !(stdout == "bar\n" || stdout == "") {
return fmt.Errorf("expected %q or %q, got %q", "bar", "", stdout)
}
return nil
})
base.Cmd("rm", "-f", containerName).AssertOK()
}
// Tests whether `nerdctl logs` properly separates stdout/stderr output
@ -125,13 +81,8 @@ bar
func TestLogsOutStreamsSeparated(t *testing.T) {
testCase := nerdtest.Setup()
if runtime.GOOS == "windows" {
// Logging seems broken on windows.
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237")
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage,
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage,
"sh", "-euc", "echo stdout1; echo stderr1 >&2; echo stdout2; echo stderr2 >&2")
}
@ -140,6 +91,8 @@ func TestLogsOutStreamsSeparated(t *testing.T) {
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
// Arbitrary, but we need to wait until the logs show up
time.Sleep(3 * time.Second)
return helpers.Command("logs", data.Identifier())
}
@ -152,165 +105,116 @@ func TestLogsOutStreamsSeparated(t *testing.T) {
}
func TestLogsWithInheritedFlags(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Require = require.Not(nerdtest.Docker)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("-n="+testutil.Namespace, "run", "--name", data.Identifier(), testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar")
// Seen flaky with Docker
t.Parallel()
base := testutil.NewBase(t)
for k, v := range base.Args {
if strings.HasPrefix(v, "--namespace=") {
base.Args[k] = "-n=" + testutil.Namespace
}
}
containerName := testutil.Identifier(t)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
defer base.Cmd("rm", containerName).Run()
base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar").AssertOK()
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("-n="+testutil.Namespace, "logs", "-n", "1", data.Identifier())
}
// FIXME: why?
testCase.Expected = test.Expects(0, nil, expect.Match(regexp.MustCompile("^(?:bar\n|)$")))
testCase.Run(t)
// It appears this test flakes out with Docker seeing only "foo\n"
// Tentatively adding a pause in case this is just slow
time.Sleep(time.Second)
// test rootCmd alias `-n` already used in logs subcommand
base.Cmd("logs", "-n", "1", containerName).AssertOutWithFunc(func(stdout string) error {
if !(stdout == "bar\n" || stdout == "") {
return fmt.Errorf("expected %q or %q, got %q", "bar", "", stdout)
}
return nil
})
}
func TestLogsOfJournaldDriver(t *testing.T) {
const expected = `foo
bar
`
testCase := nerdtest.Setup()
testCase.Require = require.All(
require.Binary("journalctl"),
&test.Requirement{
Check: func(data test.Data, helpers test.Helpers) (bool, string) {
works := false
cmd := helpers.Custom("journalctl", "-xe")
cmd.Run(&test.Expected{
ExitCode: expect.ExitCodeNoCheck,
Output: func(stdout string, t tig.T) {
if stdout != "" {
works = true
}
},
})
return works, "Journactl to return data for the current user"
},
},
)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
testutil.RequireExecutable(t, "journalctl")
journalctl, _ := exec.LookPath("journalctl")
res := icmd.RunCmd(icmd.Command(journalctl, "-xe"))
if res.ExitCode != 0 {
t.Skipf("current user is not allowed to access journal logs: %s", res.Combined())
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--network", "none", "--log-driver", "journald", "--name", data.Identifier(), testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar")
data.Labels().Set("cID", data.Identifier())
}
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
testCase.SubTests = []*test.Case{
{
Description: "logs",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Labels().Get("cID"))
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(expected)),
},
{
Description: "logs --since 60s",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "--since", "60s", data.Labels().Get("cID"))
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.DoesNotContain("foo", "bar")),
},
}
defer base.Cmd("rm", containerName).Run()
base.Cmd("run", "-d", "--network", "none", "--log-driver", "journald", "--name", containerName, testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar").AssertOK()
time.Sleep(3 * time.Second)
base.Cmd("logs", containerName).AssertOutContains("bar")
// Run logs twice, make sure that the logs are not removed
base.Cmd("logs", containerName).AssertOutContains("foo")
base.Cmd("logs", "--since", "5s", containerName).AssertOutWithFunc(func(stdout string) error {
if !strings.Contains(stdout, "bar") {
return fmt.Errorf("expected bar, got %s", stdout)
}
if !strings.Contains(stdout, "foo") {
return fmt.Errorf("expected foo, got %s", stdout)
}
return nil
})
base.Cmd("rm", "-f", containerName).AssertOK()
}
func TestLogsWithFailingContainer(t *testing.T) {
const expected = `foo
bar
`
testCase := nerdtest.Setup()
if runtime.GOOS == "windows" {
// Logging seems broken on windows.
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("run", "--name", data.Identifier(), testutil.CommonImage, "sh", "-euxc", "echo foo; echo bar; exit 42; echo baz")
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
}
testCase.Expected = test.Expects(0, nil, expect.Equals(expected))
testCase.Run(t)
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
defer base.Cmd("rm", containerName).Run()
base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar; exit 42; echo baz").AssertOK()
time.Sleep(3 * time.Second)
// AssertOutContains also asserts that the exit code of the logs command == 0,
// even when the container is failing
base.Cmd("logs", "-f", containerName).AssertOutContains("bar")
base.Cmd("logs", "-f", containerName).AssertOutNotContains("baz")
base.Cmd("rm", "-f", containerName).AssertOK()
}
func TestLogsWithRunningContainer(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
defer base.Cmd("rm", "-f", containerName).Run()
expected := make([]string, 10)
for i := 0; i < 10; i++ {
expected[i] = fmt.Sprint(i + 1)
}
testCase := nerdtest.Setup()
if runtime.GOOS == "windows" {
// Logging seems broken on windows.
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage, "sh", "-euc", "for i in `seq 1 10`; do echo $i; sleep 1; done")
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
}
testCase.Expected = test.Expects(0, nil, expect.Contains(expected[0], expected[1:]...))
testCase.Run(t)
base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euc", "for i in `seq 1 10`; do echo $i; sleep 1; done").AssertOK()
base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
}
func TestLogsWithoutNewlineOrEOF(t *testing.T) {
testCase := nerdtest.Setup()
// FIXME: test does not work on Windows yet because containerd doesn't send an exit event appropriately after task exit on Windows")
// FIXME: nerdctl behavior does not match docker - test disabled for nerdctl until we fix
testCase.Require = require.All(
require.Linux,
nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4201"),
)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage, "printf", "'Hello World!\nThere is no newline'")
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "printf", "'Hello World!\nThere is no newline'")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
// FIXME: arbitrary timeouts are by nature a problem.
time.Sleep(5 * time.Second)
return helpers.Command("logs", "-f", data.Identifier())
}
testCase.Expected = test.Expects(0, nil, expect.Equals("'Hello World!\nThere is no newline'"))
testCase.Run(t)
}
@ -318,44 +222,19 @@ func TestLogsAfterRestartingContainer(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("FIXME: test does not work on Windows yet. Restarting a container fails with: failed to create shim task: hcs::CreateComputeSystem <id>: The requested operation for attach namespace failed.: unknown")
}
testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage,
"printf", "'Hello World!\nThere is no newline'")
data.Labels().Set("cID", data.Identifier())
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.SubTests = []*test.Case{
{
Description: "logs -f works",
NoParallel: true,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-f", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals("'Hello World!\nThere is no newline'")),
},
{
Description: "logs -f works after restart",
NoParallel: true,
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("start", data.Labels().Get("cID"))
// FIXME: this is inherently flaky
time.Sleep(5 * time.Second)
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", "-f", data.Labels().Get("cID"))
},
Expected: test.Expects(0, nil, expect.Equals("'Hello World!\nThere is no newline''Hello World!\nThere is no newline'")),
},
}
testCase.Run(t)
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
defer base.Cmd("rm", "-f", containerName).Run()
base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"printf", "'Hello World!\nThere is no newline'").AssertOK()
expected := []string{"Hello World!", "There is no newline"}
time.Sleep(3 * time.Second)
base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
// restart and check logs again
base.Cmd("start", containerName)
time.Sleep(3 * time.Second)
base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
}
func TestLogsWithForegroundContainers(t *testing.T) {
@ -377,7 +256,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
},
Expected: test.Expects(0, nil, expect.Equals("foo\nbar\n")),
Expected: test.Expects(0, nil, expect.All(
expect.Contains("foo", "bar"),
expect.DoesNotContain("baz"),
)),
},
{
Description: "interactive",
@ -390,7 +272,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
},
Expected: test.Expects(0, nil, expect.Equals("foo\nbar\n")),
Expected: test.Expects(0, nil, expect.All(
expect.Contains("foo", "bar"),
expect.DoesNotContain("baz"),
)),
},
{
Description: "PTY",
@ -405,7 +290,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
},
Expected: test.Expects(0, nil, expect.Equals("foo\nbar\n")),
Expected: test.Expects(0, nil, expect.All(
expect.Contains("foo", "bar"),
expect.DoesNotContain("baz"),
)),
},
{
Description: "interactivePTY",
@ -420,88 +308,69 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
},
Expected: test.Expects(0, nil, expect.Equals("foo\nbar\n")),
Expected: test.Expects(0, nil, expect.All(
expect.Contains("foo", "bar"),
expect.DoesNotContain("baz"),
)),
},
}
}
func TestLogsTailFollowRotate(t *testing.T) {
// FIXME this is flaky by nature... the number of lines is arbitrary, the wait is arbitrary,
// and both are some sort of educated guess that things will mostly always kinda work maybe...
func TestTailFollowRotateLogs(t *testing.T) {
// FIXME this is flaky by nature... 2 lines is arbitrary, 10000 ms is arbitrary, and both are some sort of educated
// guess that things will mostly always kinda work maybe...
// Furthermore, parallelizing will put pressure on the daemon which might be even slower in answering, increasing
// the risk of transient failure.
// This test needs to be rethought entirely
// t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("tail log is not supported on Windows")
}
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
const sampleJSONLog = `{"log":"A\n","stream":"stdout","time":"2024-04-11T12:01:09.800288974Z"}`
const linesPerFile = 200
testCase := nerdtest.Setup()
defer base.Cmd("rm", "-f", containerName).Run()
base.Cmd("run", "-d", "--log-driver", "json-file",
"--log-opt", fmt.Sprintf("max-size=%d", len(sampleJSONLog)*linesPerFile),
"--log-opt", "max-file=10",
"--name", containerName, testutil.CommonImage,
"sh", "-euc", "while true; do echo A; usleep 100; done").AssertOK()
// tail log is not supported on Windows
testCase.Require = require.Not(require.Windows)
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--log-driver", "json-file",
"--log-opt", fmt.Sprintf("max-size=%d", len(sampleJSONLog)*linesPerFile),
"--log-opt", "max-file=10",
"--name", data.Identifier(), testutil.CommonImage,
"sh", "-euc", "while true; do echo A; usleep 100; done")
// FIXME: ... inherently racy...
time.Sleep(5 * time.Second)
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.Command("logs", "-f", data.Identifier())
// FIXME: this is flaky by nature. We assume that the container has started and will output enough in 5 seconds.
cmd.WithTimeout(5 * time.Second)
return cmd
}
testCase.Expected = test.Expects(expect.ExitCodeTimeout, nil, func(stdout string, t tig.T) {
tailLogs := strings.Split(strings.TrimSpace(stdout), "\n")
for _, line := range tailLogs {
if line != "" {
assert.Equal(t, "A", line)
}
tailLogCmd := base.Cmd("logs", "-f", containerName)
tailLogCmd.Timeout = 1000 * time.Millisecond
logRun := tailLogCmd.Run()
tailLogs := strings.Split(strings.TrimSpace(logRun.Stdout()), "\n")
for _, line := range tailLogs {
if line != "" {
assert.Equal(t, "A", line)
}
assert.Assert(t, len(tailLogs) > linesPerFile, fmt.Sprintf("expected %d lines or more, found %d", linesPerFile, len(tailLogs)))
})
testCase.Run(t)
}
assert.Equal(t, true, len(tailLogs) > linesPerFile, logRun.Stderr())
}
func TestLogsNoneLoggerHasNoLogURI(t *testing.T) {
func TestNoneLoggerHasNoLogURI(t *testing.T) {
testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), "--log-driver", "none", testutil.CommonImage, "sh", "-euxc", "echo foo")
}
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
}
testCase.Expected = test.Expects(1, nil, nil)
testCase.Run(t)
}
func TestLogsWithDetails(t *testing.T) {
testCase := nerdtest.Setup()
// FIXME: this is not working on windows. There is some deep issue with windows logs:
// https://github.com/containerd/nerdctl/issues/4237
if runtime.GOOS == "windows" {
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237")
}
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--log-driver", "json-file",
helpers.Ensure("run", "-d", "--log-driver", "json-file",
"--log-opt", "max-size=10m",
"--log-opt", "max-file=3",
"--log-opt", "env=ENV",
@ -532,7 +401,7 @@ func TestLogsFollowNoExtraneousLineFeed(t *testing.T) {
testCase.Setup = func(data test.Data, helpers test.Helpers) {
// Create a container that outputs a message without a trailing newline
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage,
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage,
"sh", "-c", "printf 'Hello without newline'")
}
@ -542,6 +411,8 @@ func TestLogsFollowNoExtraneousLineFeed(t *testing.T) {
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
// Use logs -f to follow the logs
// Arbitrary, but we need to wait until the logs show up
time.Sleep(3 * time.Second)
return helpers.Command("logs", "-f", data.Identifier())
}
@ -554,7 +425,7 @@ func TestLogsFollowNoExtraneousLineFeed(t *testing.T) {
func TestLogsWithStartContainer(t *testing.T) {
testCase := nerdtest.Setup()
// Windows does not support dual logging.
// For windows we havent added support for dual logging so not adding the test.
testCase.Require = require.Not(require.Windows)
testCase.SubTests = []*test.Case{
@ -563,28 +434,34 @@ func TestLogsWithStartContainer(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage)
cmd.WithPseudoTTY()
cmd.Feed(strings.NewReader("echo foo\nexit\n"))
cmd.WithFeeder(func() io.Reader {
return strings.NewReader("echo foo\nexit\n")
})
cmd.Run(&test.Expected{
ExitCode: 0,
})
cmd = helpers.Command("start", "-ia", data.Identifier())
cmd.WithPseudoTTY()
cmd.Feed(strings.NewReader("echo bar\nexit\n"))
cmd.Run(&test.Expected{
ExitCode: 0,
})
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier())
cmd := helpers.Command("start", "-ia", data.Identifier())
cmd.WithPseudoTTY()
cmd.WithFeeder(func() io.Reader {
return strings.NewReader("echo bar\nexit\n")
})
cmd.Run(&test.Expected{
ExitCode: 0,
})
cmd = helpers.Command("logs", data.Identifier())
return cmd
},
Expected: test.Expects(0, nil, expect.Contains("foo", "bar")),
},
{
// FIXME: is this test safe or could it be racy?
Description: "Test logs are captured after stopping and starting a non-interactive container and continue capturing new logs",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sh", "-c", "while true; do echo foo; sleep 1; done")
@ -604,10 +481,10 @@ func TestLogsWithStartContainer(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
finalLogsCount := strings.Count(stdout, "foo")
initialFooCount, _ := strconv.Atoi(data.Labels().Get("initialFooCount"))
assert.Assert(t, finalLogsCount > initialFooCount, "Expected 'foo' count to increase after restart")
assert.Assert(t, finalLogsCount > initialFooCount, "Expected 'foo' count to increase after restart", info)
},
}
},

View File

@ -29,7 +29,6 @@ import (
"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/containerutil"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
"github.com/containerd/nerdctl/v2/pkg/portutil"
)
func PortCommand() *cobra.Command {
@ -82,26 +81,13 @@ func portAction(cmd *cobra.Command, args []string) error {
}
defer cancel()
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
if err != nil {
return err
}
walker := &containerwalker.ContainerWalker{
Client: client,
OnFound: func(ctx context.Context, found containerwalker.Found) error {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
}
containerLabels, err := found.Container.Labels(ctx)
if err != nil {
return err
}
ports, err := portutil.LoadPortMappings(dataStore, globalOptions.Namespace, found.Container.ID(), containerLabels)
if err != nil {
return err
}
return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto, ports)
return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto)
},
}
req := args[0]

View File

@ -1,123 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package container
import (
"fmt"
"strconv"
"testing"
"time"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/portlock"
)
// iptablesCheckCommand is the shell command to check iptables rules
const iptablesCheckCommand = "iptables -t nat -S && iptables -t filter -S && iptables -t mangle -S"
// testContainerRmIptablesExecutor is a common executor function for testing iptables rules cleanup
func testContainerRmIptablesExecutor(data test.Data, helpers test.Helpers) test.TestableCommand {
t := helpers.T()
// Get the container ID from the label
containerID := data.Labels().Get("containerID")
// Remove the container
helpers.Ensure("rm", "-f", containerID)
time.Sleep(1 * time.Second)
// Create a TestableCommand using helpers.Custom
if rootlessutil.IsRootless() {
// In rootless mode, we need to enter the rootlesskit network namespace
if netns, err := rootlessutil.DetachedNetNS(); err != nil {
t.Log(fmt.Sprintf("Failed to get detached network namespace: %v", err))
t.FailNow()
} else {
if netns != "" {
// Use containerd-rootless-setuptool.sh to enter the RootlessKit namespace
return helpers.Custom("containerd-rootless-setuptool.sh", "nsenter", "--", "nsenter", "--net="+netns, "sh", "-ec", iptablesCheckCommand)
}
// Enter into :RootlessKit namespace using containerd-rootless-setuptool.sh
return helpers.Custom("containerd-rootless-setuptool.sh", "nsenter", "--", "sh", "-ec", iptablesCheckCommand)
}
}
// In non-rootless mode, check iptables rules directly on the host
return helpers.Custom("sh", "-ec", iptablesCheckCommand)
}
// TestContainerRmIptables tests that iptables rules are cleared after container deletion
func TestContainerRmIptables(t *testing.T) {
testCase := nerdtest.Setup()
// Require iptables and containerd-rootless-setuptool.sh commands to be available
testCase.Require = require.All(
require.Binary("iptables"),
require.Binary("containerd-rootless-setuptool.sh"),
require.Not(require.Windows),
require.Not(nerdtest.Docker),
)
testCase.SubTests = []*test.Case{
{
Description: "Test iptables rules are cleared after container deletion",
Setup: func(data test.Data, helpers test.Helpers) {
// Get a free port using portlock
port, err := portlock.Acquire(0)
if err != nil {
helpers.T().Log(fmt.Sprintf("Failed to acquire port: %v", err))
helpers.T().FailNow()
}
data.Labels().Set("port", strconv.Itoa(port))
// Create a container with port mapping to ensure iptables rules are created
containerID := helpers.Capture("run", "-d", "--name", data.Identifier(), "-p", fmt.Sprintf("%d:80", port), testutil.NginxAlpineImage)
data.Labels().Set("containerID", containerID)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
// Make sure container is removed even if test fails
helpers.Anyhow("rm", "-f", data.Identifier())
// Release the acquired port
if portStr := data.Labels().Get("port"); portStr != "" {
port, _ := strconv.Atoi(portStr)
_ = portlock.Release(port)
}
},
Command: testContainerRmIptablesExecutor,
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
// Get the container ID from the label
containerID := data.Labels().Get("containerID")
return &test.Expected{
ExitCode: expect.ExitCodeSuccess,
// Verify that the iptables output does not contain the container ID
Output: expect.DoesNotContain(containerID),
}
},
},
}
testCase.Run(t)
}

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -154,12 +153,12 @@ func TestRestartWithSignal(t *testing.T) {
Output: expect.All(
// Check that we saw SIGUSR1 inside the container
expect.Contains(nerdtest.SignalCaught),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
// Ensure the container was restarted
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
// Check the new pid is different
newpid := strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid)
assert.Assert(helpers.T(), newpid != data.Labels().Get("oldpid"))
assert.Assert(helpers.T(), newpid != data.Labels().Get("oldpid"), info)
},
),
}

View File

@ -234,15 +234,6 @@ func setCreateFlags(cmd *cobra.Command) {
// rootfs flags (from Podman)
cmd.Flags().Bool("rootfs", false, "The first argument is not an image but the rootfs to the exploded container")
// Health check flags
cmd.Flags().String("health-cmd", "", "Command to run to check health")
cmd.Flags().Duration("health-interval", 0, "Time between running the check (default: 30s)")
cmd.Flags().Duration("health-timeout", 0, "Maximum time to allow one check to run (default: 30s)")
cmd.Flags().Int("health-retries", 0, "Consecutive failures needed to report unhealthy (default: 3)")
cmd.Flags().Duration("health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown")
cmd.Flags().Duration("health-start-interval", 0, "Time between running the checks during the start period")
cmd.Flags().Bool("no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
// #region env flags
// entrypoint needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"}
// entrypoint StringArray is an internal implementation to support `nerdctl compose` entrypoint yaml filed with multiple strings
@ -376,7 +367,7 @@ func runAction(cmd *cobra.Command, args []string) error {
return errors.New("flags -d and -a cannot be specified together")
}
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)
netFlags, err := loadNetworkFlags(cmd)
if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err)
}

View File

@ -35,7 +35,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/cmd/container"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
@ -135,9 +134,7 @@ func TestRunCgroupV2(t *testing.T) {
base.Cmd("exec", testutil.Identifier(t)+"-testUpdate2",
"cat", "cpu.max", "memory.max", "memory.swap.max", "memory.low",
"pids.max", "cpu.weight", "cpuset.cpus", "cpuset.mems").AssertOutExactly(expected2)
base.Cmd("run", "--rm", "--security-opt", "writable-cgroups=true", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/foo").AssertOK()
base.Cmd("run", "--rm", "--security-opt", "writable-cgroups=false", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/foo").AssertFail()
base.Cmd("run", "--rm", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/foo").AssertFail()
}
func TestRunCgroupV1(t *testing.T) {
@ -179,9 +176,6 @@ func TestRunCgroupV1(t *testing.T) {
const expected = "42000\n100000\n0\n44040192\n6291456\n104857600\n0\n42\n2000\n0-1\n"
base.Cmd("run", "--rm", "--cpus", "0.42", "--cpuset-mems", "0", "--memory", "42m", "--memory-reservation", "6m", "--memory-swap", "100m", "--memory-swappiness", "0", "--pids-limit", "42", "--cpu-shares", "2000", "--cpuset-cpus", "0-1", testutil.AlpineImage, "cat", quota, period, cpusetMems, memoryLimit, memoryReservation, memorySwap, memorySwappiness, pidsLimit, cpuShare, cpusetCpus).AssertOutExactly(expected)
base.Cmd("run", "--rm", "--cpu-quota", "42000", "--cpu-period", "100000", "--cpuset-mems", "0", "--memory", "42m", "--memory-reservation", "6m", "--memory-swap", "100m", "--memory-swappiness", "0", "--pids-limit", "42", "--cpu-shares", "2000", "--cpuset-cpus", "0-1", testutil.AlpineImage, "cat", quota, period, cpusetMems, memoryLimit, memoryReservation, memorySwap, memorySwappiness, pidsLimit, cpuShare, cpusetCpus).AssertOutExactly(expected)
base.Cmd("run", "--rm", "--security-opt", "writable-cgroups=true", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/pids/foo").AssertOK()
base.Cmd("run", "--rm", "--security-opt", "writable-cgroups=false", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/pids/foo").AssertFail()
base.Cmd("run", "--rm", testutil.AlpineImage, "mkdir", "/sys/fs/cgroup/pids/foo").AssertFail()
}
// TestIssue3781 tests https://github.com/containerd/nerdctl/issues/3781
@ -316,7 +310,7 @@ func TestRunDevice(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("exec", data.Labels().Get("id"), "sh", "-ec", "echo -n \"overwritten-lo1-content\">"+lo[1].Device)
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {
Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, info string, t *testing.T) {
lo1Read, err := os.ReadFile(lo[1].Device)
assert.NilError(t, err)
assert.Equal(t, string(bytes.Trim(lo1Read, "\x00")), "overwritten-lo1-content")
@ -529,7 +523,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "{{.HostConfig.BlkioWeight}}", data.Identifier()), "150"))
},
),
@ -551,7 +545,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioWeightDevice}}{{.Weight}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "100"))
},
@ -580,7 +574,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceReadBps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "1048576"))
},
@ -609,7 +603,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceWriteBps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "2097152"))
},
@ -638,7 +632,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceReadIOps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "1000"))
},
@ -667,7 +661,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceWriteIOps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "2000"))
},
@ -702,7 +696,7 @@ func TestRunCPURealTimeSettingCgroupV1(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
rtRuntime := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimeRuntime}}", data.Identifier())
rtPeriod := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimePeriod}}", data.Identifier())
assert.Assert(t, strings.Contains(rtRuntime, "950000"))

View File

@ -36,7 +36,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
@ -549,7 +548,7 @@ func TestRunWithDetachKeys(t *testing.T) {
Errors: []error{errors.New("detach keys")},
Output: expect.All(
expect.Contains("markmark"),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
),
@ -617,7 +616,7 @@ func TestIssue3568(t *testing.T) {
Errors: []error{errors.New("detach keys")},
Output: expect.All(
expect.Contains("markmark"),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
),
@ -652,8 +651,8 @@ func TestPortBindingWithCustomHost(t *testing.T) {
ExitCode: 0,
Errors: []error{},
Output: expect.All(
func(stdout string, t tig.T) {
resp, err := nettestutil.HTTPGet(address, 5, false)
func(stdout string, info string, t *testing.T) {
resp, err := nettestutil.HTTPGet(address, 30, false)
assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body)

View File

@ -29,7 +29,6 @@ import (
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
@ -308,7 +307,7 @@ func TestRunBindMountTmpfs(t *testing.T) {
}
func mountExistsWithOpt(mountPoint, mountOpt string) test.Comparator {
return func(stdout string, t tig.T) {
return func(stdout, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
mountOutput := []string{}
for _, line := range lines {
@ -353,8 +352,6 @@ func TestRunBindMountBind(t *testing.T) {
"top",
)
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container"))
// Save host rwDir location and container id for subtests
data.Labels().Set("container", data.Identifier("container"))
data.Labels().Set("rwDir", rwDir)

View File

@ -28,7 +28,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/strutil"
)
func loadNetworkFlags(cmd *cobra.Command, globalOpts types.GlobalCommandOptions) (types.NetworkOptions, error) {
func loadNetworkFlags(cmd *cobra.Command) (types.NetworkOptions, error) {
netOpts := types.NetworkOptions{}
// --net/--network=<net name> ...
@ -101,58 +101,33 @@ func loadNetworkFlags(cmd *cobra.Command, globalOpts types.GlobalCommandOptions)
netOpts.Domainname = domainname
// --dns=<DNS host> ...
// Use command flags if set, otherwise use global config is set
var dnsSlice []string
if cmd.Flags().Changed("dns") {
var err error
dnsSlice, err = cmd.Flags().GetStringSlice("dns")
if err != nil {
return netOpts, err
}
} else {
dnsSlice = globalOpts.DNS
dnsSlice, err := cmd.Flags().GetStringSlice("dns")
if err != nil {
return netOpts, err
}
netOpts.DNSServers = strutil.DedupeStrSlice(dnsSlice)
// --dns-search=<domain name> ...
// Use command flags if set, otherwise use global config is set
var dnsSearchSlice []string
if cmd.Flags().Changed("dns-search") {
var err error
dnsSearchSlice, err = cmd.Flags().GetStringSlice("dns-search")
if err != nil {
return netOpts, err
}
} else {
dnsSearchSlice = globalOpts.DNSSearch
dnsSearchSlice, err := cmd.Flags().GetStringSlice("dns-search")
if err != nil {
return netOpts, err
}
netOpts.DNSSearchDomains = strutil.DedupeStrSlice(dnsSearchSlice)
// --dns-opt/--dns-option=<resolv.conf line> ...
// Use command flags if set, otherwise use global config if set
dnsOptions := []string{}
// Check if either dns-opt or dns-option flags were set
dnsOptChanged := cmd.Flags().Changed("dns-opt")
dnsOptionChanged := cmd.Flags().Changed("dns-option")
if dnsOptChanged || dnsOptionChanged {
// Use command flags
dnsOptFlags, err := cmd.Flags().GetStringSlice("dns-opt")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptFlags...)
dnsOptionFlags, err := cmd.Flags().GetStringSlice("dns-option")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptionFlags...)
} else {
// Use global config defaults
dnsOptions = append(dnsOptions, globalOpts.DNSOpts...)
dnsOptFlags, err := cmd.Flags().GetStringSlice("dns-opt")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptFlags...)
dnsOptionFlags, err := cmd.Flags().GetStringSlice("dns-option")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptionFlags...)
netOpts.DNSResolvConfOptions = strutil.DedupeStrSlice(dnsOptions)

View File

@ -155,7 +155,7 @@ func baseTestRunPort(t *testing.T, nginxImage string, nginxIndexHTMLSnippet stri
hostPort: "7000-7005",
containerPort: "80-85",
connectURLPort: 7001,
err: "error after 5 attempts",
err: "error after 30 attempts",
runShouldSuccess: true,
},
{
@ -209,7 +209,7 @@ func baseTestRunPort(t *testing.T, nginxImage string, nginxIndexHTMLSnippet stri
return
}
resp, err := nettestutil.HTTPGet(connectURL, 5, false)
resp, err := nettestutil.HTTPGet(connectURL, 30, false)
if tc.err != "" {
assert.ErrorContains(t, err, tc.err)
return

View File

@ -36,10 +36,10 @@ import (
"github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/pkg/netns"
"github.com/containerd/errdefs"
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -247,7 +247,7 @@ func TestRunPortWithNoHostPort(t *testing.T) {
return
}
connectURL := fmt.Sprintf("http://%s:%s", "127.0.0.1", paramsMap["portNumber"])
resp, err := nettestutil.HTTPGet(connectURL, 5, false)
resp, err := nettestutil.HTTPGet(connectURL, 30, false)
assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body)
assert.NilError(t, err)
@ -332,7 +332,7 @@ func TestUniqueHostPortAssignement(t *testing.T) {
// Make HTTP GET request to container 1
connectURL1 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port1)
resp1, err := nettestutil.HTTPGet(connectURL1, 5, false)
resp1, err := nettestutil.HTTPGet(connectURL1, 30, false)
assert.NilError(t, err)
respBody1, err := io.ReadAll(resp1.Body)
assert.NilError(t, err)
@ -340,7 +340,7 @@ func TestUniqueHostPortAssignement(t *testing.T) {
// Make HTTP GET request to container 2
connectURL2 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port2)
resp2, err := nettestutil.HTTPGet(connectURL2, 5, false)
resp2, err := nettestutil.HTTPGet(connectURL2, 30, false)
assert.NilError(t, err)
respBody2, err := io.ReadAll(resp2.Body)
assert.NilError(t, err)
@ -349,81 +349,29 @@ func TestUniqueHostPortAssignement(t *testing.T) {
}
}
func TestHostPortAlreadyInUse(t *testing.T) {
testCases := []struct {
hostPort string
containerPort string
}{
{
hostPort: "5000",
containerPort: "80/tcp",
},
{
hostPort: "5000",
containerPort: "80/tcp",
},
{
hostPort: "5000",
containerPort: "80/udp",
},
{
hostPort: "5000",
containerPort: "80/sctp",
},
}
tID := testutil.Identifier(t)
for i, tc := range testCases {
tc := tc
tcName := fmt.Sprintf("%+v", tc)
t.Run(tcName, func(t *testing.T) {
if strings.Contains(tc.containerPort, "sctp") && rootlessutil.IsRootless() {
t.Skip("sctp is not supported in rootless mode")
}
testContainerName1 := fmt.Sprintf("%s-%d-1", tID, i)
testContainerName2 := fmt.Sprintf("%s-%d-2", tID, i)
base := testutil.NewBase(t)
t.Cleanup(func() {
base.Cmd("rm", "-f", testContainerName1, testContainerName2).AssertOK()
})
pFlag := fmt.Sprintf("%s:%s", tc.hostPort, tc.containerPort)
cmd1 := base.Cmd("run", "-d",
"--name", testContainerName1, "-p",
pFlag,
testutil.NginxAlpineImage)
cmd2 := base.Cmd("run", "-d",
"--name", testContainerName2, "-p",
pFlag,
testutil.NginxAlpineImage)
cmd1.AssertOK()
cmd2.AssertFail()
})
}
}
func TestRunPort(t *testing.T) {
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
}
func TestRunWithManyPortsThenCleanUp(t *testing.T) {
func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
testCase := nerdtest.Setup()
// docker does not set label restriction to 4096 bytes
testCase.Require = require.Not(nerdtest.Docker)
testCase.SubTests = []*test.Case{
{
Description: "Run a container with many ports, and then clean up.",
Description: "Run a container with invalid ports, and then clean up.",
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "--data-root", data.Temp().Path(), "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--data-root", data.Temp().Path(), "--rm", "-p", "22200-22299:22200-22299", testutil.CommonImage)
return helpers.Command("run", "--data-root", data.Temp().Path(), "--rm", "--name", data.Identifier(), "-p", "22200-22299:22200-22299", testutil.CommonImage)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
ExitCode: 1,
Errors: []error{errdefs.ErrInvalidArgument},
Output: func(stdout string, info string, t *testing.T) {
getAddrHash := func(addr string) string {
const addrHashLen = 8
@ -570,103 +518,158 @@ func TestSharedNetworkSetup(t *testing.T) {
testCase := &test.Case{
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
data.Labels().Set("container1", data.Identifier("container1"))
helpers.Ensure("run", "-d", "--name", data.Identifier("container1"),
testutil.CommonImage, "sleep", "inf")
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container1"))
data.Labels().Set("containerName1", data.Identifier("-container1"))
containerName1 := data.Labels().Get("containerName1")
helpers.Ensure("run", "-d", "--name", containerName1,
testutil.NginxAlpineImage)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container1"))
helpers.Anyhow("rm", "-f", data.Identifier("-container1"))
},
SubTests: []*test.Case{
{
Description: "Test network is shared",
NoParallel: true, // The validation involves starting of the main container: container1
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container2"))
helpers.Anyhow("rm", "-f", data.Identifier())
},
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure(
"run", "-d", "--name", data.Identifier("container2"),
"--network=container:"+data.Labels().Get("container1"),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "-d", "--name", containerName2,
"--network=container:"+data.Labels().Get("containerName1"),
testutil.NginxAlpineImage)
data.Labels().Set("container2", data.Identifier("container2"))
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container2"))
return cmd
},
SubTests: []*test.Case{
{
NoParallel: true,
Description: "Test network is shared",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("exec", data.Labels().Get("container2"), "wget", "-qO-", "http://127.0.0.1:80")
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, info string, t *testing.T) {
containerName2 := data.Identifier()
assert.Assert(t, strings.Contains(helpers.Capture("exec", containerName2, "wget", "-qO-", "http://127.0.0.1:80"), testutil.NginxAlpineIndexHTMLSnippet), info)
helpers.Ensure("restart", data.Labels().Get("containerName1"))
helpers.Ensure("stop", "--time=1", containerName2)
helpers.Ensure("start", containerName2)
assert.Assert(t, strings.Contains(helpers.Capture("exec", containerName2, "wget", "-qO-", "http://127.0.0.1:80"), testutil.NginxAlpineIndexHTMLSnippet), info)
},
Expected: test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet)),
},
{
NoParallel: true,
Description: "Test network is shared after restart",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("restart", data.Labels().Get("container1"))
helpers.Ensure("stop", "--time=1", data.Labels().Get("container2"))
helpers.Ensure("start", data.Labels().Get("container2"))
nerdtest.EnsureContainerStarted(helpers, data.Labels().Get("container2"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("exec", data.Labels().Get("container2"), "wget", "-qO-", "http://127.0.0.1:80")
},
Expected: test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet)),
},
}
},
},
{
Description: "Test uts is supported in shared network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--uts", "host",
"--network=container:"+data.Labels().Get("container1"),
testutil.CommonImage)
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "-d", "--name", containerName2, "--uts", "host",
"--network=container:"+data.Labels().Get("containerName1"),
testutil.AlpineImage)
return cmd
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
}
},
Expected: test.Expects(0, nil, nil),
},
{
Description: "Test dns is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--dns", "0.1.2.3",
"--network=container:"+data.Labels().Get("container1"),
testutil.CommonImage)
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "-d", "--name", containerName2, "--dns", "0.1.2.3",
"--network=container:"+data.Labels().Get("containerName1"),
testutil.AlpineImage)
return cmd
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
if nerdtest.IsDocker() {
return &test.Expected{
ExitCode: 125,
}
}
return &test.Expected{
ExitCode: 1,
}
},
// 1 for nerdctl, 125 for docker
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
},
{
Description: "Test dns options is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--dns-option", "attempts:5",
"--network=container:"+data.Labels().Get("container1"),
testutil.CommonImage, "cat", "/etc/resolv.conf")
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "--name", containerName2, "--dns-option", "attempts:5",
"--network=container:"+data.Labels().Get("containerName1"),
testutil.AlpineImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
// The Option doesnt throw an error but is never inserted to the resolv.conf
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, "attempts:5"), info)
},
}
},
// The Option doesn't throw an error but is never inserted to the resolv.conf
Expected: test.Expects(0, nil, expect.DoesNotContain("attempts:5")),
},
{
Description: "Test publish is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--publish", "80:8080",
"--network=container:"+data.Labels().Get("container1"),
testutil.AlpineImage)
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "-d", "--name", containerName2, "--publish", "80:8080",
"--network=container:"+data.Labels().Get("containerName1"),
testutil.AlpineImage)
return cmd
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
if nerdtest.IsDocker() {
return &test.Expected{
ExitCode: 125,
}
}
return &test.Expected{
ExitCode: 1,
}
},
// 1 for nerdctl, 125 for docker
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
},
{
Description: "Test hostname is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--hostname", "test",
"--network=container:"+data.Labels().Get("container1"),
testutil.AlpineImage)
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
containerName2 := data.Identifier()
cmd := helpers.Command()
cmd.WithArgs("run", "-d", "--name", containerName2, "--hostname", "test",
"--network=container:"+data.Labels().Get("containerName1"),
testutil.AlpineImage)
return cmd
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
if nerdtest.IsDocker() {
return &test.Expected{
ExitCode: 125,
}
}
return &test.Expected{
ExitCode: 1,
}
},
// 1 for nerdctl, 125 for docker
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
},
},
}
@ -679,15 +682,15 @@ func TestSharedNetworkWithNone(t *testing.T) {
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier("container1"), "--network", "none",
testutil.CommonImage, "sleep", "inf")
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container1"))
testutil.NginxAlpineImage)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container1"))
helpers.Anyhow("rm", "-f", data.Identifier("container2"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm",
"--network=container:"+data.Identifier("container1"), testutil.CommonImage)
return helpers.Command("run", "-d", "--name", data.Identifier("container2"),
"--network=container:"+data.Identifier("container1"), testutil.NginxAlpineImage)
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, nil),
}
@ -911,14 +914,13 @@ func TestNoneNetworkHostName(t *testing.T) {
nerdtest.Setup()
testCase := &test.Case{
Require: require.Not(require.Windows),
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Setup: func(data test.Data, helpers test.Helpers) {
output := helpers.Capture("run", "-d", "--name", data.Identifier(), "--network", "none", testutil.CommonImage, "sleep", "inf")
output := helpers.Capture("run", "-d", "--name", data.Identifier(), "--network", "none", testutil.NginxAlpineImage)
assert.Assert(helpers.T(), len(output) > 12, output)
data.Labels().Set("hostname", output[:12])
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("exec", data.Identifier(), "cat", "/etc/hostname")
@ -937,20 +939,20 @@ func TestHostNetworkHostName(t *testing.T) {
testCase := &test.Case{
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Custom("cat", "/etc/hostname").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
data.Labels().Set("hostHostname", stdout)
},
})
data.Labels().Set("containerName1", data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm",
"--network", "host",
testutil.AlpineImage, "cat", "/etc/hostname")
return helpers.Custom("cat", "/etc/hostname")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.Equals(data.Labels().Get("hostHostname")),
Output: func(stdout string, info string, t *testing.T) {
hostname := stdout
assert.Assert(t, strings.Compare(strings.TrimSpace(helpers.Capture("run", "--name", data.Identifier(), "--network", "host", testutil.AlpineImage, "cat", "/etc/hostname")), strings.TrimSpace(hostname)) == 0, info)
},
}
},
}
@ -961,18 +963,27 @@ func TestNoneNetworkDnsConfigs(t *testing.T) {
nerdtest.Setup()
testCase := &test.Case{
Require: require.Not(require.Windows),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm",
"--network", "none",
"--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5",
testutil.CommonImage, "cat", "/etc/resolv.conf")
Setup: func(data test.Data, helpers test.Helpers) {
data.Labels().Set("containerName1", data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "-d", "--name", data.Identifier(), "--network", "none", "--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5", testutil.NginxAlpineImage)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, info string, t *testing.T) {
out := helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf")
assert.Assert(t, strings.Contains(out, "0.1.2.3"), info)
assert.Assert(t, strings.Contains(out, "example.com"), info)
assert.Assert(t, strings.Contains(out, "attempts:5"), info)
assert.Assert(t, strings.Contains(out, "timeout:3"), info)
},
}
},
Expected: test.Expects(0, nil, expect.Contains(
"0.1.2.3",
"example.com",
"attempts:5",
"timeout:3",
)),
}
testCase.Run(t)
}
@ -981,101 +992,26 @@ func TestHostNetworkDnsConfigs(t *testing.T) {
nerdtest.Setup()
testCase := &test.Case{
Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) {
data.Labels().Set("containerName1", data.Identifier())
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm",
"--network", "host",
"--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return helpers.Command("run", "-d", "--name", data.Identifier(), "--network", "host", "--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5", testutil.NginxAlpineImage)
},
Expected: test.Expects(0, nil, expect.Contains(
"0.1.2.3",
"example.com",
"attempts:5",
"timeout:3",
)),
}
testCase.Run(t)
}
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, info string, t *testing.T) {
out := helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf")
assert.Assert(t, strings.Contains(out, "0.1.2.3"), info)
assert.Assert(t, strings.Contains(out, "example.com"), info)
assert.Assert(t, strings.Contains(out, "attempts:5"), info)
assert.Assert(t, strings.Contains(out, "timeout:3"), info)
func TestDNSWithGlobalConfig(t *testing.T) {
var configContent test.ConfigValue = `debug = false
debug_full = false
dns = ["10.10.10.10", "20.20.20.20"]
dns_opts = ["ndots:2", "timeout:5"]
dns_search = ["example.com", "test.local"]`
nerdtest.Setup()
testCase := &test.Case{
Config: test.WithConfig(nerdtest.NerdctlToml, configContent),
// NERDCTL_TOML not supported in Docker
Require: require.Not(nerdtest.Docker),
SubTests: []*test.Case{
{
Description: "Global DNS settings are used when command line options are not provided",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
{
Description: "Command line DNS options override global config",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm",
"--dns", "9.9.9.9",
"--dns-search", "override.com",
"--dns-opt", "ndots:3",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 9.9.9.9"),
expect.Contains("search override.com"),
expect.Contains("options ndots:3"),
)),
},
{
Description: "Global DNS settings should also apply when using host network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", "--network", "host",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
{
Description: "Global DNS settings should also apply when using none network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", "--network", "none",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
}
},
}
testCase.Run(t)

View File

@ -69,7 +69,7 @@ func TestRunRestart(t *testing.T) {
}
return nil
}
assert.NilError(t, check(5))
assert.NilError(t, check(30))
base.KillDaemon()
base.EnsureDaemonActive()

View File

@ -25,7 +25,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -45,7 +44,7 @@ func TestRunSoci(t *testing.T) {
testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Custom("mount").Run(&test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
data.Labels().Set("beforeCount", strconv.Itoa(strings.Count(stdout, "fuse.rawBridge")))
},
})
@ -61,12 +60,12 @@ func TestRunSoci(t *testing.T) {
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
var afterCount int
beforeCount, _ := strconv.Atoi(data.Labels().Get("beforeCount"))
helpers.Custom("mount").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout, info string, t *testing.T) {
afterCount = strings.Count(stdout, "fuse.rawBridge")
},
})

View File

@ -37,7 +37,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -157,7 +156,7 @@ func TestRunExitCode(t *testing.T) {
Output: expect.All(
expect.Match(regexp.MustCompile("Exited [(]123[)][A-Za-z0-9 ]+"+data.Identifier("exit123"))),
expect.Match(regexp.MustCompile("Exited [(]0[)][A-Za-z0-9 ]+"+data.Identifier("exit0"))),
func(stdout string, t tig.T) {
func(stdout, info string, t *testing.T) {
assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit0")).State.Status, "exited")
assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit123")).State.Status, "exited")
},
@ -838,223 +837,3 @@ func TestRunDomainname(t *testing.T) {
})
}
}
func TestRunHealthcheckFlags(t *testing.T) {
testCase := nerdtest.Setup()
testCases := []struct {
name string
args []string
shouldFail bool
expectTest []string
expectRetries int
expectInterval time.Duration
expectTimeout time.Duration
expectStartPeriod time.Duration
}{
{
name: "Valid_full_config",
args: []string{
"--health-cmd", "curl -f http://localhost || exit 1",
"--health-interval", "30s",
"--health-timeout", "5s",
"--health-retries", "3",
"--health-start-period", "2s",
},
expectTest: []string{"CMD-SHELL", "curl -f http://localhost || exit 1"},
expectInterval: 30 * time.Second,
expectTimeout: 5 * time.Second,
expectRetries: 3,
expectStartPeriod: 2 * time.Second,
},
{
name: "No_healthcheck",
args: []string{
"--no-healthcheck",
},
expectTest: []string{"NONE"},
},
{
name: "No_healthcheck_flag",
args: []string{},
expectTest: nil,
},
{
name: "Conflicting_flags",
args: []string{
"--no-healthcheck", "--health-cmd", "true",
},
shouldFail: true,
},
{
name: "Negative_retries",
args: []string{
"--health-cmd", "true",
"--health-retries", "-2",
},
shouldFail: true,
},
{
name: "Negative_timeout",
args: []string{
"--health-cmd", "true",
"--health-timeout", "-5s",
},
shouldFail: true,
},
{
name: "Invalid_timeout_format",
args: []string{
"--health-cmd", "true",
"--health-timeout", "5blah",
},
shouldFail: true,
},
{
name: "Health_cmd_cmd_shell",
args: []string{
"--health-cmd", "curl -f http://localhost || exit 1",
},
expectTest: []string{"CMD-SHELL", "curl -f http://localhost || exit 1"},
},
{
name: "Health_cmd_array_like",
args: []string{
"--health-cmd", "echo hello",
},
expectTest: []string{"CMD-SHELL", "echo hello"},
},
{
name: "Health_cmd_empty",
args: []string{
"--health-cmd", "",
"--health-retries", "2",
},
expectTest: nil,
expectRetries: 2,
},
}
for _, tc := range testCases {
tc := tc
testCase.SubTests = append(testCase.SubTests, &test.Case{
Description: tc.name,
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
args := append([]string{"run", "-d", "--name", tc.name}, tc.args...)
args = append(args, testutil.CommonImage, "sleep", "infinity")
return helpers.Command(args...)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
if tc.shouldFail {
return &test.Expected{
ExitCode: expect.ExitCodeGenericFail,
}
}
return &test.Expected{
ExitCode: expect.ExitCodeSuccess,
Output: expect.All(
func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, tc.name)
hc := inspect.Config.Healthcheck
if tc.expectTest == nil {
assert.Assert(t, hc == nil || len(hc.Test) == 0)
} else {
assert.Assert(t, hc != nil)
assert.DeepEqual(t, hc.Test, tc.expectTest)
}
if tc.expectRetries > 0 {
assert.Equal(t, hc.Retries, tc.expectRetries)
}
if tc.expectTimeout > 0 {
assert.Equal(t, hc.Timeout, tc.expectTimeout)
}
if tc.expectInterval > 0 {
assert.Equal(t, hc.Interval, tc.expectInterval)
}
if tc.expectStartPeriod > 0 {
assert.Equal(t, hc.StartPeriod, tc.expectStartPeriod)
}
},
),
}
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", tc.name)
},
})
}
testCase.Run(t)
}
func TestRunHealthcheckFromImage(t *testing.T) {
nerdtest.Setup()
dockerfile := fmt.Sprintf(`FROM %s
HEALTHCHECK --interval=30s --timeout=10s CMD wget -q --spider http://localhost:8080 || exit 1
`, testutil.CommonImage)
testCase := &test.Case{
Require: nerdtest.Build,
Setup: func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerfile, "Dockerfile")
data.Labels().Set("image", data.Identifier())
helpers.Ensure("build", "-t", data.Labels().Get("image"), data.Temp().Path())
},
SubTests: []*test.Case{
{
Description: "merge_with_image",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "-d", "--name", data.Identifier(),
"--health-retries=5",
"--health-interval=45s",
data.Labels().Get("image"))
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: expect.ExitCodeSuccess,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
hc := inspect.Config.Healthcheck
assert.Assert(t, hc != nil, "expected healthcheck config to be present")
assert.DeepEqual(t, hc.Test, []string{"CMD-SHELL", "wget -q --spider http://localhost:8080 || exit 1"})
assert.Equal(t, 5, hc.Retries) // From CLI flags
assert.Equal(t, 45*time.Second, hc.Interval) // From CLI flags
assert.Equal(t, 10*time.Second, hc.Timeout) // From Dockerfile
}),
}
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
},
{
Description: "Disable image health checks via runtime flag",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command(
"run", "-d", "--name", data.Identifier(),
"--no-healthcheck",
data.Labels().Get("image"),
)
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: expect.ExitCodeSuccess,
Output: expect.All(func(stdout string, t tig.T) {
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
hc := inspect.Config.Healthcheck
assert.Assert(t, hc != nil, "expected healthcheck config to be present")
assert.DeepEqual(t, hc.Test, []string{"NONE"})
}),
}
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier())
},
},
},
}
testCase.Run(t)
}

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -223,13 +222,12 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
t.Fatalf("Failed to get container host UID: %v", err)
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
},
}
},
@ -251,13 +249,12 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
t.Fatalf("Failed to get container host UID: %v", err)
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
},
}
},
@ -298,13 +295,12 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
t.Fatalf("Failed to get container host UID: %v", err)
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
},
}
},
@ -326,13 +322,12 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
t.Fatalf("Failed to get container host UID: %v", err)
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
},
}
},
@ -372,13 +367,12 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
t.Fatalf("Failed to get container host UID: %v", err)
}
assert.Assert(t, actualHostUID == "0")
assert.Assert(t, actualHostUID == "0", info)
},
}
},

View File

@ -27,7 +27,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -68,7 +67,7 @@ func TestStartDetachKeys(t *testing.T) {
ExitCode: 0,
Errors: []error{errors.New("detach keys")},
Output: expect.All(
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
},
),

View File

@ -66,14 +66,14 @@ func TestStopStart(t *testing.T) {
return nil
}
assert.NilError(t, check(5))
assert.NilError(t, check(30))
base.Cmd("stop", testContainerName).AssertOK()
base.Cmd("exec", testContainerName, "ps").AssertFail()
if check(1) == nil {
t.Fatal("expected to get an error")
}
base.Cmd("start", testContainerName).AssertOK()
assert.NilError(t, check(5))
assert.NilError(t, check(30))
}
func TestStopWithStopSignal(t *testing.T) {

View File

@ -163,7 +163,7 @@ RUN uname -m > /usr/share/nginx/html/index.html
}
for testURL, expectedIndexHTML := range testCases {
resp, err := nettestutil.HTTPGet(testURL, 5, false)
resp, err := nettestutil.HTTPGet(testURL, 50, false)
assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body)
assert.NilError(t, err)

View File

@ -283,10 +283,3 @@ func AddPersistentBoolFlag(cmd *cobra.Command, name string, aliases, nonPersiste
}
}
}
// HiddenPersistentStringArrayFlag creates a persistent string slice flag and hides it.
// Used mainly to pass global config values to individual commands.
func HiddenPersistentStringArrayFlag(cmd *cobra.Command, name string, value []string, usage string) {
cmd.PersistentFlags().StringSlice(name, value, usage)
cmd.PersistentFlags().MarkHidden(name)
}

View File

@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/containerd/nerdctl/v2/pkg"
"github.com/containerd/nerdctl/v2/pkg/api/types"
)
@ -47,39 +46,6 @@ func VerifyOptions(cmd *cobra.Command) (opt types.ImageVerifyOptions, err error)
return
}
func ValidateHealthcheckFlags(options types.ContainerCreateOptions) error {
healthFlagsSet :=
options.HealthInterval != 0 ||
options.HealthTimeout != 0 ||
options.HealthRetries != 0 ||
options.HealthStartPeriod != 0 ||
options.HealthStartInterval != 0
if options.NoHealthcheck {
if options.HealthCmd != "" || healthFlagsSet {
return fmt.Errorf("--no-healthcheck conflicts with --health-* options")
}
}
// Note: HealthCmd can be empty with other healthcheck flags set cause healthCmd could be coming from image.
if options.HealthInterval < 0 {
return fmt.Errorf("--health-interval cannot be negative")
}
if options.HealthTimeout < 0 {
return fmt.Errorf("--health-timeout cannot be negative")
}
if options.HealthRetries < 0 {
return fmt.Errorf("--health-retries cannot be negative")
}
if options.HealthStartPeriod < 0 {
return fmt.Errorf("--health-start-period cannot be negative")
}
if options.HealthStartInterval < 0 {
return fmt.Errorf("--health-start-interval cannot be negative")
}
return nil
}
func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) {
debug, err := cmd.Flags().GetBool("debug")
if err != nil {
@ -145,24 +111,6 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
if err != nil {
return types.GlobalCommandOptions{}, err
}
dns, err := cmd.Flags().GetStringSlice("global-dns")
if err != nil {
return types.GlobalCommandOptions{}, err
}
dnsOpts, err := cmd.Flags().GetStringSlice("global-dns-opts")
if err != nil {
return types.GlobalCommandOptions{}, err
}
dnsSearch, err := cmd.Flags().GetStringSlice("global-dns-search")
if err != nil {
return types.GlobalCommandOptions{}, err
}
// Point to dataRoot for filesystem-helpers implementing rollback / backups.
err = pkg.InitFS(dataRoot)
if err != nil {
return types.GlobalCommandOptions{}, err
}
return types.GlobalCommandOptions{
Debug: debug,
@ -181,9 +129,6 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
BridgeIP: bridgeIP,
KubeHideDupe: kubeHideDupe,
CDISpecDirs: cdiSpecDirs,
DNS: dns,
DNSOpts: dnsOpts,
DNSSearch: dnsSearch,
}, nil
}

View File

@ -74,7 +74,7 @@ func ComposeUp(t *testing.T, base *testutil.Base, dockerComposeYAML string, opts
base.Cmd("network", "inspect", fmt.Sprintf("%s_default", projectName)).AssertOK()
checkWordpress := func() error {
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false)
resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 10, false)
if err != nil {
return err
}

View File

@ -89,12 +89,6 @@ func convertCommand() *cobra.Command {
cmd.Flags().String("overlaybd-dbstr", "", "Database config string for overlaybd")
// #endregion
// #region soci flags
cmd.Flags().Bool("soci", false, "Convert image to SOCI Index V2 format.")
cmd.Flags().Int64("soci-min-layer-size", -1, "The minimum size of layers that will be converted to SOCI Index V2 format")
cmd.Flags().Int64("soci-span-size", -1, "The size of SOCI spans")
// #endregion
// #region generic flags
cmd.Flags().Bool("uncompress", false, "Convert tar.gz layers to uncompressed tar layers")
cmd.Flags().Bool("oci", false, "Convert Docker media types to OCI media types")
@ -219,21 +213,6 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
}
// #endregion
// #region soci flags
soci, err := cmd.Flags().GetBool("soci")
if err != nil {
return types.ImageConvertOptions{}, err
}
sociMinLayerSize, err := cmd.Flags().GetInt64("soci-min-layer-size")
if err != nil {
return types.ImageConvertOptions{}, err
}
sociSpanSize, err := cmd.Flags().GetInt64("soci-span-size")
if err != nil {
return types.ImageConvertOptions{}, err
}
// #endregion
// #region generic flags
uncompress, err := cmd.Flags().GetBool("uncompress")
if err != nil {
@ -258,6 +237,37 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
return types.ImageConvertOptions{
GOptions: globalOptions,
Format: format,
// #region estargz flags
Estargz: estargz,
EstargzRecordIn: estargzRecordIn,
EstargzCompressionLevel: estargzCompressionLevel,
EstargzChunkSize: estargzChunkSize,
EstargzMinChunkSize: estargzMinChunkSize,
EstargzExternalToc: estargzExternalTOC,
EstargzKeepDiffID: estargzKeepDiffID,
// #endregion
// #region zstd flags
Zstd: zstd,
ZstdCompressionLevel: zstdCompressionLevel,
// #endregion
// #region zstd:chunked flags
ZstdChunked: zstdchunked,
ZstdChunkedCompressionLevel: zstdChunkedCompressionLevel,
ZstdChunkedChunkSize: zstdChunkedChunkSize,
ZstdChunkedRecordIn: zstdChunkedRecordIn,
// #endregion
// #region nydus flags
Nydus: nydus,
NydusBuilderPath: nydusBuilderPath,
NydusWorkDir: nydusWorkDir,
NydusPrefetchPatterns: nydusPrefetchPatterns,
NydusCompressor: nydusCompressor,
// #endregion
// #region overlaybd flags
Overlaybd: overlaybd,
OverlayFsType: overlaybdFsType,
OverlaydbDBStr: overlaybdDbstr,
// #endregion
// #region generic flags
Uncompress: uncompress,
Oci: oci,
@ -266,45 +276,6 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
Platforms: platforms,
AllPlatforms: allPlatforms,
// #endregion
// Embed image format options
EstargzOptions: types.EstargzOptions{
Estargz: estargz,
EstargzRecordIn: estargzRecordIn,
EstargzCompressionLevel: estargzCompressionLevel,
EstargzChunkSize: estargzChunkSize,
EstargzMinChunkSize: estargzMinChunkSize,
EstargzExternalToc: estargzExternalTOC,
EstargzKeepDiffID: estargzKeepDiffID,
},
ZstdOptions: types.ZstdOptions{
Zstd: zstd,
ZstdCompressionLevel: zstdCompressionLevel,
},
ZstdChunkedOptions: types.ZstdChunkedOptions{
ZstdChunked: zstdchunked,
ZstdChunkedCompressionLevel: zstdChunkedCompressionLevel,
ZstdChunkedChunkSize: zstdChunkedChunkSize,
ZstdChunkedRecordIn: zstdChunkedRecordIn,
},
NydusOptions: types.NydusOptions{
Nydus: nydus,
NydusBuilderPath: nydusBuilderPath,
NydusWorkDir: nydusWorkDir,
NydusPrefetchPatterns: nydusPrefetchPatterns,
NydusCompressor: nydusCompressor,
},
OverlaybdOptions: types.OverlaybdOptions{
Overlaybd: overlaybd,
OverlayFsType: overlaybdFsType,
OverlaydbDBStr: overlaybdDbstr,
},
SociConvertOptions: types.SociConvertOptions{
Soci: soci,
SociOptions: types.SociOptions{
SpanSize: sociSpanSize,
MinLayerSize: sociMinLayerSize,
},
},
Stdout: cmd.OutOrStdout(),
}, nil
}

View File

@ -19,14 +19,13 @@ package image
import (
"fmt"
"testing"
"time"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
)
func TestImageConvert(t *testing.T) {
@ -89,24 +88,6 @@ func TestImageConvert(t *testing.T) {
},
Expected: test.Expects(0, nil, nil),
},
{
Description: "soci",
Require: require.All(
require.Not(nerdtest.Docker),
nerdtest.Soci,
nerdtest.SociVersion("0.10.0"),
),
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rmi", "-f", data.Identifier("converted-image"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("image", "convert", "--soci",
"--soci-span-size", "2097152",
"--soci-min-layer-size", "0",
testutil.CommonImage, data.Identifier("converted-image"))
},
Expected: test.Expects(0, nil, nil),
},
},
}
@ -119,11 +100,7 @@ func TestImageConvertNydusVerify(t *testing.T) {
const remoteImageKey = "remoteImageKey"
var reg *registry.Server
// It is unclear what is problematic here, but we use the kernel version to discriminate against EL
// See: https://github.com/containerd/nerdctl/issues/4332
testutil.RequireKernelVersion(t, ">= 6.0.0-0")
var registry *testregistry.RegistryServer
testCase := &test.Case{
Require: require.All(
@ -133,30 +110,26 @@ func TestImageConvertNydusVerify(t *testing.T) {
require.Binary("nydusd"),
require.Not(nerdtest.Docker),
nerdtest.Rootful,
nerdtest.Registry,
),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
reg.Setup(data, helpers)
data.Labels().Set(remoteImageKey, fmt.Sprintf("%s:%d/nydusd-image:test", "localhost", reg.Port))
base := testutil.NewBase(t)
registry = testregistry.NewWithNoAuth(base, 0, false)
data.Labels().Set(remoteImageKey, fmt.Sprintf("%s:%d/nydusd-image:test", "localhost", registry.Port))
helpers.Ensure("image", "convert", "--nydus", "--oci", testutil.CommonImage, data.Identifier("converted-image"))
helpers.Ensure("tag", data.Identifier("converted-image"), data.Labels().Get(remoteImageKey))
helpers.Ensure("push", data.Labels().Get(remoteImageKey))
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rmi", "-f", data.Identifier("converted-image"))
if reg != nil {
reg.Cleanup(data, helpers)
if registry != nil {
registry.Cleanup(nil)
helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey))
}
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.Custom("nydusify",
return helpers.Custom("nydusify",
"check",
"--work-dir",
data.Temp().Dir("nydusify-temp"),
"--source",
testutil.CommonImage,
"--target",
@ -164,8 +137,6 @@ func TestImageConvertNydusVerify(t *testing.T) {
"--source-insecure",
"--target-insecure",
)
cmd.WithTimeout(30 * time.Second)
return cmd
},
Expected: test.Expects(0, nil, nil),
}

View File

@ -28,13 +28,13 @@ import (
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
)
func TestImageEncryptJWE(t *testing.T) {
nerdtest.Setup()
var reg *registry.Server
var registry *testregistry.RegistryServer
const remoteImageKey = "remoteImageKey"
@ -44,14 +44,12 @@ func TestImageEncryptJWE(t *testing.T) {
require.Not(nerdtest.Docker),
// This test needs to rmi the common image
nerdtest.Private,
nerdtest.Registry,
),
Cleanup: func(data test.Data, helpers test.Helpers) {
if reg != nil {
reg.Cleanup(data, helpers)
if registry != nil {
registry.Cleanup(nil)
helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey))
}
helpers.Anyhow("rmi", "-f", data.Identifier("decrypted"))
},
Setup: func(data test.Data, helpers test.Helpers) {
@ -59,11 +57,10 @@ func TestImageEncryptJWE(t *testing.T) {
data.Labels().Set("private", pri)
data.Labels().Set("public", pub)
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
reg.Setup(data, helpers)
base := testutil.NewBase(t)
registry = testregistry.NewWithNoAuth(base, 0, false)
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
encryptImageRef := fmt.Sprintf("127.0.0.1:%d/%s:encrypted", reg.Port, data.Identifier())
encryptImageRef := fmt.Sprintf("127.0.0.1:%d/%s:encrypted", registry.Port, data.Identifier())
helpers.Ensure("image", "encrypt", "--recipient=jwe:"+pub, testutil.CommonImage, encryptImageRef)
inspector := helpers.Capture("image", "inspect", "--mode=native", "--format={{len .Index.Manifests}}", encryptImageRef)
assert.Equal(t, inspector, "1\n")

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/formatter"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -44,43 +43,6 @@ type historyObj struct {
Comment string
}
const createdAt1 = "2021-03-31T10:21:21-07:00"
const createdAt2 = "2021-03-31T10:21:23-07:00"
// Expected content of the common image on arm64
var (
createdAtTime, _ = time.Parse(time.RFC3339, createdAt2)
expectedHistory = []historyObj{
{
CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
Size: "0B",
CreatedAt: createdAt2,
Snapshot: "<missing>",
Comment: "",
CreatedSince: formatter.TimeSinceInHuman(createdAtTime),
},
{
CreatedBy: "/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5…",
Size: "5.947MB",
CreatedAt: createdAt1,
Snapshot: "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c…",
Comment: "",
CreatedSince: formatter.TimeSinceInHuman(createdAtTime),
},
}
expectedHistoryNoTrunc = []historyObj{
{
Snapshot: "<missing>",
Size: "0",
},
{
Snapshot: "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a",
CreatedBy: "/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5db152fcc582aaccd9e1ec9e3343874e9969a205550fe07d in / ",
Size: "5947392",
},
}
)
func decode(stdout string) ([]historyObj, error) {
dec := json.NewDecoder(strings.NewReader(stdout))
object := []historyObj{}
@ -128,65 +90,65 @@ func TestImageHistory(t *testing.T) {
{
Description: "trunc, no quiet, human",
Command: test.Command("image", "history", "--human=true", "--format=json", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail")
assert.Equal(t, len(history), 2, "history should be 2 in length")
assert.NilError(t, err, info)
assert.Equal(t, len(history), 2, info)
h0Time, _ := time.Parse(time.RFC3339, history[0].CreatedAt)
h1Time, _ := time.Parse(time.RFC3339, history[1].CreatedAt)
comp0Time, _ := time.Parse(time.RFC3339, expectedHistory[0].CreatedAt)
comp1Time, _ := time.Parse(time.RFC3339, expectedHistory[1].CreatedAt)
localTimeL1, _ := time.Parse(time.RFC3339, "2021-03-31T10:21:23-07:00")
localTimeL2, _ := time.Parse(time.RFC3339, "2021-03-31T10:21:21-07:00")
compTime1, _ := time.Parse(time.RFC3339, history[0].CreatedAt)
compTime2, _ := time.Parse(time.RFC3339, history[1].CreatedAt)
assert.Equal(t, compTime1.UTC().String(), localTimeL1.UTC().String(), info)
assert.Equal(t, history[0].CreatedBy, "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", info)
assert.Equal(t, compTime2.UTC().String(), localTimeL2.UTC().String(), info)
assert.Equal(t, history[1].CreatedBy, "/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5…", info)
assert.Equal(t, h0Time.UTC().String(), comp0Time.UTC().String())
assert.Equal(t, history[0].CreatedBy, expectedHistory[0].CreatedBy)
assert.Equal(t, history[0].Size, expectedHistory[0].Size)
assert.Equal(t, history[0].CreatedSince, expectedHistory[0].CreatedSince)
assert.Equal(t, history[0].Snapshot, expectedHistory[0].Snapshot)
assert.Equal(t, history[0].Comment, expectedHistory[0].Comment)
assert.Equal(t, history[0].Size, "0B", info)
assert.Equal(t, history[0].CreatedSince, formatter.TimeSinceInHuman(compTime1), info)
assert.Equal(t, history[0].Snapshot, "<missing>", info)
assert.Equal(t, history[0].Comment, "", info)
assert.Equal(t, h1Time.UTC().String(), comp1Time.UTC().String())
assert.Equal(t, history[1].CreatedBy, expectedHistory[1].CreatedBy)
assert.Equal(t, history[1].Size, expectedHistory[1].Size)
assert.Equal(t, history[1].CreatedSince, expectedHistory[1].CreatedSince)
assert.Equal(t, history[1].Snapshot, expectedHistory[1].Snapshot)
assert.Equal(t, history[1].Comment, expectedHistory[1].Comment)
assert.Equal(t, history[1].Size, "5.947MB", info)
assert.Equal(t, history[1].CreatedSince, formatter.TimeSinceInHuman(compTime2), info)
assert.Equal(t, history[1].Snapshot, "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c…", info)
assert.Equal(t, history[1].Comment, "", info)
}),
},
{
Description: "no human - dates and sizes are not prettyfied",
Description: "no human - dates and sizes and not prettyfied",
Command: test.Command("image", "history", "--human=false", "--format=json", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail")
assert.Equal(t, history[0].Size, expectedHistoryNoTrunc[0].Size)
assert.Equal(t, history[0].CreatedSince, history[0].CreatedAt)
assert.Equal(t, history[1].Size, expectedHistoryNoTrunc[1].Size)
assert.Equal(t, history[1].CreatedSince, history[1].CreatedAt)
assert.NilError(t, err, info)
assert.Equal(t, history[0].Size, "0", info)
assert.Equal(t, history[0].CreatedSince, history[0].CreatedAt, info)
assert.Equal(t, history[1].Size, "5947392", info)
assert.Equal(t, history[1].CreatedSince, history[1].CreatedAt, info)
}),
},
{
Description: "no trunc - do not truncate sha or cmd",
Command: test.Command("image", "history", "--human=false", "--no-trunc", "--format=json", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail")
assert.Equal(t, history[1].Snapshot, expectedHistoryNoTrunc[1].Snapshot)
assert.Equal(t, history[1].CreatedBy, expectedHistoryNoTrunc[1].CreatedBy)
assert.NilError(t, err, info)
assert.Equal(t, history[1].Snapshot, "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a")
assert.Equal(t, history[1].CreatedBy, "/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5db152fcc582aaccd9e1ec9e3343874e9969a205550fe07d in / ")
}),
},
{
Description: "Quiet has no effect with format, so, go no-json, no-trunc",
Command: test.Command("image", "history", "--human=false", "--no-trunc", "--quiet", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
assert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+"\n"+expectedHistoryNoTrunc[1].Snapshot+"\n")
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
assert.Equal(t, stdout, "<missing>\nsha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a\n")
}),
},
{
Description: "With quiet, trunc has no effect",
Command: test.Command("image", "history", "--human=false", "--no-trunc", "--quiet", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
assert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+"\n"+expectedHistoryNoTrunc[1].Snapshot+"\n")
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
assert.Equal(t, stdout, "<missing>\nsha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a\n")
}),
},
},

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -46,14 +45,14 @@ func TestImageInspectSimpleCases(t *testing.T) {
{
Description: "Contains some stuff",
Command: test.Command("image", "inspect", testutil.CommonImage),
Expected: test.Expects(0, nil, func(stdout string, t tig.T) {
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
assert.Assert(t, len(dc[0].RootFS.Layers) > 0, "there should be at least one rootfs layer\n")
assert.Assert(t, dc[0].Architecture != "", "architecture should be set\n")
assert.Assert(t, dc[0].Size > 0, "size should be > 0 \n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
assert.Assert(t, len(dc[0].RootFS.Layers) > 0, info)
assert.Assert(t, dc[0].Architecture != "", info)
assert.Assert(t, dc[0].Size > 0, info)
}),
},
{
@ -116,11 +115,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
reference := dc[0].ID
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
@ -141,11 +140,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
reference := dc[0].ID
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
@ -174,11 +173,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
for _, id := range []string{"doesnotexist", "doesnotexist:either", "busybox:bogustag"} {
@ -197,11 +196,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
for _, id := range []string{"∞∞∞∞∞∞∞∞∞∞", "busybox:∞∞∞∞∞∞∞∞∞∞"} {
cmd := helpers.Command("image", "inspect", id)
@ -219,11 +218,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, 2, len(dc), "Unexpectedly did not get 2 results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 2, len(dc), "Unexpectedly did not get 2 results\n"+info)
reference := nerdtest.InspectImage(helpers, "busybox")
assert.Equal(t, dc[0].ID, reference.ID)
assert.Equal(t, dc[1].ID, reference.ID)

View File

@ -19,7 +19,8 @@ package image
import (
"errors"
"fmt"
"regexp"
"os"
"path/filepath"
"runtime"
"slices"
"strings"
@ -30,9 +31,7 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
"github.com/containerd/nerdctl/v2/pkg/tabutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -41,12 +40,10 @@ import (
func TestImages(t *testing.T) {
nerdtest.Setup()
commonImage, _ := referenceutil.Parse(testutil.CommonImage)
testCase := &test.Case{
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", commonImage.String())
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
helpers.Ensure("pull", "--quiet", testutil.NginxAlpineImage)
},
SubTests: []*test.Case{
@ -55,53 +52,53 @@ func TestImages(t *testing.T) {
Command: test.Command("images"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) >= 2, "there should be at least two lines\n")
assert.Assert(t, len(lines) >= 2, info)
header := "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tPLATFORM\tSIZE\tBLOB SIZE"
if nerdtest.IsDocker() {
header = "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE"
}
tab := tabutil.NewReader(header)
err := tab.ParseHeader(lines[0])
assert.NilError(t, err, "ParseHeader should not fail\n")
assert.NilError(t, err, info)
found := false
for _, line := range lines[1:] {
repo, _ := tab.ReadRow(line, "REPOSITORY")
tag, _ := tab.ReadRow(line, "TAG")
if repo+":"+tag == commonImage.FamiliarName()+":"+commonImage.Tag {
if repo+":"+tag == testutil.CommonImage {
found = true
break
}
}
assert.Assert(t, found, "we should have found an image\n")
assert.Assert(t, found, info)
},
}
},
},
{
Description: "With names",
Command: test.Command("images", "--names", commonImage.String()),
Command: test.Command("images", "--names", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
expect.Contains(commonImage.String()),
func(stdout string, t tig.T) {
expect.Contains(testutil.CommonImage),
func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) >= 2, "there should be at least two lines\n")
assert.Assert(t, len(lines) >= 2, info)
tab := tabutil.NewReader("NAME\tIMAGE ID\tCREATED\tPLATFORM\tSIZE\tBLOB SIZE")
err := tab.ParseHeader(lines[0])
assert.NilError(t, err, "ParseHeader should not fail\n")
assert.NilError(t, err, info)
found := false
for _, line := range lines[1:] {
name, _ := tab.ReadRow(line, "NAME")
if name == commonImage.String() {
if name == testutil.CommonImage {
found = true
break
}
}
assert.Assert(t, found, "we should have found an image\n")
assert.Assert(t, found, info)
},
),
}
@ -112,12 +109,12 @@ func TestImages(t *testing.T) {
Command: test.Command("images", "--format", "'{{json .CreatedAt}}'"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) >= 2, "there should be at least two lines\n")
assert.Assert(t, len(lines) >= 2, info)
createdTimes := lines
slices.Reverse(createdTimes)
assert.Assert(t, slices.IsSorted(createdTimes), "created times should be sorted\n")
assert.Assert(t, slices.IsSorted(createdTimes), info)
},
}
},
@ -138,23 +135,22 @@ func TestImages(t *testing.T) {
func TestImagesFilter(t *testing.T) {
nerdtest.Setup()
commonImage, _ := referenceutil.Parse(testutil.CommonImage)
testCase := &test.Case{
Require: nerdtest.Build,
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", commonImage.String())
helpers.Ensure("tag", commonImage.String(), "taggedimage:one-fragment-one")
helpers.Ensure("tag", commonImage.String(), "taggedimage:two-fragment-two")
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
helpers.Ensure("tag", testutil.CommonImage, "taggedimage:one-fragment-one")
helpers.Ensure("tag", testutil.CommonImage, "taggedimage:two-fragment-two")
dockerfile := fmt.Sprintf(`FROM %s
CMD ["echo", "nerdctl-build-test-string"] \n
LABEL foo=bar
LABEL version=0.1
RUN echo "actually creating a layer so that docker sets the createdAt time"
`, commonImage.String())
`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
data.Labels().Set("buildCtx", buildCtx)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
@ -241,45 +237,47 @@ RUN echo "actually creating a layer so that docker sets the createdAt time"
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
expect.Contains(commonImage.FamiliarName(), commonImage.Tag),
expect.Contains(testutil.ImageRepo(testutil.CommonImage)),
expect.DoesNotContain(data.Labels().Get("builtImageID")),
),
}
},
},
{
Description: "since=" + commonImage.String(),
Command: test.Command("images", "--filter", fmt.Sprintf("since=%s", commonImage.String())),
Description: "since=" + testutil.CommonImage,
Command: test.Command("images", "--filter", fmt.Sprintf("since=%s", testutil.CommonImage)),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
expect.Contains(data.Labels().Get("builtImageID")),
expect.DoesNotMatch(regexp.MustCompile(commonImage.FamiliarName()+"[\\s]+"+commonImage.Tag)),
expect.DoesNotContain(testutil.ImageRepo(testutil.CommonImage)),
),
}
},
},
{
Description: "since=" + commonImage.String() + " " + commonImage.String(),
Command: test.Command("images", "--filter", fmt.Sprintf("since=%s", commonImage.String()), commonImage.String()),
Description: "since=" + testutil.CommonImage + " " + testutil.CommonImage,
Command: test.Command("images", "--filter", fmt.Sprintf("since=%s", testutil.CommonImage), testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
expect.DoesNotContain(data.Labels().Get("builtImageID")),
expect.DoesNotMatch(regexp.MustCompile(commonImage.FamiliarName()+"[\\s]+"+commonImage.Tag)),
Output: expect.DoesNotContain(
data.Labels().Get("builtImageID"),
testutil.ImageRepo(testutil.CommonImage),
),
}
},
},
{
Description: "since=non-exists-image",
Require: nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/3511"),
Command: test.Command("images", "--filter", "since=non-exists-image"),
Expected: test.Expects(expect.ExitCodeGenericFail, []error{errors.New("no such image: ")}, nil),
Expected: test.Expects(expect.ExitCodeGenericFail, []error{errors.New("No such image: ")}, nil),
},
{
Description: "before=non-exists-image",
Require: nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/3511"),
Command: test.Command("images", "--filter", "before=non-exists-image"),
Expected: test.Expects(expect.ExitCodeGenericFail, []error{errors.New("no such image: ")}, nil),
Expected: test.Expects(expect.ExitCodeGenericFail, []error{errors.New("No such image: ")}, nil),
},
},
}
@ -300,7 +298,8 @@ func TestImagesFilterDangling(t *testing.T) {
CMD ["echo", "nerdctl-build-notag-string"]
`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
data.Labels().Set("buildCtx", buildCtx)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
@ -344,7 +343,7 @@ func TestImagesKubeWithKubeHideDupe(t *testing.T) {
Command: test.Command("--kube-hide-dupe", "images"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var imageID string
var skipLine int
lines := strings.Split(strings.TrimSpace(stdout), "\n")
@ -354,7 +353,7 @@ func TestImagesKubeWithKubeHideDupe(t *testing.T) {
}
tab := tabutil.NewReader(header)
err := tab.ParseHeader(lines[0])
assert.NilError(t, err, "ParseHeader should not fail\n")
assert.NilError(t, err, info)
found := true
for i, line := range lines[1:] {
repo, _ := tab.ReadRow(line, "REPOSITORY")
@ -375,7 +374,7 @@ func TestImagesKubeWithKubeHideDupe(t *testing.T) {
break
}
}
assert.Assert(t, found, "We should have found the image\n")
assert.Assert(t, found, info)
},
}
},

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -62,7 +61,7 @@ func TestLoadStdinFromPipe(t *testing.T) {
return &test.Expected{
Output: expect.All(
expect.Contains(fmt.Sprintf("Loaded image: %s:latest", identifier)),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
assert.Assert(t, strings.Contains(helpers.Capture("images"), identifier))
},
),

View File

@ -18,6 +18,8 @@ package image
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
@ -27,7 +29,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -71,7 +72,8 @@ func TestImagePrune(t *testing.T) {
`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
helpers.Ensure("build", buildCtx)
// After we rebuild with tag, docker will no longer show the <none> version from above
// Swapping order does not change anything.
@ -85,13 +87,13 @@ func TestImagePrune(t *testing.T) {
identifier := data.Identifier()
return &test.Expected{
Output: expect.All(
func(stdout string, t tig.T) {
assert.Assert(t, !strings.Contains(stdout, identifier))
func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, identifier), info)
},
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images")
assert.Assert(t, !strings.Contains(imgList, "<none>"), imgList)
assert.Assert(t, strings.Contains(imgList, identifier))
assert.Assert(t, strings.Contains(imgList, identifier), info)
},
),
}
@ -118,7 +120,8 @@ func TestImagePrune(t *testing.T) {
`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
helpers.Ensure("build", buildCtx)
helpers.Ensure("build", "-t", identifier, buildCtx)
imgList := helpers.Capture("images")
@ -130,18 +133,18 @@ func TestImagePrune(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
func(stdout string, t tig.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier()))
func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier()), info)
},
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()))
assert.Assert(t, strings.Contains(imgList, data.Identifier()), info)
assert.Assert(t, !strings.Contains(imgList, "<none>"), imgList)
helpers.Ensure("rm", "-f", data.Identifier())
removed := helpers.Capture("image", "prune", "--force", "--all")
assert.Assert(t, strings.Contains(removed, data.Identifier()))
assert.Assert(t, strings.Contains(removed, data.Identifier()), info)
imgList = helpers.Capture("images")
assert.Assert(t, !strings.Contains(imgList, data.Identifier()))
assert.Assert(t, !strings.Contains(imgList, data.Identifier()), info)
},
),
}
@ -161,7 +164,8 @@ CMD ["echo", "nerdctl-test-image-prune-filter-label"]
LABEL foo=bar
LABEL version=0.1`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
helpers.Ensure("build", "-t", data.Identifier(), buildCtx)
imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()), "Missing "+data.Identifier())
@ -170,18 +174,18 @@ LABEL version=0.1`, testutil.CommonImage)
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: expect.All(
func(stdout string, t tig.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier()))
func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier()), info)
},
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()))
assert.Assert(t, strings.Contains(imgList, data.Identifier()), info)
},
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
prune := helpers.Capture("image", "prune", "--force", "--all", "--filter", "label=foo=bar")
assert.Assert(t, strings.Contains(prune, data.Identifier()))
assert.Assert(t, strings.Contains(prune, data.Identifier()), info)
imgList := helpers.Capture("images")
assert.Assert(t, !strings.Contains(imgList, data.Identifier()))
assert.Assert(t, !strings.Contains(imgList, data.Identifier()), info)
},
),
}
@ -200,7 +204,8 @@ LABEL version=0.1`, testutil.CommonImage)
RUN echo "Anything, so that we create actual content for docker to set the current time for CreatedAt"
CMD ["echo", "nerdctl-test-image-prune-until"]`, testutil.CommonImage)
buildCtx := data.Temp().Path()
data.Temp().Save(dockerfile, "Dockerfile")
err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600)
assert.NilError(helpers.T(), err)
helpers.Ensure("build", "-t", data.Identifier(), buildCtx)
imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()), "Missing "+data.Identifier())
@ -211,9 +216,9 @@ CMD ["echo", "nerdctl-test-image-prune-until"]`, testutil.CommonImage)
return &test.Expected{
Output: expect.All(
expect.DoesNotContain(data.Labels().Get("imageID")),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Labels().Get("imageID")))
assert.Assert(t, strings.Contains(imgList, data.Labels().Get("imageID")), info)
},
),
}
@ -230,9 +235,9 @@ CMD ["echo", "nerdctl-test-image-prune-until"]`, testutil.CommonImage)
return &test.Expected{
Output: expect.All(
expect.Contains(data.Labels().Get("imageID")),
func(stdout string, t tig.T) {
func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images")
assert.Assert(t, !strings.Contains(imgList, data.Labels().Get("imageID")), imgList)
assert.Assert(t, !strings.Contains(imgList, data.Labels().Get("imageID")), imgList, info)
},
),
}

View File

@ -27,7 +27,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -130,8 +129,8 @@ CMD ["echo", "nerdctl-build-test-string"]
data.Temp().Save(dockerfile, "Dockerfile")
reg = nerdtest.RegistryWithNoAuth(data, helpers, 80, false)
reg.Setup(data, helpers)
testImageRef := fmt.Sprintf("%s/%s",
reg.IP.String(), data.Identifier())
testImageRef := fmt.Sprintf("%s/%s:%s",
reg.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
buildCtx := data.Temp().Path()
helpers.Ensure("build", "-t", testImageRef, buildCtx)
@ -183,7 +182,7 @@ func TestImagePullSoci(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Custom("mount")
cmd.Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
data.Labels().Set("remoteSnapshotsInitialCount", strconv.Itoa(strings.Count(stdout, "fuse.rawBridge")))
},
})
@ -197,7 +196,7 @@ func TestImagePullSoci(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, _ string, t *testing.T) {
remoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get("remoteSnapshotsInitialCount"))
remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge")
assert.Equal(t,
@ -219,7 +218,7 @@ func TestImagePullSoci(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Custom("mount")
cmd.Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
data.Labels().Set("remoteSnapshotsInitialCount", strconv.Itoa(strings.Count(stdout, "fuse.rawBridge")))
},
})
@ -233,7 +232,7 @@ func TestImagePullSoci(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
remoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get("remoteSnapshotsInitialCount"))
remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge")
assert.Equal(t,

View File

@ -20,53 +20,43 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"testing"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
)
func TestPush(t *testing.T) {
nerdtest.Setup()
var registryNoAuthHTTPRandom, registryNoAuthHTTPDefault, registryTokenAuthHTTPSRandom *registry.Server
var tokenServer *registry.TokenAuthServer
var registryNoAuthHTTPRandom, registryNoAuthHTTPDefault, registryTokenAuthHTTPSRandom *testregistry.RegistryServer
testCase := &test.Case{
Require: require.All(
require.Linux,
nerdtest.Registry,
),
Require: require.Linux,
Setup: func(data test.Data, helpers test.Helpers) {
registryNoAuthHTTPRandom = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
registryNoAuthHTTPRandom.Setup(data, helpers)
registryNoAuthHTTPDefault = nerdtest.RegistryWithNoAuth(data, helpers, 80, false)
registryNoAuthHTTPDefault.Setup(data, helpers)
registryTokenAuthHTTPSRandom, tokenServer = nerdtest.RegistryWithTokenAuth(data, helpers, "admin", "badmin", 0, true)
tokenServer.Setup(data, helpers)
registryTokenAuthHTTPSRandom.Setup(data, helpers)
base := testutil.NewBase(t)
registryNoAuthHTTPRandom = testregistry.NewWithNoAuth(base, 0, false)
registryNoAuthHTTPDefault = testregistry.NewWithNoAuth(base, 80, false)
registryTokenAuthHTTPSRandom = testregistry.NewWithTokenAuth(base, "admin", "badmin", 0, true)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
if registryNoAuthHTTPRandom != nil {
registryNoAuthHTTPRandom.Cleanup(data, helpers)
registryNoAuthHTTPRandom.Cleanup(nil)
}
if registryNoAuthHTTPDefault != nil {
registryNoAuthHTTPDefault.Cleanup(data, helpers)
registryNoAuthHTTPDefault.Cleanup(nil)
}
if registryTokenAuthHTTPSRandom != nil {
registryTokenAuthHTTPSRandom.Cleanup(data, helpers)
}
if tokenServer != nil {
tokenServer.Cleanup(data, helpers)
registryTokenAuthHTTPSRandom.Cleanup(nil)
}
},
@ -75,8 +65,8 @@ func TestPush(t *testing.T) {
Description: "plain http",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
},
@ -95,8 +85,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
},
@ -114,8 +104,8 @@ func TestPush(t *testing.T) {
Description: "plain http with localhost",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
"127.0.0.1", registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
"127.0.0.1", registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
},
@ -129,8 +119,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s/%s",
registryNoAuthHTTPDefault.IP.String(), data.Identifier())
testImageRef := fmt.Sprintf("%s/%s:%s",
registryNoAuthHTTPDefault.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
},
@ -149,8 +139,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
helpers.Ensure("--insecure-registry", "login", "-u", "admin", "-p", "badmin",
@ -172,8 +162,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef)
helpers.Ensure("--hosts-dir", registryTokenAuthHTTPSRandom.HostsDir, "login", "-u", "admin", "-p", "badmin",
@ -195,8 +185,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.NonDistBlobImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.NonDistBlobImage, testImageRef)
},
@ -210,12 +200,12 @@ func TestPush(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
blobURL := fmt.Sprintf("http://%s:%d/v2/%s/blobs/%s", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)
resp, err := http.Get(blobURL)
assert.Assert(t, err, "error making http request")
if resp.Body != nil {
_ = resp.Body.Close()
resp.Body.Close()
}
assert.Equal(t, resp.StatusCode, http.StatusNotFound, "non-distributable blob should not be available")
},
@ -227,8 +217,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.NonDistBlobImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.NonDistBlobImage, testImageRef)
},
@ -242,12 +232,12 @@ func TestPush(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
blobURL := fmt.Sprintf("http://%s:%d/v2/%s/blobs/%s", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)
resp, err := http.Get(blobURL)
assert.Assert(t, err, "error making http request")
if resp.Body != nil {
_ = resp.Body.Close()
resp.Body.Close()
}
assert.Equal(t, resp.StatusCode, http.StatusOK, "non-distributable blob should be available")
},
@ -262,8 +252,8 @@ func TestPush(t *testing.T) {
),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.UbuntuImage)
testImageRef := fmt.Sprintf("%s:%d/%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())
testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.UbuntuImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.UbuntuImage, testImageRef)
},

View File

@ -26,7 +26,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/imgutil"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -64,7 +63,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("image is being used")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName),
})
@ -84,7 +83,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.DoesNotContain(repoName),
})
@ -109,7 +108,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("image is being used")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName),
})
@ -141,7 +140,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains("<none>"),
})
@ -163,7 +162,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("image is being used")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName),
})
@ -185,7 +184,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
// a created container with removed image doesn't impact other `rmi` command
Output: expect.DoesNotContain(repoName, nginxRepoName),
@ -213,7 +212,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("image is being used")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName),
})
@ -247,7 +246,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains("<none>"),
})
@ -273,7 +272,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("image is being used")},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName),
})
@ -294,7 +293,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{
Output: expect.DoesNotContain(repoName),
})
@ -337,10 +336,10 @@ func TestIssue3016(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images", data.Labels().Get(tagIDKey)).Run(&test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
assert.Equal(t, len(strings.Split(stdout, "\n")), 2)
},
})
@ -379,17 +378,17 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("--kube-hide-dupe", "images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numTags+1)
assert.Assert(t, len(lines) == numTags+1, info)
},
})
helpers.Command("images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numNoTags+1)
assert.Assert(t, len(lines) == numNoTags+1, info)
},
})
},
@ -411,17 +410,17 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("--kube-hide-dupe", "images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numTags+1)
assert.Assert(t, len(lines) == numTags+1, info)
},
})
helpers.Command("images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numNoTags+2)
assert.Assert(t, len(lines) == numNoTags+2, info)
},
})
},
@ -441,17 +440,17 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("--kube-hide-dupe", "images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numTags)
assert.Assert(t, len(lines) == numTags, info)
},
})
helpers.Command("images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numNoTags)
assert.Assert(t, len(lines) == numNoTags, info)
},
})
},
@ -470,7 +469,7 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
helpers.Command("--kube-hide-dupe", "rmi", stdout[0:12]).Run(&test.Expected{
ExitCode: 1,
Errors: []error{errors.New("multiple IDs found with provided prefix: ")},
@ -479,9 +478,9 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
ExitCode: 0,
})
helpers.Command("images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numNoTags)
assert.Assert(t, len(lines) == numNoTags, info)
},
})
},
@ -500,7 +499,7 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
imgID := strings.Split(stdout, "\n")
helpers.Command("--kube-hide-dupe", "rmi", imgID[0]).Run(&test.Expected{
ExitCode: 1,
@ -510,9 +509,9 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
ExitCode: 0,
})
helpers.Command("images").Run(&test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
lines := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Assert(t, len(lines) == numNoTags)
assert.Assert(t, len(lines) == numNoTags, info)
},
})
},

View File

@ -32,7 +32,7 @@ import (
func SaveCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "save [flags] IMAGE [IMAGE...]",
Use: "save",
Args: cobra.MinimumNArgs(1),
Short: "Save one or more images to a tar archive (streamed to STDOUT by default)",
Long: "The archive implements both Docker Image Spec v1.2 and OCI Image Spec v1.0.",

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -49,7 +48,7 @@ func TestSaveContent(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
rootfsPath := filepath.Join(data.Temp().Path(), "rootfs")
err := testhelpers.ExtractDockerArchive(filepath.Join(data.Temp().Path(), "out.tar"), rootfsPath)
assert.NilError(t, err)
@ -189,7 +188,7 @@ func TestSaveMultipleImagesWithSameIDAndLoad(t *testing.T) {
return &test.Expected{
ExitCode: 0,
Errors: []error{},
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
assert.Equal(t, strings.Count(stdout, data.Labels().Get("id")), 2)
},
}

View File

@ -23,7 +23,6 @@ import (
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -51,23 +50,23 @@ func TestInspectSimpleCase(t *testing.T) {
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
Output: func(stdout string, t tig.T) {
Output: func(stdout string, info string, t *testing.T) {
var inspectResult []json.RawMessage
err := json.Unmarshal([]byte(stdout), &inspectResult)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.Equal(t, len(inspectResult), 2, "Unexpectedly got multiple results\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, len(inspectResult), 2, "Unexpectedly got multiple results\n"+info)
var dci dockercompat.Image
err = json.Unmarshal(inspectResult[0], &dci)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
inspecti := nerdtest.InspectImage(helpers, testutil.CommonImage)
assert.Equal(t, dci.ID, inspecti.ID, "id should match\n")
assert.Equal(t, dci.ID, inspecti.ID, info)
var dcc dockercompat.Container
err = json.Unmarshal(inspectResult[1], &dcc)
assert.NilError(t, err, "Unable to unmarshal output\n")
assert.NilError(t, err, "Unable to unmarshal output\n"+info)
inspectc := nerdtest.InspectContainer(helpers, data.Identifier())
assert.Equal(t, dcc.ID, inspectc.ID, "id should match\n")
assert.Assert(t, dcc.ID == inspectc.ID, info)
},
}
},

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