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 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx - 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 # Login against a Docker registry except on PR
# https://github.com/docker/login-action # https://github.com/docker/login-action
@ -61,12 +61,10 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR) # Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - 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: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
secrets: |
github_token=${{ secrets.GITHUB_TOKEN }}

View File

@ -70,8 +70,6 @@ jobs:
local goarm="${3:-}" local goarm="${3:-}"
local result local result
GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" go build ./examples/...
github::timer::begin github::timer::begin
GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" make binaries \ GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" make binaries \
@ -99,12 +97,3 @@ jobs:
build linux s390x build linux s390x
[ ! "$failure" ] || exit 1 [ ! "$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 uses: crazy-max/ghaction-github-runtime@3cb05d89e1f492524af3d41a1c98c83bc3025124 # v3.1.0
- name: "Run: build dependencies for the integration test environment image" - name: "Run: build dependencies for the integration test environment image"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
# Cache is sharded per-architecture # Cache is sharded per-architecture
arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }} arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }}
@ -51,7 +49,6 @@ jobs:
args=(--build-arg CONTAINERD_VERSION=${{ inputs.containerd-version }}) args=(--build-arg CONTAINERD_VERSION=${{ inputs.containerd-version }})
fi fi
docker buildx build \ docker buildx build \
--secret id=github_token,env=GITHUB_TOKEN \
--cache-to type=gha,compression=zstd,mode=max,scope=test-integration-dependencies-"$arch" \ --cache-to type=gha,compression=zstd,mode=max,scope=test-integration-dependencies-"$arch" \
--cache-from type=gha,scope=test-integration-dependencies-"$arch" \ --cache-from type=gha,scope=test-integration-dependencies-"$arch" \
--target build-dependencies "${args[@]}" . --target build-dependencies "${args[@]}" .

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,6 @@ linters:
- revive - revive
# Gocritic # Gocritic
- gocritic - gocritic
- forbidigo
# 3. We used to use these, but have now removed them # 3. We used to use these, but have now removed them
@ -42,15 +41,6 @@ linters:
# - nakedret # - nakedret
settings: 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: staticcheck:
checks: checks:
# Below is the default set # Below is the default set
@ -63,6 +53,9 @@ linters:
- "-ST1022" - "-ST1022"
##### TODO: fix and enable these ##### 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. # 6 occurrences.
# Apply De Morgans law https://staticcheck.dev/docs/checks#QF1001 # Apply De Morgans law https://staticcheck.dev/docs/checks#QF1001
- "-QF1001" - "-QF1001"
@ -121,7 +114,7 @@ linters:
arguments: [7] arguments: [7]
- name: function-length - name: function-length
# 155 occurrences (at default 0, 75). Really long functions should really be broken up in most cases. # 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 - name: cyclomatic
# 204 occurrences (at default 10) # 204 occurrences (at default 10)
arguments: [100] arguments: [100]
@ -129,7 +122,7 @@ linters:
# 222 occurrences. Could indicate failure to handle broken conditions. # 222 occurrences. Could indicate failure to handle broken conditions.
disabled: true disabled: true
- name: cognitive-complexity - name: cognitive-complexity
arguments: [205] arguments: [197]
# 441 occurrences (at default 7). We should try to lower it (involves significant refactoring). # 441 occurrences (at default 7). We should try to lower it (involves significant refactoring).
##### P2: nice to have. ##### 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 # Basic deps
# @BINARY: the binary checksums are verified via Dockerfile.d/SHA256SUMS.d/<COMPONENT>-<VERSION> # @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 RUNC_VERSION=v1.3.0@4ca628d1d4c974f92d24daccb901aa078aad748e
ARG CNI_PLUGINS_VERSION=v1.7.1@BINARY ARG CNI_PLUGINS_VERSION=v1.7.1@BINARY
# Extra deps: Build # Extra deps: Build
ARG BUILDKIT_VERSION=v0.23.2@BINARY ARG BUILDKIT_VERSION=v0.21.1@BINARY
# Extra deps: Lazy-pulling # Extra deps: Lazy-pulling
ARG STARGZ_SNAPSHOTTER_VERSION=v0.16.3@BINARY ARG STARGZ_SNAPSHOTTER_VERSION=v0.16.3@BINARY
# Extra deps: Encryption # Extra deps: Encryption
ARG IMGCRYPT_VERSION=v2.0.1@c377ec98ff79ec9205eabf555ebd2ea784738c6c ARG IMGCRYPT_VERSION=v2.0.1@c377ec98ff79ec9205eabf555ebd2ea784738c6c
# Extra deps: Rootless # Extra deps: Rootless
ARG ROOTLESSKIT_VERSION=v2.3.5@BINARY ARG ROOTLESSKIT_VERSION=v2.3.5@BINARY
ARG SLIRP4NETNS_VERSION=v1.3.3@BINARY ARG SLIRP4NETNS_VERSION=v1.3.2@BINARY
# Extra deps: bypass4netns # Extra deps: bypass4netns
ARG BYPASS4NETNS_VERSION=v0.4.2@aa04bd3dcc48c6dae6d7327ba219bda8fe2a4634 ARG BYPASS4NETNS_VERSION=v0.4.2@aa04bd3dcc48c6dae6d7327ba219bda8fe2a4634
# Extra deps: FUSE-OverlayFS # Extra deps: FUSE-OverlayFS
ARG FUSE_OVERLAYFS_VERSION=v1.15@BINARY 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 # Extra deps: Init
ARG TINI_VERSION=v0.19.0@BINARY ARG TINI_VERSION=v0.19.0@BINARY
# Extra deps: Debug # Extra deps: Debug
ARG BUILDG_VERSION=v0.5.3@BINARY ARG BUILDG_VERSION=v0.5.2@BINARY
# Extra deps: gomodjail # Extra deps: gomodjail
ARG GOMODJAIL_VERSION=v0.1.2@0a86b34442a491fa8f5e4565e9c846fce310239c ARG GOMODJAIL_VERSION=v0.1.2@0a86b34442a491fa8f5e4565e9c846fce310239c
@ -47,22 +47,19 @@ ARG GOMODJAIL_VERSION=v0.1.2@0a86b34442a491fa8f5e4565e9c846fce310239c
ARG GO_VERSION=1.24 ARG GO_VERSION=1.24
ARG UBUNTU_VERSION=24.04 ARG UBUNTU_VERSION=24.04
ARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1 ARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1
ARG GOTESTSUM_VERSION=v1.12.3 ARG GOTESTSUM_VERSION=v1.12.2
ARG NYDUS_VERSION=v2.3.2 ARG NYDUS_VERSION=v2.3.1
ARG SOCI_SNAPSHOTTER_VERSION=0.11.1 ARG SOCI_SNAPSHOTTER_VERSION=0.9.0
ARG KUBO_VERSION=v0.35.0 ARG KUBO_VERSION=v0.34.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1@sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3 AS xx 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 / / COPY --from=xx / /
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \ RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
make \
git \ git \
jq \
curl \
dpkg-dev dpkg-dev
ARG TARGETARCH ARG TARGETARCH
# libbtrfs: for containerd # libbtrfs: for containerd
@ -76,12 +73,11 @@ RUN xx-apt-get update -qq && xx-apt-get install -qq --no-install-recommends \
pkg-config pkg-config
RUN git config --global advice.detachedHead false RUN git config --global advice.detachedHead false
ADD hack/git-checkout-tag-with-hash.sh /usr/local/bin/ 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 TARGETARCH
ARG CONTAINERD_VERSION 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 WORKDIR /go/src/github.com/containerd/containerd
RUN git-checkout-tag-with-hash.sh ${CONTAINERD_VERSION} && \ RUN git-checkout-tag-with-hash.sh ${CONTAINERD_VERSION} && \
mkdir -p /out /out/$TARGETARCH && \ 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 && \ RUN GO=xx-go make STATIC=1 && \
cp -a bin/containerd bin/containerd-shim-runc-v2 bin/ctr /out/$TARGETARCH 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 RUNC_VERSION
ARG TARGETARCH 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 WORKDIR /go/src/github.com/opencontainers/runc
RUN git-checkout-tag-with-hash.sh ${RUNC_VERSION} && \ RUN git-checkout-tag-with-hash.sh ${RUNC_VERSION} && \
mkdir -p /out 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 && \ 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} 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 BYPASS4NETNS_VERSION
ARG TARGETARCH 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 WORKDIR /go/src/github.com/rootless-containers/bypass4netns
RUN git-checkout-tag-with-hash.sh ${BYPASS4NETNS_VERSION} && \ RUN git-checkout-tag-with-hash.sh ${BYPASS4NETNS_VERSION} && \
mkdir -p /out/${TARGETARCH} mkdir -p /out/${TARGETARCH}
@ -111,20 +107,20 @@ ENV CGO_ENABLED=1
RUN GO=xx-go make static && \ RUN GO=xx-go make static && \
xx-verify --static bypass4netns && cp -a bypass4netns bypass4netnsd /out/${TARGETARCH} 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 GOMODJAIL_VERSION
ARG TARGETARCH 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 WORKDIR /go/src/github.com/AkihiroSuda/gomodjail
RUN git-checkout-tag-with-hash.sh ${GOMODJAIL_VERSION} && \ RUN git-checkout-tag-with-hash.sh ${GOMODJAIL_VERSION} && \
mkdir -p /out/${TARGETARCH} mkdir -p /out/${TARGETARCH}
RUN GO=xx-go make STATIC=1 && \ RUN GO=xx-go make STATIC=1 && \
xx-verify --static _output/bin/gomodjail && cp -a _output/bin/gomodjail /out/${TARGETARCH} 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 KUBO_VERSION
ARG TARGETARCH 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 WORKDIR /go/src/github.com/ipfs/kubo
RUN git-checkout-tag-with-hash.sh ${KUBO_VERSION} && \ RUN git-checkout-tag-with-hash.sh ${KUBO_VERSION} && \
mkdir -p /out/${TARGETARCH} mkdir -p /out/${TARGETARCH}
@ -133,6 +129,11 @@ RUN xx-go --wrap && \
make build && \ make build && \
xx-verify --static cmd/ipfs/ipfs && cp -a cmd/ipfs/ipfs /out/${TARGETARCH} 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 FROM build-base AS build-minimal
RUN BINDIR=/out/bin make binaries install RUN BINDIR=/out/bin make binaries install
# We do not set CMD to `go test` here, because it requires systemd # 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 ARG CONTAINERD_VERSION
COPY --from=build-containerd /out/${TARGETARCH:-amd64}/* /out/bin/ COPY --from=build-containerd /out/${TARGETARCH:-amd64}/* /out/bin/
COPY --from=build-containerd /out/containerd.service /out/lib/systemd/system/containerd.service 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 ARG RUNC_VERSION
COPY --from=build-runc /out/runc.${TARGETARCH:-amd64} /out/bin/runc 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 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" && \ 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}" && \ 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 && \ 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}" && \ rm -f "${fname}" && \
echo "- CNI plugins: ${CNI_PLUGINS_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- CNI plugins: ${CNI_PLUGINS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BUILDKIT_VERSION ARG BUILDKIT_VERSION
RUN BUILDKIT_VERSION=${BUILDKIT_VERSION%%@*}; \ RUN BUILDKIT_VERSION=${BUILDKIT_VERSION/@BINARY}; \
fname="buildkit-${BUILDKIT_VERSION}.${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \ 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}" && \ 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 && \ grep "${fname}" "/SHA256SUMS.d/buildkit-${BUILDKIT_VERSION}" | sha256sum -c && \
@ -176,11 +177,10 @@ RUN cd /out/lib/systemd/system && \
echo "" >> buildkit.service && \ echo "" >> buildkit.service && \
echo "# This file was converted from containerd.service, with \`sed -E '${sedcomm}'\`" >> buildkit.service echo "# This file was converted from containerd.service, with \`sed -E '${sedcomm}'\`" >> buildkit.service
ARG STARGZ_SNAPSHOTTER_VERSION ARG STARGZ_SNAPSHOTTER_VERSION
RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \ RUN STARGZ_SNAPSHOTTER_VERSION=${STARGZ_SNAPSHOTTER_VERSION/@BINARY}; \
STARGZ_SNAPSHOTTER_VERSION=${STARGZ_SNAPSHOTTER_VERSION%%@*}; \
fname="stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \ 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}" && \ 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 "${fname}" "/SHA256SUMS.d/stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}" | sha256sum -c - && \
grep "stargz-snapshotter.service" "/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 && \ 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 && \ mv stargz-snapshotter.service /out/lib/systemd/system/stargz-snapshotter.service && \
echo "- Stargz Snapshotter: ${STARGZ_SNAPSHOTTER_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- Stargz Snapshotter: ${STARGZ_SNAPSHOTTER_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG IMGCRYPT_VERSION 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 && \ cd /go/src/github.com/containerd/imgcrypt && \
git-checkout-tag-with-hash.sh "${IMGCRYPT_VERSION}" && \ git-checkout-tag-with-hash.sh "${IMGCRYPT_VERSION}" && \
CGO_ENABLED=0 make && DESTDIR=/out make install && \ 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 ARG SLIRP4NETNS_VERSION
RUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION%%@*}; \ RUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION/@BINARY}; \
fname="slirp4netns-$(cat /target_uname_m)" && \ 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}" && \ 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 && \ 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 echo "- slirp4netns: ${SLIRP4NETNS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BYPASS4NETNS_VERSION ARG BYPASS4NETNS_VERSION
COPY --from=build-bypass4netns /out/${TARGETARCH:-amd64}/* /out/bin/ 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 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)" && \ 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}" && \ 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 && \ 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 && \ chmod +x /out/bin/fuse-overlayfs && \
echo "- fuse-overlayfs: ${FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- fuse-overlayfs: ${FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG CONTAINERD_FUSE_OVERLAYFS_VERSION ARG CONTAINERD_FUSE_OVERLAYFS_VERSION
RUN CONTAINERD_FUSE_OVERLAYFS_VERSION=${CONTAINERD_FUSE_OVERLAYFS_VERSION%%@*}; \ 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" && \ 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}" && \ 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 && \ grep "${fname}" "/SHA256SUMS.d/containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION}" | sha256sum -c && \
tar xzf "${fname}" -C /out/bin && \ tar xzf "${fname}" -C /out/bin && \
rm -f "${fname}" && \ rm -f "${fname}" && \
echo "- containerd-fuse-overlayfs: ${CONTAINERD_FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- containerd-fuse-overlayfs: ${CONTAINERD_FUSE_OVERLAYFS_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG TINI_VERSION ARG TINI_VERSION
RUN TINI_VERSION=${TINI_VERSION%%@*}; \ RUN TINI_VERSION=${TINI_VERSION/@BINARY}; \
fname="tini-static-${TARGETARCH:-amd64}" && \ fname="tini-static-${TARGETARCH:-amd64}" && \
curl -o "${fname}" -fsSL --proto '=https' --tlsv1.2 "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/${fname}" && \ 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 && \ grep "${fname}" "/SHA256SUMS.d/tini-${TINI_VERSION}" | sha256sum -c && \
cp -a "${fname}" /out/bin/tini && chmod +x /out/bin/tini && \ cp -a "${fname}" /out/bin/tini && chmod +x /out/bin/tini && \
echo "- Tini: ${TINI_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- Tini: ${TINI_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG BUILDG_VERSION ARG BUILDG_VERSION
# FIXME: this is a mildly-confusing approach. Buildkit will perform some "smart" replacement at build time and output RUN BUILDG_VERSION=${BUILDG_VERSION/@BINARY}; \
# confusing debugging information, eg: BUILDG_VERSION will appear as if the original ARG value was used.
RUN BUILDG_VERSION=${BUILDG_VERSION%%@*}; \
fname="buildg-${BUILDG_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \ 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}" && \ 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 && \ grep "${fname}" "/SHA256SUMS.d/buildg-${BUILDG_VERSION}" | sha256sum -c && \
@ -238,7 +236,7 @@ RUN BUILDG_VERSION=${BUILDG_VERSION%%@*}; \
rm -f "${fname}" && \ rm -f "${fname}" && \
echo "- buildg: ${BUILDG_VERSION}" >> /out/share/doc/nerdctl-full/README.md echo "- buildg: ${BUILDG_VERSION}" >> /out/share/doc/nerdctl-full/README.md
ARG ROOTLESSKIT_VERSION ARG ROOTLESSKIT_VERSION
RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION%%@*}; \ RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION/@BINARY}; \
fname="rootlesskit-$(cat /target_uname_m).tar.gz" && \ 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}" && \ 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 && \ grep "${fname}" "/SHA256SUMS.d/rootlesskit-${ROOTLESSKIT_VERSION}" | sha256sum -c && \
@ -248,17 +246,13 @@ RUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION%%@*}; \
ARG GOMODJAIL_VERSION ARG GOMODJAIL_VERSION
COPY --from=build-gomodjail /out/${TARGETARCH:-amd64}/* /out/bin/ COPY --from=build-gomodjail /out/${TARGETARCH:-amd64}/* /out/bin/
RUN echo "- gomodjail: ${GOMODJAIL_VERSION}" >> /out/share/doc/nerdctl-full/README.md 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 && \ RUN echo "" >> /out/share/doc/nerdctl-full/README.md && \
echo "## License" >> /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/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/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/{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 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 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 \ iproute2 iptables \
dbus dbus-user-session systemd systemd-sysv \ dbus dbus-user-session systemd systemd-sysv \
fuse3 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/ COPY --from=out-full / /usr/local/
RUN perl -pi -e 's/multi-user.target/docker-entrypoint.target/g' /usr/local/lib/systemd/system/*.service && \ RUN perl -pi -e 's/multi-user.target/docker-entrypoint.target/g' /usr/local/lib/systemd/system/*.service && \
systemctl enable containerd buildkit stargz-snapshotter && \ systemctl enable containerd buildkit stargz-snapshotter && \
@ -314,7 +310,7 @@ RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
git \ git \
make make
# We wouldn't need this if Docker Hub could have "golang:${GO_VERSION}-ubuntu" # 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 ARG TARGETARCH
ENV PATH=/usr/local/go/bin:$PATH ENV PATH=/usr/local/go/bin:$PATH
ARG GOTESTSUM_VERSION ARG GOTESTSUM_VERSION
@ -329,10 +325,7 @@ COPY --from=ghcr.io/sigstore/cosign/cosign:v2.2.3@sha256:8fc9cad121611e8479f65f7
ARG SOCI_SNAPSHOTTER_VERSION ARG SOCI_SNAPSHOTTER_VERSION
RUN fname="soci-snapshotter-${SOCI_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz" && \ 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}" && \ 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 && \ 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
# enable offline ipfs for integration test # enable offline ipfs for integration test
COPY --from=build-kubo /out/${TARGETARCH:-amd64}/* /usr/local/bin/ 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 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 # GitHub ID, Name, Email address, GPG fingerprint
"jsturtevant","James Sturtevant","jstur@microsoft.com","" "jsturtevant","James Sturtevant","jstur@microsoft.com",""
"manugupt1", "Manu Gupta", "manugupt1@gmail.com","FCA9 504A 4118 EA5C F466 CC30 A5C3 A8F4 E7FE 9E10" "manugupt1", "Manu Gupta", "manugupt1@gmail.com","FCA9 504A 4118 EA5C F466 CC30 A5C3 A8F4 E7FE 9E10"
"Shubhranshu153","Shubharanshu Mahapatra","shubhum@amazon.com",""
# EMERITUS # EMERITUS
# See EMERITUS.md # See EMERITUS.md

View File

@ -46,9 +46,6 @@ LINT_COMMIT_RANGE ?= main..HEAD
GO_BUILD_LDFLAGS ?= -s -w GO_BUILD_LDFLAGS ?= -s -w
GO_BUILD_FLAGS ?= GO_BUILD_FLAGS ?=
BUILDTAGS ?=
GO_TAGS=$(if $(BUILDTAGS),-tags "$(strip $(BUILDTAGS))",)
########################## ##########################
# Helpers # Helpers
########################## ##########################
@ -57,7 +54,7 @@ ifdef VERBOSE
VERBOSE_FLAG_LONG := --verbose VERBOSE_FLAG_LONG := --verbose
endif 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 ifndef NO_COLOR
NC := \033[0m NC := \033[0m
@ -185,7 +182,7 @@ lint-licenses-all:
&& GOOS=linux make lint-licenses \ && GOOS=linux make lint-licenses \
&& GOOS=windows make lint-licenses \ && GOOS=windows make lint-licenses \
&& GOOS=freebsd make lint-licenses \ && GOOS=freebsd make lint-licenses \
&& GOOS=darwin make lint-licenses && GOOS=darwin make lint-go
$(call footer, $@) $(call footer, $@)
########################## ##########################
@ -203,7 +200,7 @@ fix-go-all:
&& GOOS=linux make fix-go \ && GOOS=linux make fix-go \
&& GOOS=windows make fix-go \ && GOOS=windows make fix-go \
&& GOOS=freebsd make fix-go \ && GOOS=freebsd make fix-go \
&& GOOS=darwin make fix-go && GOOS=darwin make lint-go
$(call footer, $@) $(call footer, $@)
fix-mod: fix-mod:
@ -221,14 +218,12 @@ install-dev-tools:
# git-validation: main (2025-02-25) # git-validation: main (2025-02-25)
# ltag: main (2025-03-04) # ltag: main (2025-03-04)
# go-licenses: v2.0.0-alpha.1 (2024-06-27) # 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) \ @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/golangci/golangci-lint/v2/cmd/golangci-lint@2b224c2cf4c9f261c22a16af7f8ca6408467f338 \
&& go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \ && go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \
&& go install github.com/containerd/ltag@66e6a514664ee2d11a470735519fa22b1a9eaabd \ && 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" @echo "Remember to add \$$HOME/go/bin to your path"
$(call footer, $@) $(call footer, $@)
@ -258,7 +253,7 @@ TAR_OWNER0_FLAGS=--owner=0 --group=0
TAR_FLATTEN_FLAGS=--transform 's/.*\///g' TAR_FLATTEN_FLAGS=--transform 's/.*\///g'
define make_artifact_full_linux 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 gzip -9 $(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar
endef endef

View File

@ -19,6 +19,7 @@ package builder
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
@ -262,6 +263,13 @@ func GetBuildkitHost(cmd *cobra.Command, namespace string) (string, error) {
return buildkitHost, nil 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) return buildkitutil.GetBuildkitHost(namespace)
} }

View File

@ -27,7 +27,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
assert.Assert( assert.Assert(
t, t,
strings.Contains( strings.Contains(
helpers.Capture("run", "--rm", data.Identifier("child")), helpers.Capture("run", "--rm", data.Identifier("child")),
"test-nerdctl-build-context-oci-layout", "test-nerdctl-build-context-oci-layout",
), ),
info,
) )
}, },
} }

View File

@ -19,9 +19,7 @@ package builder
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -31,7 +29,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/buildkitutil"
"github.com/containerd/nerdctl/v2/pkg/platformutil" "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) { Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rmi", "-f", data.Identifier("ignored")) helpers.Anyhow("rmi", "-f", data.Identifier("ignored"))
helpers.Anyhow("rmi", "-f", data.Identifier())
}, },
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil),
}, },
@ -343,7 +339,7 @@ COPY %s /`, testFileName)
}, },
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 // Expecting testFileName to exist inside the output target directory
assert.Equal(t, data.Temp().Load(testFileName), testContent, "file content is identical") 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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") 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) { func TestBuildAttestation(t *testing.T) {
nerdtest.Setup() nerdtest.Setup()
// Using regex patterns to match SBOM and provenance files with optional platform suffix const testSBOMFileName = "sbom.spdx.json"
const testSBOMFilePattern = `sbom\.spdx(?:\.[a-z0-9_]+)?\.json` const testProvenanceFileName = "provenance.json"
const testProvenanceFilePattern = `provenance(?:\.[a-z0-9_]+)?\.json`
dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage) 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout, info string, t *testing.T) {
files, err := os.ReadDir(data.Temp().Path("dir-for-bom")) data.Temp().Exists("dir-for-bom", testSBOMFileName)
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)
}, },
} }
}, },
@ -927,18 +912,8 @@ func TestBuildAttestation(t *testing.T) {
}, },
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout, info string, t *testing.T) {
files, err := os.ReadDir(data.Temp().Path("dir-for-prov")) data.Temp().Exists("dir-for-prov", testProvenanceFileName)
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)
}, },
} }
}, },
@ -960,29 +935,9 @@ func TestBuildAttestation(t *testing.T) {
}, },
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout, info string, t *testing.T) {
// Check if any file in the directory matches the SBOM file pattern data.Temp().Exists("dir-for-attest", testSBOMFileName)
files, err := os.ReadDir(data.Temp().Path("dir-for-attest")) data.Temp().Exists("dir-for-attest", testProvenanceFileName)
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)
}, },
} }
}, },

View File

@ -30,7 +30,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/buildkitutil" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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 // FIXME: this test should be rewritten to dynamically retrieve the ids, and use images
// available on all platforms // available on all platforms
oldImage := testutil.BusyboxImage oldImage := testutil.BusyboxImage
parsedOldImage, err := referenceutil.Parse(oldImage) oldImageSha := "7b3ccabffc97de872a30dfd234fd972a66d247c8cfc69b0550f276481852627c"
assert.NilError(helpers.T(), err)
oldImageSha := parsedOldImage.Digest.String()
newImage := testutil.AlpineImage newImage := testutil.AlpineImage
parsedNewImage, err := referenceutil.Parse(newImage) newImageSha := "ec14c7992a97fc11425907e908340c6c3d6ff602f5f13d899e6b7027c9b4133a"
assert.NilError(helpers.T(), err)
newImageSha := parsedNewImage.Digest.String()
helpers.Ensure("pull", "--quiet", oldImage) 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.Temp().Save(dockerfile, "Dockerfile")
data.Labels().Set("oldImageSha", oldImageSha) data.Labels().Set("oldImageSha", oldImageSha)
data.Labels().Set("newImageSha", newImageSha) 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 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) { func NamespaceNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
globalOptions, err := helpers.ProcessRootCmdFlags(cmd) globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
if err != nil { 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) { func IPAMDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"default"}, cobra.ShellCompDirectiveNoFileComp 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) { func TestComposeBuild(t *testing.T) {
dockerfile := "FROM " + testutil.CommonImage dockerfile := "FROM " + testutil.AlpineImage
testCase := nerdtest.Setup() testCase := nerdtest.Setup()
@ -46,11 +46,15 @@ services:
svc0: svc0:
build: . build: .
image: %s image: %s
ports:
- 8080:80
depends_on: depends_on:
- svc1 - svc1
svc1: svc1:
build: . build: .
image: %s image: %s
ports:
- 8081:80
`, imageSvc0, imageSvc1) `, imageSvc0, imageSvc1)
data.Temp().Save(dockerComposeYAML, "compose.yaml") 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/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
) )
func TestComposeConfig(t *testing.T) { func TestComposeConfig(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(` const dockerComposeYAML = `
services: services:
hello: hello:
image: %s image: alpine:3.13
`, testutil.CommonImage) `
testCase := nerdtest.Setup() testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) { 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 { testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, 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") 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/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -32,6 +31,8 @@ import (
func TestComposeCopy(t *testing.T) { func TestComposeCopy(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -78,7 +79,7 @@ services:
}, },
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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") copied := data.Temp().Load("test-file2")
assert.Equal(t, copied, testFileContent) 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/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -33,10 +32,12 @@ import (
func TestComposeCreate(t *testing.T) { func TestComposeCreate(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
`, testutil.CommonImage) `, testutil.AlpineImage)
testCase := nerdtest.Setup() testCase := nerdtest.Setup()
@ -65,7 +66,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a") 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, assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`") "stdout should contain `created`")
@ -86,6 +87,8 @@ services:
func TestComposeCreateDependency(t *testing.T) { func TestComposeCreateDependency(t *testing.T) {
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -122,7 +125,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a") 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, assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`") "stdout should contain `created`")
@ -134,7 +137,7 @@ services:
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc1", "-a") 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, assert.Assert(t,
strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"),
"stdout should contain `created`") "stdout should contain `created`")
@ -149,10 +152,12 @@ func TestComposeCreatePull(t *testing.T) {
base := testutil.NewBase(t) base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
`, testutil.CommonImage) `, testutil.AlpineImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML) comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp() defer comp.CleanUp()
@ -162,12 +167,12 @@ services:
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
// `compose create --pull never` should fail: no such image // `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() base.ComposeCmd("-f", comp.YAMLFullPath(), "create", "--pull", "never").AssertFail()
// `compose create --pull missing(default)|always` should succeed: image is pulled and container is created // `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.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(), "create", "--pull", "always").AssertOK()
base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created") base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created")
} }
@ -182,7 +187,7 @@ services:
image: %s image: %s
`, imageSvc0) `, imageSvc0)
dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage) dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage)
testutil.RequiresBuild(t) testutil.RequiresBuild(t)
testutil.RegisterBuildCacheCleanup(t) testutil.RegisterBuildCacheCleanup(t)

View File

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

View File

@ -34,6 +34,8 @@ import (
func TestComposeExec(t *testing.T) { func TestComposeExec(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(` dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -177,6 +179,8 @@ services:
func TestComposeExecTTY(t *testing.T) { func TestComposeExecTTY(t *testing.T) {
const expectedOutput = "speed 38400 baud" const expectedOutput = "speed 38400 baud"
dockerComposeYAML := fmt.Sprintf(` dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -263,6 +267,8 @@ services:
func TestComposeExecWithIndex(t *testing.T) { func TestComposeExecWithIndex(t *testing.T) {
dockerComposeYAML := fmt.Sprintf(` dockerComposeYAML := fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -279,11 +285,6 @@ services:
data.Labels().Set("projectName", strings.ToLower(filepath.Base(data.Temp().Dir()))) data.Labels().Set("projectName", strings.ToLower(filepath.Base(data.Temp().Dir())))
helpers.Ensure("compose", "-f", yamlPath, "up", "-d", "svc0") 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) { testCase.Cleanup = func(data test.Data, helpers test.Helpers) {

View File

@ -17,26 +17,77 @@
package compose package compose
import ( import (
"encoding/json"
"fmt" "fmt"
"strings"
"testing" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestComposeImages(t *testing.T) { func TestComposeImages(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` 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: services:
wordpress: wordpress:
image: %s image: %s
container_name: wordpress container_name: wordpress
ports:
- 8080:80
environment: environment:
WORDPRESS_DB_HOST: db WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser WORDPRESS_DB_USER: exampleuser
@ -60,71 +111,41 @@ volumes:
db: db:
`, testutil.WordpressImage, testutil.MariaDBImage) `, testutil.WordpressImage, testutil.MariaDBImage)
wordpressImageName, _ := referenceutil.Parse(testutil.WordpressImage) comp := testutil.NewComposeDir(t, dockerComposeYAML)
dbImageName, _ := referenceutil.Parse(testutil.MariaDBImage) 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) { assertHandler := func(svc string, count int, fields ...string) func(stdout string) error {
data.Temp().Save(dockerComposeYAML, "compose.yaml") return func(stdout string) error {
data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) // 1. check json output can be unmarshalled back to printables.
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d") 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) { // check other formats are not supported
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down") 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{ base.ComposeCmd("-f", comp.YAMLFullPath(), "images", "--format", "json", "wordpress").
{ AssertOutWithFunc(assertHandler("wordpress", 1, `"ContainerName":"wordpress"`))
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)
} }

View File

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

View File

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

View File

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

View File

@ -20,17 +20,15 @@ import (
"fmt" "fmt"
"testing" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestComposePort(t *testing.T) { func TestComposePort(t *testing.T) {
base := testutil.NewBase(t) base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -57,6 +55,8 @@ func TestComposePortFailure(t *testing.T) {
base := testutil.NewBase(t) base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s 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", "udp", "svc0", "10000").AssertFail()
base.ComposeCmd("-f", comp.YAMLFullPath(), "port", "--protocol", "tcp", "svc0", "10001").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/containerd/v2/core/runtime/restart"
"github.com/containerd/errdefs" "github.com/containerd/errdefs"
"github.com/containerd/go-cni" "github.com/containerd/go-cni"
"github.com/containerd/log"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "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/clientutil"
"github.com/containerd/nerdctl/v2/pkg/cmd/compose" "github.com/containerd/nerdctl/v2/pkg/cmd/compose"
"github.com/containerd/nerdctl/v2/pkg/containerutil" "github.com/containerd/nerdctl/v2/pkg/containerutil"
@ -183,9 +183,9 @@ func psAction(cmd *cobra.Command, args []string) error {
var p composeContainerPrintable var p composeContainerPrintable
var err error var err error
if format == "json" { if format == "json" {
p, err = composeContainerPrintableJSON(ctx, container, globalOptions) p, err = composeContainerPrintableJSON(ctx, container)
} else { } else {
p, err = composeContainerPrintableTab(ctx, container, globalOptions) p, err = composeContainerPrintableTab(ctx, container)
} }
if err != nil { if err != nil {
return err return err
@ -234,7 +234,7 @@ func psAction(cmd *cobra.Command, args []string) error {
// composeContainerPrintableTab constructs composeContainerPrintable with fields // composeContainerPrintableTab constructs composeContainerPrintable with fields
// only for console output. // 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) info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil { if err != nil {
return composeContainerPrintable{}, err return composeContainerPrintable{}, err
@ -251,18 +251,6 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
if err != nil { if err != nil {
return composeContainerPrintable{}, err 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{ return composeContainerPrintable{
Name: info.Labels[labels.Name], Name: info.Labels[labels.Name],
@ -270,13 +258,13 @@ func composeContainerPrintableTab(ctx context.Context, container containerd.Cont
Command: formatter.InspectContainerCommandTrunc(spec), Command: formatter.InspectContainerCommandTrunc(spec),
Service: info.Labels[labels.ComposeService], Service: info.Labels[labels.ComposeService],
State: status, State: status,
Ports: formatter.FormatPorts(ports), Ports: formatter.FormatPorts(info.Labels),
}, nil }, nil
} }
// composeContainerPrintableJSON constructs composeContainerPrintable with fields // composeContainerPrintableJSON constructs composeContainerPrintable with fields
// only for json output and compatible docker output. // 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) info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil { if err != nil {
return composeContainerPrintable{}, err return composeContainerPrintable{}, err
@ -306,18 +294,6 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
if err != nil { if err != nil {
return composeContainerPrintable{}, err 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{ return composeContainerPrintable{
ID: container.ID(), ID: container.ID(),
@ -329,7 +305,7 @@ func composeContainerPrintableJSON(ctx context.Context, container containerd.Con
State: state, State: state,
Health: "", Health: "",
ExitCode: exitCode, ExitCode: exitCode,
Publishers: formatPublishers(portMappings), Publishers: formatPublishers(info.Labels),
}, nil }, nil
} }
@ -345,7 +321,7 @@ type PortPublisher struct {
// formatPublishers parses and returns docker-compatible []PortPublisher from // formatPublishers parses and returns docker-compatible []PortPublisher from
// label map. If an error happens, an empty slice is returned. // 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 { mapper := func(pm cni.PortMapping) PortPublisher {
return PortPublisher{ return PortPublisher{
URL: pm.HostIP, URL: pm.HostIP,
@ -356,8 +332,12 @@ func formatPublishers(portMappings []cni.PortMapping) []PortPublisher {
} }
var dockerPorts []PortPublisher var dockerPorts []PortPublisher
for _, p := range portMappings { if portMappings, err := portutil.ParsePortsLabel(labelMap); err == nil {
dockerPorts = append(dockerPorts, mapper(p)) for _, p := range portMappings {
dockerPorts = append(dockerPorts, mapper(p))
}
} else {
log.L.Error(err.Error())
} }
return dockerPorts return dockerPorts
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,19 +18,16 @@ package compose
import ( import (
"fmt" "fmt"
"regexp"
"testing" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestComposeStart(t *testing.T) { func TestComposeStart(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -40,68 +37,50 @@ services:
command: "sleep infinity" command: "sleep infinity"
`, testutil.CommonImage, testutil.CommonImage) `, 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) { base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down") defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()
}
testCase.Setup = func(data test.Data, helpers test.Helpers) { // calling `compose start` after all services up has no effect.
data.Temp().Save(dockerComposeYAML, "compose.yaml") base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertOK()
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")
}
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { // `compose start`` can start a stopped/killed service container
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "start") 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()
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0").AssertOutContainsAny("Up", "running")
return &test.Expected{ base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc1").AssertOutContainsAny("Up", "running")
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)
} }
func TestComposeStartFailWhenServicePause(t *testing.T) { 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(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
command: "sleep infinity" 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.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) { // `compose start` cannot start a paused service container
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down") base.ComposeCmd("-f", comp.YAMLFullPath(), "pause", "svc0").AssertOK()
} base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertFail()
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)
} }

View File

@ -18,22 +18,22 @@ package compose
import ( import (
"fmt" "fmt"
"regexp"
"testing" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestComposeStop(t *testing.T) { func TestComposeStop(t *testing.T) {
base := testutil.NewBase(t)
var dockerComposeYAML = fmt.Sprintf(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
wordpress: wordpress:
image: %s image: %s
ports:
- 8080:80
environment: environment:
WORDPRESS_DB_HOST: db WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser WORDPRESS_DB_USER: exampleuser
@ -57,50 +57,21 @@ volumes:
db: db:
`, testutil.WordpressImage, testutil.MariaDBImage) `, 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) { base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
data.Temp().Save(dockerComposeYAML, "compose.yaml") defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
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) { // stop should (only) stop the given service.
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down") 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{ // `--timeout` arg should work properly.
{ base.ComposeCmd("-f", comp.YAMLFullPath(), "stop", "--timeout", "5", "wordpress").AssertOK()
Description: "stop db", base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "wordpress", "-a").AssertOutContainsAny("Exit", "exited")
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"))),
},
}
testCase.Run(t)
} }

View File

@ -20,16 +20,20 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/v2/pkg/infoutil"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestComposeTop(t *testing.T) { 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(` var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services: services:
svc0: svc0:
image: %s image: %s
@ -38,36 +42,15 @@ services:
image: %s image: %s
`, testutil.CommonImage, testutil.NginxAlpineImage) `, 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) { // a running container should have the process command in output
data.Temp().Save(dockerComposeYAML, "compose.yaml") base.ComposeCmd("-f", comp.YAMLFullPath(), "top", "svc0").AssertOutContains("sleep infinity")
helpers.Ensure("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d") base.ComposeCmd("-f", comp.YAMLFullPath(), "top", "svc1").AssertOutContains("nginx")
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)
} }

View File

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

View File

@ -17,15 +17,14 @@
package compose package compose
import ( import (
"errors"
"fmt" "fmt"
"os"
"path/filepath"
"runtime"
"testing" "testing"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -33,73 +32,56 @@ import (
// https://github.com/containerd/nerdctl/issues/1942 // https://github.com/containerd/nerdctl/issues/1942
func TestComposeUpDetailedError(t *testing.T) { 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(` dockerComposeYAML := fmt.Sprintf(`
services: services:
foo: foo:
image: %s image: %s
runtime: invalid runtime: invalid
`, testutil.CommonImage) `, testutil.CommonImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
testCase := nerdtest.Setup() c := base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d")
expected := icmd.Expected{
// "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) ExitCode: 1,
testCase.Require = require.Not(require.Windows) Err: `exec: \"invalid\": executable file not found in $PATH`,
testCase.Setup = func(data test.Data, helpers test.Helpers) {
data.Temp().Save(dockerComposeYAML, "compose.yaml")
} }
// Docker expected err is different
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { if nerdtest.IsDocker() {
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "up", "-d") expected.Err = `unknown or invalid runtime name: invalid`
} }
c.Assert(expected)
testCase.Expected = test.Expects(
1,
[]error{errors.New(`invalid runtime name`)},
nil,
)
testCase.Run(t)
} }
// https://github.com/containerd/nerdctl/issues/1652 // https://github.com/containerd/nerdctl/issues/1652
func TestComposeUpBindCreateHostPath(t *testing.T) { 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\")` base := testutil.NewBase(t)
testCase.Require = require.Not(require.Windows)
testCase.Setup = func(data test.Data, helpers test.Helpers) { var dockerComposeYAML = fmt.Sprintf(`
var dockerComposeYAML = fmt.Sprintf(`
services: services:
test: test:
image: %s image: %s
command: sh -euxc "echo hi >/mnt/test" command: sh -euxc "echo hi >/mnt/test"
volumes: volumes:
# tempdir/foo should be automatically created # ./foo should be automatically created
- %s:/mnt - ./foo:/mnt
`, testutil.CommonImage, data.Temp().Path("foo")) `, 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 { base.ComposeCmd("-f", comp.YAMLFullPath(), "up").AssertOK()
return helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), "up") defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK()
} testFile := filepath.Join(comp.Dir(), "foo", "test")
testB, err := os.ReadFile(testFile)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) { assert.NilError(t, err)
helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down") assert.Equal(t, "hi\n", string(testB))
}
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)
} }

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -65,7 +64,7 @@ func TestAttach(t *testing.T) {
cmd.Run(&test.Expected{ cmd.Run(&test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{errors.New("read detach keys")}, 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")) 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")}, Errors: []error{errors.New("read detach keys")},
Output: expect.All( Output: expect.All(
expect.Contains("markmark"), 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")) 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{ cmd.Run(&test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{errors.New("read detach keys")}, 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")) 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")}, Errors: []error{errors.New("read detach keys")},
Output: expect.All( Output: expect.All(
expect.Contains("markmark"), 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")) 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{ cmd.Run(&test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{errors.New("read detach keys")}, 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")) 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, ExitCode: 42,
Output: expect.All( Output: expect.All(
expect.Contains("markmark"), 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())) assert.Assert(t, !strings.Contains(helpers.Capture("ps", "-a"), data.Identifier()))
}, },
), ),
@ -212,44 +211,3 @@ func TestAttachForAutoRemovedContainer(t *testing.T) {
testCase.Run(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 package container
import ( import (
"errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion" "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().StringP("message", "m", "", "Commit message")
cmd.Flags().StringArrayP("change", "c", nil, "Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])") 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().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 return cmd
} }
@ -77,78 +66,15 @@ func commitOptions(cmd *cobra.Command) (types.ContainerCommitOptions, error) {
return types.ContainerCommitOptions{}, err 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{ return types.ContainerCommitOptions{
Stdout: cmd.OutOrStdout(), Stdout: cmd.OutOrStdout(),
GOptions: globalOptions, GOptions: globalOptions,
Author: author, Author: author,
Message: message, Message: message,
Pause: pause, Pause: pause,
Change: change, 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,
},
}, nil }, nil
} }
func commitAction(cmd *cobra.Command, args []string) error { func commitAction(cmd *cobra.Command, args []string) error {
@ -156,6 +82,7 @@ func commitAction(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address) client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
if err != nil { if err != nil {
return err return err

View File

@ -19,10 +19,8 @@ package container
import ( import (
"strings" "strings"
"testing" "testing"
"time"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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, "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, "exec", identifier, "--", "mkdir", "-p", "/tmp/whatever").Run(&test.Expected{})
nerdtest.KubeCtlCommand(helpers, "get", "pods", identifier, "-o", "jsonpath={ .status.containerStatuses[0].containerID }").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://") 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 { testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure("commit", data.Labels().Get("containerID"), data.Identifier("testcommitsave")) helpers.Ensure("commit", data.Labels().Get("containerID"), "testcommitsave")
// Wait for the image to show up return helpers.Command("save", "testcommitsave")
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"))
} }
testCase.Expected = test.Expects(0, nil, nil) 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 = nerdtest.KubeCtlCommand(helpers, "get", "pods", tID, "-o", "jsonpath={ .status.hostIPs[0].ip }")
cmd.Run(&test.Expected{ cmd.Run(&test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
registryIP = stdout registryIP = stdout
}, },
}) })

View File

@ -19,14 +19,10 @@ package container
import ( import (
"testing" "testing"
"gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
@ -90,52 +86,3 @@ func TestCommit(t *testing.T) {
testCase.Run(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 // #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 // #region for intel RDT flags
opt.RDTClass, err = cmd.Flags().GetString("rdt-class") opt.RDTClass, err = cmd.Flags().GetString("rdt-class")
if err != nil { if err != nil {
@ -405,6 +371,7 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
// #endregion // #endregion
// #region for metadata flags // #region for metadata flags
opt.NameChanged = cmd.Flags().Changed("name")
opt.Name, err = cmd.Flags().GetString("name") opt.Name, err = cmd.Flags().GetString("name")
if err != nil { if err != nil {
return opt, err return opt, err
@ -539,7 +506,7 @@ func createAction(cmd *cobra.Command, args []string) error {
} }
defer cancel() defer cancel()
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions) netFlags, err := loadNetworkFlags(cmd)
if err != nil { if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err) 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -236,7 +235,7 @@ func TestIssue2993(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("is already used by ID")}, 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)) containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, len(containersDirs), 1) assert.Equal(t, len(containersDirs), 1)
@ -283,7 +282,7 @@ func TestIssue2993(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, 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)) containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, len(containersDirs), 0) 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
assert.NilError(t, err, "Failed to get container host UID") 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/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -97,13 +96,13 @@ func TestCreateHyperVContainer(t *testing.T) {
helpers.Command("container", "inspect", data.Labels().Get("cID")). helpers.Command("container", "inspect", data.Labels().Get("cID")).
Run(&test.Expected{ Run(&test.Expected{
ExitCode: expect.ExitCodeNoCheck, ExitCode: expect.ExitCodeNoCheck,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
var dc []dockercompat.Container var dc []dockercompat.Container
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
if err != nil || len(dc) == 0 { if err != nil || len(dc) == 0 {
return 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" ran = dc[0].State.Status == "exited"
}, },
}) })

View File

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

View File

@ -65,9 +65,6 @@ func TestExecTTY(t *testing.T) {
testCase.Setup = func(data test.Data, helpers test.Helpers) { testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", nerdtest.Infinity) helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", nerdtest.Infinity)
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
data.Labels().Set("container_name", 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" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"slices" "slices"
"strings" "strings"
"testing" "testing"
@ -534,7 +535,8 @@ RUN groupadd -r test && useradd -r -g test test
USER test USER test
`, testutil.UbuntuImage) `, 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("build", "-t", data.Identifier(), data.Temp().Path())
helpers.Ensure("create", "--name", data.Identifier(), "--user", "test", data.Identifier()) helpers.Ensure("create", "--name", data.Identifier(), "--user", "test", data.Identifier())

View File

@ -27,7 +27,6 @@ import (
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/test" "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/formatter"
"github.com/containerd/nerdctl/v2/pkg/strutil" "github.com/containerd/nerdctl/v2/pkg/strutil"
@ -305,42 +304,6 @@ func TestContainerListWithFilter(t *testing.T) {
return nil 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 { base.Cmd("ps", "-q", "--filter", "name="+testContainerA.name+testContainerA.name).AssertOutWithFunc(func(stdout string) error {
lines := strings.Split(strings.TrimSpace(stdout), "\n") lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) > 0 { if len(lines) > 0 {
@ -689,7 +652,7 @@ func TestContainerListStatusFilter(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, 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") 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 ( import (
"errors" "errors"
"fmt" "fmt"
"regexp" "io"
"os/exec"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -27,97 +28,52 @@ import (
"time" "time"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
) )
func TestLogs(t *testing.T) { func TestLogs(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
const expected = `foo 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" { //test since / until flag
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237") 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) { // Ensure follow flag works as expected:
helpers.Anyhow("rm", "-f", data.Identifier()) base.Cmd("logs", "-f", containerName).AssertOutContains("bar")
} base.Cmd("logs", "-f", containerName).AssertOutContains("foo")
testCase.Setup = func(data test.Data, helpers test.Helpers) { //test timestamps flag
helpers.Ensure("run", "--quiet", "--name", data.Identifier(), testutil.CommonImage, "sh", "-euxc", "echo foo; echo bar;") base.Cmd("logs", "-t", containerName).AssertOutContains(time.Now().UTC().Format("2006-01-02"))
data.Labels().Set("cID", data.Identifier())
}
testCase.SubTests = []*test.Case{ //test tail flag
{ base.Cmd("logs", "-n", "all", containerName).AssertOutContains(expected)
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|)$"))),
},
}
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 // Tests whether `nerdctl logs` properly separates stdout/stderr output
@ -125,13 +81,8 @@ bar
func TestLogsOutStreamsSeparated(t *testing.T) { func TestLogsOutStreamsSeparated(t *testing.T) {
testCase := nerdtest.Setup() 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) { 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") "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 { 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()) return helpers.Command("logs", data.Identifier())
} }
@ -152,165 +105,116 @@ func TestLogsOutStreamsSeparated(t *testing.T) {
} }
func TestLogsWithInheritedFlags(t *testing.T) { func TestLogsWithInheritedFlags(t *testing.T) {
testCase := nerdtest.Setup() // Seen flaky with Docker
t.Parallel()
testCase.Require = require.Not(nerdtest.Docker) base := testutil.NewBase(t)
for k, v := range base.Args {
testCase.Setup = func(data test.Data, helpers test.Helpers) { if strings.HasPrefix(v, "--namespace=") {
helpers.Ensure("-n="+testutil.Namespace, "run", "--name", data.Identifier(), testutil.CommonImage, base.Args[k] = "-n=" + testutil.Namespace
"sh", "-euxc", "echo foo; echo bar") }
} }
containerName := testutil.Identifier(t)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) { defer base.Cmd("rm", containerName).Run()
helpers.Anyhow("rm", "-f", data.Identifier()) 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 { // It appears this test flakes out with Docker seeing only "foo\n"
return helpers.Command("-n="+testutil.Namespace, "logs", "-n", "1", data.Identifier()) // Tentatively adding a pause in case this is just slow
} time.Sleep(time.Second)
// test rootCmd alias `-n` already used in logs subcommand
// FIXME: why? base.Cmd("logs", "-n", "1", containerName).AssertOutWithFunc(func(stdout string) error {
testCase.Expected = test.Expects(0, nil, expect.Match(regexp.MustCompile("^(?:bar\n|)$"))) if !(stdout == "bar\n" || stdout == "") {
return fmt.Errorf("expected %q or %q, got %q", "bar", "", stdout)
testCase.Run(t) }
return nil
})
} }
func TestLogsOfJournaldDriver(t *testing.T) { func TestLogsOfJournaldDriver(t *testing.T) {
const expected = `foo testutil.RequireExecutable(t, "journalctl")
bar journalctl, _ := exec.LookPath("journalctl")
` res := icmd.RunCmd(icmd.Command(journalctl, "-xe"))
if res.ExitCode != 0 {
testCase := nerdtest.Setup() t.Skipf("current user is not allowed to access journal logs: %s", res.Combined())
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())
} }
testCase.Setup = func(data test.Data, helpers test.Helpers) { t.Parallel()
helpers.Ensure("run", "--network", "none", "--log-driver", "journald", "--name", data.Identifier(), testutil.CommonImage, base := testutil.NewBase(t)
"sh", "-euxc", "echo foo; echo bar") containerName := testutil.Identifier(t)
data.Labels().Set("cID", data.Identifier())
}
testCase.SubTests = []*test.Case{ defer base.Cmd("rm", containerName).Run()
{ base.Cmd("run", "-d", "--network", "none", "--log-driver", "journald", "--name", containerName, testutil.CommonImage,
Description: "logs", "sh", "-euxc", "echo foo; echo bar").AssertOK()
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Labels().Get("cID")) time.Sleep(3 * time.Second)
}, base.Cmd("logs", containerName).AssertOutContains("bar")
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(expected)), // Run logs twice, make sure that the logs are not removed
}, base.Cmd("logs", containerName).AssertOutContains("foo")
{
Description: "logs --since 60s", base.Cmd("logs", "--since", "5s", containerName).AssertOutWithFunc(func(stdout string) error {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { if !strings.Contains(stdout, "bar") {
return helpers.Command("logs", "--since", "60s", data.Labels().Get("cID")) return fmt.Errorf("expected bar, got %s", stdout)
}, }
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.DoesNotContain("foo", "bar")), 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) { func TestLogsWithFailingContainer(t *testing.T) {
const expected = `foo t.Parallel()
bar base := testutil.NewBase(t)
` containerName := testutil.Identifier(t)
defer base.Cmd("rm", containerName).Run()
testCase := nerdtest.Setup() base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euxc", "echo foo; echo bar; exit 42; echo baz").AssertOK()
if runtime.GOOS == "windows" { time.Sleep(3 * time.Second)
// Logging seems broken on windows. // AssertOutContains also asserts that the exit code of the logs command == 0,
testCase.Require = nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4237") // even when the container is failing
} base.Cmd("logs", "-f", containerName).AssertOutContains("bar")
base.Cmd("logs", "-f", containerName).AssertOutNotContains("baz")
testCase.Cleanup = func(data test.Data, helpers test.Helpers) { base.Cmd("rm", "-f", containerName).AssertOK()
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)
} }
func TestLogsWithRunningContainer(t *testing.T) { 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) expected := make([]string, 10)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
expected[i] = fmt.Sprint(i + 1) expected[i] = fmt.Sprint(i + 1)
} }
testCase := nerdtest.Setup() base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"sh", "-euc", "for i in `seq 1 10`; do echo $i; sleep 1; done").AssertOK()
if runtime.GOOS == "windows" { base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
// 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)
} }
func TestLogsWithoutNewlineOrEOF(t *testing.T) { func TestLogsWithoutNewlineOrEOF(t *testing.T) {
testCase := nerdtest.Setup() 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: 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 // FIXME: nerdctl behavior does not match docker - test disabled for nerdctl until we fix
testCase.Require = require.All( testCase.Require = require.All(
require.Linux, require.Linux,
nerdtest.NerdctlNeedsFixing("https://github.com/containerd/nerdctl/issues/4201"),
) )
testCase.Setup = func(data test.Data, helpers test.Helpers) { 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) { testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier()) helpers.Anyhow("rm", "-f", data.Identifier())
} }
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { 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()) return helpers.Command("logs", "-f", data.Identifier())
} }
testCase.Expected = test.Expects(0, nil, expect.Equals("'Hello World!\nThere is no newline'")) testCase.Expected = test.Expects(0, nil, expect.Equals("'Hello World!\nThere is no newline'"))
testCase.Run(t) testCase.Run(t)
} }
@ -318,44 +222,19 @@ func TestLogsAfterRestartingContainer(t *testing.T) {
if runtime.GOOS != "linux" { 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") 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")
} }
t.Parallel()
testCase := nerdtest.Setup() base := testutil.NewBase(t)
containerName := testutil.Identifier(t)
testCase.Setup = func(data test.Data, helpers test.Helpers) { defer base.Cmd("rm", "-f", containerName).Run()
helpers.Ensure("run", "--name", data.Identifier(), testutil.CommonImage, base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage,
"printf", "'Hello World!\nThere is no newline'") "printf", "'Hello World!\nThere is no newline'").AssertOK()
data.Labels().Set("cID", data.Identifier()) expected := []string{"Hello World!", "There is no newline"}
} time.Sleep(3 * time.Second)
base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
testCase.Cleanup = func(data test.Data, helpers test.Helpers) { // restart and check logs again
helpers.Anyhow("rm", "-f", data.Identifier()) base.Cmd("start", containerName)
} time.Sleep(3 * time.Second)
base.Cmd("logs", "-f", containerName).AssertOutContainsAll(expected...)
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)
} }
func TestLogsWithForegroundContainers(t *testing.T) { func TestLogsWithForegroundContainers(t *testing.T) {
@ -377,7 +256,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier()) 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", Description: "interactive",
@ -390,7 +272,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier()) 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", Description: "PTY",
@ -405,7 +290,10 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier()) 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", Description: "interactivePTY",
@ -420,88 +308,69 @@ func TestLogsWithForegroundContainers(t *testing.T) {
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier()) 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) { func TestTailFollowRotateLogs(t *testing.T) {
// FIXME this is flaky by nature... the number of lines is arbitrary, the wait is arbitrary, // FIXME this is flaky by nature... 2 lines is arbitrary, 10000 ms is arbitrary, and both are some sort of educated
// and both are some sort of educated guess that things will mostly always kinda work maybe... // 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 sampleJSONLog = `{"log":"A\n","stream":"stdout","time":"2024-04-11T12:01:09.800288974Z"}`
const linesPerFile = 200 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 tailLogCmd := base.Cmd("logs", "-f", containerName)
testCase.Require = require.Not(require.Windows) tailLogCmd.Timeout = 1000 * time.Millisecond
logRun := tailLogCmd.Run()
testCase.Setup = func(data test.Data, helpers test.Helpers) { tailLogs := strings.Split(strings.TrimSpace(logRun.Stdout()), "\n")
helpers.Ensure("run", "-d", "--log-driver", "json-file", for _, line := range tailLogs {
"--log-opt", fmt.Sprintf("max-size=%d", len(sampleJSONLog)*linesPerFile), if line != "" {
"--log-opt", "max-file=10", assert.Equal(t, "A", line)
"--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)
}
} }
}
assert.Assert(t, len(tailLogs) > linesPerFile, fmt.Sprintf("expected %d lines or more, found %d", linesPerFile, len(tailLogs))) assert.Equal(t, true, len(tailLogs) > linesPerFile, logRun.Stderr())
})
testCase.Run(t)
} }
func TestNoneLoggerHasNoLogURI(t *testing.T) {
func TestLogsNoneLoggerHasNoLogURI(t *testing.T) {
testCase := nerdtest.Setup() testCase := nerdtest.Setup()
testCase.Setup = func(data test.Data, helpers test.Helpers) { testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--name", data.Identifier(), "--log-driver", "none", testutil.CommonImage, "sh", "-euxc", "echo foo") helpers.Ensure("run", "--name", data.Identifier(), "--log-driver", "none", testutil.CommonImage, "sh", "-euxc", "echo foo")
} }
testCase.Cleanup = func(data test.Data, helpers test.Helpers) { testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier()) helpers.Anyhow("rm", "-f", data.Identifier())
} }
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("logs", data.Identifier()) return helpers.Command("logs", data.Identifier())
} }
testCase.Expected = test.Expects(1, nil, nil) testCase.Expected = test.Expects(1, nil, nil)
testCase.Run(t) testCase.Run(t)
} }
func TestLogsWithDetails(t *testing.T) { func TestLogsWithDetails(t *testing.T) {
testCase := nerdtest.Setup() 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) { 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-size=10m",
"--log-opt", "max-file=3", "--log-opt", "max-file=3",
"--log-opt", "env=ENV", "--log-opt", "env=ENV",
@ -532,7 +401,7 @@ func TestLogsFollowNoExtraneousLineFeed(t *testing.T) {
testCase.Setup = func(data test.Data, helpers test.Helpers) { testCase.Setup = func(data test.Data, helpers test.Helpers) {
// Create a container that outputs a message without a trailing newline // 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'") "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 { testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
// Use logs -f to follow the logs // 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()) return helpers.Command("logs", "-f", data.Identifier())
} }
@ -554,7 +425,7 @@ func TestLogsFollowNoExtraneousLineFeed(t *testing.T) {
func TestLogsWithStartContainer(t *testing.T) { func TestLogsWithStartContainer(t *testing.T) {
testCase := nerdtest.Setup() 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.Require = require.Not(require.Windows)
testCase.SubTests = []*test.Case{ testCase.SubTests = []*test.Case{
@ -563,28 +434,34 @@ func TestLogsWithStartContainer(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage) cmd := helpers.Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage)
cmd.WithPseudoTTY() 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{ cmd.Run(&test.Expected{
ExitCode: 0, 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) { Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier()) helpers.Anyhow("rm", "-f", data.Identifier())
}, },
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { 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")), 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", 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) { 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") 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
finalLogsCount := strings.Count(stdout, "foo") finalLogsCount := strings.Count(stdout, "foo")
initialFooCount, _ := strconv.Atoi(data.Labels().Get("initialFooCount")) 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/clientutil"
"github.com/containerd/nerdctl/v2/pkg/containerutil" "github.com/containerd/nerdctl/v2/pkg/containerutil"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
"github.com/containerd/nerdctl/v2/pkg/portutil"
) )
func PortCommand() *cobra.Command { func PortCommand() *cobra.Command {
@ -82,26 +81,13 @@ func portAction(cmd *cobra.Command, args []string) error {
} }
defer cancel() defer cancel()
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
if err != nil {
return err
}
walker := &containerwalker.ContainerWalker{ walker := &containerwalker.ContainerWalker{
Client: client, Client: client,
OnFound: func(ctx context.Context, found containerwalker.Found) error { OnFound: func(ctx context.Context, found containerwalker.Found) error {
if found.MatchCount > 1 { if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
} }
containerLabels, err := found.Container.Labels(ctx) return containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto)
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)
}, },
} }
req := args[0] 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -154,12 +153,12 @@ func TestRestartWithSignal(t *testing.T) {
Output: expect.All( Output: expect.All(
// Check that we saw SIGUSR1 inside the container // Check that we saw SIGUSR1 inside the container
expect.Contains(nerdtest.SignalCaught), expect.Contains(nerdtest.SignalCaught),
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
// Ensure the container was restarted // Ensure the container was restarted
nerdtest.EnsureContainerStarted(helpers, data.Identifier()) nerdtest.EnsureContainerStarted(helpers, data.Identifier())
// Check the new pid is different // Check the new pid is different
newpid := strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid) 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) // rootfs flags (from Podman)
cmd.Flags().Bool("rootfs", false, "The first argument is not an image but the rootfs to the exploded container") 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 // #region env flags
// entrypoint needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"} // 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 // 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") return errors.New("flags -d and -a cannot be specified together")
} }
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions) netFlags, err := loadNetworkFlags(cmd)
if err != nil { if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err) 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/cmd/container"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
@ -135,9 +134,7 @@ func TestRunCgroupV2(t *testing.T) {
base.Cmd("exec", testutil.Identifier(t)+"-testUpdate2", base.Cmd("exec", testutil.Identifier(t)+"-testUpdate2",
"cat", "cpu.max", "memory.max", "memory.swap.max", "memory.low", "cat", "cpu.max", "memory.max", "memory.swap.max", "memory.low",
"pids.max", "cpu.weight", "cpuset.cpus", "cpuset.mems").AssertOutExactly(expected2) "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) { 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" 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", "--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", "--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 // 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 { 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) 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) lo1Read, err := os.ReadFile(lo[1].Device)
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, string(bytes.Trim(lo1Read, "\x00")), "overwritten-lo1-content") assert.Equal(t, string(bytes.Trim(lo1Read, "\x00")), "overwritten-lo1-content")
@ -529,7 +523,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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")) 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{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioWeightDevice}}{{.Weight}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "100")) assert.Assert(t, strings.Contains(inspectOut, "100"))
}, },
@ -580,7 +574,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceReadBps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "1048576")) assert.Assert(t, strings.Contains(inspectOut, "1048576"))
}, },
@ -609,7 +603,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceWriteBps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "2097152")) assert.Assert(t, strings.Contains(inspectOut, "2097152"))
}, },
@ -638,7 +632,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceReadIOps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "1000")) assert.Assert(t, strings.Contains(inspectOut, "1000"))
}, },
@ -667,7 +661,7 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) inspectOut := helpers.Capture("inspect", "--format", "{{range .HostConfig.BlkioDeviceWriteIOps}}{{.Rate}}{{end}}", data.Identifier())
assert.Assert(t, strings.Contains(inspectOut, "2000")) assert.Assert(t, strings.Contains(inspectOut, "2000"))
}, },
@ -702,7 +696,7 @@ func TestRunCPURealTimeSettingCgroupV1(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: expect.All( 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()) rtRuntime := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimeRuntime}}", data.Identifier())
rtPeriod := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimePeriod}}", data.Identifier()) rtPeriod := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimePeriod}}", data.Identifier())
assert.Assert(t, strings.Contains(rtRuntime, "950000")) 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/rootlessutil"
@ -549,7 +548,7 @@ func TestRunWithDetachKeys(t *testing.T) {
Errors: []error{errors.New("detach keys")}, Errors: []error{errors.New("detach keys")},
Output: expect.All( Output: expect.All(
expect.Contains("markmark"), 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")) 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")}, Errors: []error{errors.New("detach keys")},
Output: expect.All( Output: expect.All(
expect.Contains("markmark"), 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")) 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, ExitCode: 0,
Errors: []error{}, Errors: []error{},
Output: expect.All( Output: expect.All(
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
resp, err := nettestutil.HTTPGet(address, 5, false) resp, err := nettestutil.HTTPGet(address, 30, false)
assert.NilError(t, err) assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)

View File

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

View File

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

View File

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

View File

@ -36,10 +36,10 @@ import (
"github.com/containerd/containerd/v2/defaults" "github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/pkg/netns" "github.com/containerd/containerd/v2/pkg/netns"
"github.com/containerd/errdefs"
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -247,7 +247,7 @@ func TestRunPortWithNoHostPort(t *testing.T) {
return return
} }
connectURL := fmt.Sprintf("http://%s:%s", "127.0.0.1", paramsMap["portNumber"]) 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) assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
assert.NilError(t, err) assert.NilError(t, err)
@ -332,7 +332,7 @@ func TestUniqueHostPortAssignement(t *testing.T) {
// Make HTTP GET request to container 1 // Make HTTP GET request to container 1
connectURL1 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port1) 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) assert.NilError(t, err)
respBody1, err := io.ReadAll(resp1.Body) respBody1, err := io.ReadAll(resp1.Body)
assert.NilError(t, err) assert.NilError(t, err)
@ -340,7 +340,7 @@ func TestUniqueHostPortAssignement(t *testing.T) {
// Make HTTP GET request to container 2 // Make HTTP GET request to container 2
connectURL2 := fmt.Sprintf("http://%s:%s", "127.0.0.1", port2) 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) assert.NilError(t, err)
respBody2, err := io.ReadAll(resp2.Body) respBody2, err := io.ReadAll(resp2.Body)
assert.NilError(t, err) 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) { func TestRunPort(t *testing.T) {
baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true) baseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)
} }
func TestRunWithManyPortsThenCleanUp(t *testing.T) { func TestRunWithInvalidPortThenCleanUp(t *testing.T) {
testCase := nerdtest.Setup() testCase := nerdtest.Setup()
// docker does not set label restriction to 4096 bytes // docker does not set label restriction to 4096 bytes
testCase.Require = require.Not(nerdtest.Docker) testCase.Require = require.Not(nerdtest.Docker)
testCase.SubTests = []*test.Case{ 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 { 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 1,
Errors: []error{}, Errors: []error{errdefs.ErrInvalidArgument},
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
getAddrHash := func(addr string) string { getAddrHash := func(addr string) string {
const addrHashLen = 8 const addrHashLen = 8
@ -570,103 +518,158 @@ func TestSharedNetworkSetup(t *testing.T) {
testCase := &test.Case{ testCase := &test.Case{
Require: require.Not(require.Windows), Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
data.Labels().Set("container1", data.Identifier("container1")) data.Labels().Set("containerName1", data.Identifier("-container1"))
helpers.Ensure("run", "-d", "--name", data.Identifier("container1"), containerName1 := data.Labels().Get("containerName1")
testutil.CommonImage, "sleep", "inf") helpers.Ensure("run", "-d", "--name", containerName1,
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container1")) testutil.NginxAlpineImage)
}, },
Cleanup: func(data test.Data, helpers test.Helpers) { 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{ SubTests: []*test.Case{
{ {
Description: "Test network is shared", Description: "Test network is shared",
NoParallel: true, // The validation involves starting of the main container: container1 NoParallel: true, // The validation involves starting of the main container: container1
Cleanup: func(data test.Data, helpers test.Helpers) { 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) { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
helpers.Ensure( containerName2 := data.Identifier()
"run", "-d", "--name", data.Identifier("container2"), cmd := helpers.Command()
"--network=container:"+data.Labels().Get("container1"), cmd.WithArgs("run", "-d", "--name", containerName2,
"--network=container:"+data.Labels().Get("containerName1"),
testutil.NginxAlpineImage) testutil.NginxAlpineImage)
data.Labels().Set("container2", data.Identifier("container2")) return cmd
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container2"))
}, },
SubTests: []*test.Case{ Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
{ return &test.Expected{
NoParallel: true, Output: func(stdout string, info string, t *testing.T) {
Description: "Test network is shared", containerName2 := data.Identifier()
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { assert.Assert(t, strings.Contains(helpers.Capture("exec", containerName2, "wget", "-qO-", "http://127.0.0.1:80"), testutil.NginxAlpineIndexHTMLSnippet), info)
return helpers.Command("exec", data.Labels().Get("container2"), "wget", "-qO-", "http://127.0.0.1:80") 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", Description: "Test uts is supported in shared network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Cleanup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", "--uts", "host", helpers.Anyhow("rm", "-f", data.Identifier())
"--network=container:"+data.Labels().Get("container1"), },
testutil.CommonImage) 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", Description: "Test dns is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Cleanup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", "--dns", "0.1.2.3", helpers.Anyhow("rm", "-f", data.Identifier())
"--network=container:"+data.Labels().Get("container1"), },
testutil.CommonImage) 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", Description: "Test dns options is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Cleanup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", "--dns-option", "attempts:5", helpers.Anyhow("rm", "-f", data.Identifier())
"--network=container:"+data.Labels().Get("container1"), },
testutil.CommonImage, "cat", "/etc/resolv.conf") 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", Description: "Test publish is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Cleanup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", "--publish", "80:8080", helpers.Anyhow("rm", "-f", data.Identifier())
"--network=container:"+data.Labels().Get("container1"), },
testutil.AlpineImage) 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", Description: "Test hostname is not supported",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Cleanup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", "--hostname", "test", helpers.Anyhow("rm", "-f", data.Identifier())
"--network=container:"+data.Labels().Get("container1"), },
testutil.AlpineImage) 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), Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier("container1"), "--network", "none", helpers.Ensure("run", "-d", "--name", data.Identifier("container1"), "--network", "none",
testutil.CommonImage, "sleep", "inf") testutil.NginxAlpineImage)
nerdtest.EnsureContainerStarted(helpers, data.Identifier("container1"))
}, },
Cleanup: func(data test.Data, helpers test.Helpers) { Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container1")) helpers.Anyhow("rm", "-f", data.Identifier("container1"))
helpers.Anyhow("rm", "-f", data.Identifier("container2"))
}, },
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", return helpers.Command("run", "-d", "--name", data.Identifier("container2"),
"--network=container:"+data.Identifier("container1"), testutil.CommonImage) "--network=container:"+data.Identifier("container1"), testutil.NginxAlpineImage)
}, },
Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), Expected: test.Expects(expect.ExitCodeSuccess, nil, nil),
} }
@ -911,14 +914,13 @@ func TestNoneNetworkHostName(t *testing.T) {
nerdtest.Setup() nerdtest.Setup()
testCase := &test.Case{ testCase := &test.Case{
Require: require.Not(require.Windows), 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) { 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) assert.Assert(helpers.T(), len(output) > 12, output)
data.Labels().Set("hostname", output[:12]) 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 { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("exec", data.Identifier(), "cat", "/etc/hostname") return helpers.Command("exec", data.Identifier(), "cat", "/etc/hostname")
@ -937,20 +939,20 @@ func TestHostNetworkHostName(t *testing.T) {
testCase := &test.Case{ testCase := &test.Case{
Require: require.Not(require.Windows), Require: require.Not(require.Windows),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Custom("cat", "/etc/hostname").Run(&test.Expected{ data.Labels().Set("containerName1", data.Identifier())
Output: func(stdout string, t tig.T) { },
data.Labels().Set("hostHostname", stdout) Cleanup: func(data test.Data, helpers test.Helpers) {
}, helpers.Anyhow("rm", "-f", data.Identifier())
})
}, },
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", return helpers.Custom("cat", "/etc/hostname")
"--network", "host",
testutil.AlpineImage, "cat", "/etc/hostname")
}, },
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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() nerdtest.Setup()
testCase := &test.Case{ testCase := &test.Case{
Require: require.Not(require.Windows), Require: require.Not(require.Windows),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Setup: func(data test.Data, helpers test.Helpers) {
return helpers.Command("run", "--rm", data.Labels().Set("containerName1", data.Identifier())
"--network", "none", },
"--dns", "0.1.2.3", "--dns-search", "example.com", "--dns-option", "timeout:3", "--dns-option", "attempts:5", Cleanup: func(data test.Data, helpers test.Helpers) {
testutil.CommonImage, "cat", "/etc/resolv.conf") 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) testCase.Run(t)
} }
@ -981,101 +992,26 @@ func TestHostNetworkDnsConfigs(t *testing.T) {
nerdtest.Setup() nerdtest.Setup()
testCase := &test.Case{ testCase := &test.Case{
Require: require.Not(require.Windows), 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 { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", 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)
"--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")
}, },
Expected: test.Expects(0, nil, expect.Contains( Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
"0.1.2.3", return &test.Expected{
"example.com", Output: func(stdout string, info string, t *testing.T) {
"attempts:5", out := helpers.Capture("exec", data.Identifier(), "cat", "/etc/resolv.conf")
"timeout:3", 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)
testCase.Run(t) 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) testCase.Run(t)

View File

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

View File

@ -25,7 +25,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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) { testCase.Setup = func(data test.Data, helpers test.Helpers) {
helpers.Custom("mount").Run(&test.Expected{ helpers.Custom("mount").Run(&test.Expected{
ExitCode: 0, 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"))) 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 { testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: func(stdout string, t tig.T) { Output: func(stdout, info string, t *testing.T) {
var afterCount int var afterCount int
beforeCount, _ := strconv.Atoi(data.Labels().Get("beforeCount")) beforeCount, _ := strconv.Atoi(data.Labels().Get("beforeCount"))
helpers.Custom("mount").Run(&test.Expected{ 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") 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -157,7 +156,7 @@ func TestRunExitCode(t *testing.T) {
Output: expect.All( Output: expect.All(
expect.Match(regexp.MustCompile("Exited [(]123[)][A-Za-z0-9 ]+"+data.Identifier("exit123"))), 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"))), 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("exit0")).State.Status, "exited")
assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit123")).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/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil { if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err)) t.Fatalf("Failed to get container host UID: %v", err)
t.FailNow()
} }
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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil { if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err)) t.Fatalf("Failed to get container host UID: %v", err)
t.FailNow()
} }
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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil { if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err)) t.Fatalf("Failed to get container host UID: %v", err)
t.FailNow()
} }
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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil { if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err)) t.Fatalf("Failed to get container host UID: %v", err)
t.FailNow()
} }
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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil { if err != nil {
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err)) t.Fatalf("Failed to get container host UID: %v", err)
t.FailNow()
} }
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/expect"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -68,7 +67,7 @@ func TestStartDetachKeys(t *testing.T) {
ExitCode: 0, ExitCode: 0,
Errors: []error{errors.New("detach keys")}, Errors: []error{errors.New("detach keys")},
Output: expect.All( 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")) 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 return nil
} }
assert.NilError(t, check(5)) assert.NilError(t, check(30))
base.Cmd("stop", testContainerName).AssertOK() base.Cmd("stop", testContainerName).AssertOK()
base.Cmd("exec", testContainerName, "ps").AssertFail() base.Cmd("exec", testContainerName, "ps").AssertFail()
if check(1) == nil { if check(1) == nil {
t.Fatal("expected to get an error") t.Fatal("expected to get an error")
} }
base.Cmd("start", testContainerName).AssertOK() base.Cmd("start", testContainerName).AssertOK()
assert.NilError(t, check(5)) assert.NilError(t, check(30))
} }
func TestStopWithStopSignal(t *testing.T) { 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 { for testURL, expectedIndexHTML := range testCases {
resp, err := nettestutil.HTTPGet(testURL, 5, false) resp, err := nettestutil.HTTPGet(testURL, 50, false)
assert.NilError(t, err) assert.NilError(t, err)
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
assert.NilError(t, err) 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/spf13/cobra"
"github.com/containerd/nerdctl/v2/pkg"
"github.com/containerd/nerdctl/v2/pkg/api/types" "github.com/containerd/nerdctl/v2/pkg/api/types"
) )
@ -47,39 +46,6 @@ func VerifyOptions(cmd *cobra.Command) (opt types.ImageVerifyOptions, err error)
return 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) { func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) {
debug, err := cmd.Flags().GetBool("debug") debug, err := cmd.Flags().GetBool("debug")
if err != nil { if err != nil {
@ -145,24 +111,6 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
if err != nil { if err != nil {
return types.GlobalCommandOptions{}, err 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{ return types.GlobalCommandOptions{
Debug: debug, Debug: debug,
@ -181,9 +129,6 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
BridgeIP: bridgeIP, BridgeIP: bridgeIP,
KubeHideDupe: kubeHideDupe, KubeHideDupe: kubeHideDupe,
CDISpecDirs: cdiSpecDirs, CDISpecDirs: cdiSpecDirs,
DNS: dns,
DNSOpts: dnsOpts,
DNSSearch: dnsSearch,
}, nil }, 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() base.Cmd("network", "inspect", fmt.Sprintf("%s_default", projectName)).AssertOK()
checkWordpress := func() error { 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 { if err != nil {
return err return err
} }

View File

@ -89,12 +89,6 @@ func convertCommand() *cobra.Command {
cmd.Flags().String("overlaybd-dbstr", "", "Database config string for overlaybd") cmd.Flags().String("overlaybd-dbstr", "", "Database config string for overlaybd")
// #endregion // #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 // #region generic flags
cmd.Flags().Bool("uncompress", false, "Convert tar.gz layers to uncompressed tar layers") 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") 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 // #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 // #region generic flags
uncompress, err := cmd.Flags().GetBool("uncompress") uncompress, err := cmd.Flags().GetBool("uncompress")
if err != nil { if err != nil {
@ -258,6 +237,37 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
return types.ImageConvertOptions{ return types.ImageConvertOptions{
GOptions: globalOptions, GOptions: globalOptions,
Format: format, 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 // #region generic flags
Uncompress: uncompress, Uncompress: uncompress,
Oci: oci, Oci: oci,
@ -266,45 +276,6 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
Platforms: platforms, Platforms: platforms,
AllPlatforms: allPlatforms, AllPlatforms: allPlatforms,
// #endregion // #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(), Stdout: cmd.OutOrStdout(),
}, nil }, nil
} }

View File

@ -19,14 +19,13 @@ package image
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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) { func TestImageConvert(t *testing.T) {
@ -89,24 +88,6 @@ func TestImageConvert(t *testing.T) {
}, },
Expected: test.Expects(0, nil, nil), 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" const remoteImageKey = "remoteImageKey"
var reg *registry.Server var registry *testregistry.RegistryServer
// 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")
testCase := &test.Case{ testCase := &test.Case{
Require: require.All( Require: require.All(
@ -133,30 +110,26 @@ func TestImageConvertNydusVerify(t *testing.T) {
require.Binary("nydusd"), require.Binary("nydusd"),
require.Not(nerdtest.Docker), require.Not(nerdtest.Docker),
nerdtest.Rootful, nerdtest.Rootful,
nerdtest.Registry,
), ),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) base := testutil.NewBase(t)
reg.Setup(data, helpers) registry = testregistry.NewWithNoAuth(base, 0, false)
data.Labels().Set(remoteImageKey, fmt.Sprintf("%s:%d/nydusd-image:test", "localhost", registry.Port))
data.Labels().Set(remoteImageKey, fmt.Sprintf("%s:%d/nydusd-image:test", "localhost", reg.Port))
helpers.Ensure("image", "convert", "--nydus", "--oci", testutil.CommonImage, data.Identifier("converted-image")) 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("tag", data.Identifier("converted-image"), data.Labels().Get(remoteImageKey))
helpers.Ensure("push", data.Labels().Get(remoteImageKey)) helpers.Ensure("push", data.Labels().Get(remoteImageKey))
}, },
Cleanup: func(data test.Data, helpers test.Helpers) { Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) helpers.Anyhow("rmi", "-f", data.Identifier("converted-image"))
if reg != nil { if registry != nil {
reg.Cleanup(data, helpers) registry.Cleanup(nil)
helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey)) helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey))
} }
}, },
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.Custom("nydusify", return helpers.Custom("nydusify",
"check", "check",
"--work-dir",
data.Temp().Dir("nydusify-temp"),
"--source", "--source",
testutil.CommonImage, testutil.CommonImage,
"--target", "--target",
@ -164,8 +137,6 @@ func TestImageConvertNydusVerify(t *testing.T) {
"--source-insecure", "--source-insecure",
"--target-insecure", "--target-insecure",
) )
cmd.WithTimeout(30 * time.Second)
return cmd
}, },
Expected: test.Expects(0, nil, nil), 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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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) { func TestImageEncryptJWE(t *testing.T) {
nerdtest.Setup() nerdtest.Setup()
var reg *registry.Server var registry *testregistry.RegistryServer
const remoteImageKey = "remoteImageKey" const remoteImageKey = "remoteImageKey"
@ -44,14 +44,12 @@ func TestImageEncryptJWE(t *testing.T) {
require.Not(nerdtest.Docker), require.Not(nerdtest.Docker),
// This test needs to rmi the common image // This test needs to rmi the common image
nerdtest.Private, nerdtest.Private,
nerdtest.Registry,
), ),
Cleanup: func(data test.Data, helpers test.Helpers) { Cleanup: func(data test.Data, helpers test.Helpers) {
if reg != nil { if registry != nil {
reg.Cleanup(data, helpers) registry.Cleanup(nil)
helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey)) helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey))
} }
helpers.Anyhow("rmi", "-f", data.Identifier("decrypted")) helpers.Anyhow("rmi", "-f", data.Identifier("decrypted"))
}, },
Setup: func(data test.Data, helpers test.Helpers) { 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("private", pri)
data.Labels().Set("public", pub) data.Labels().Set("public", pub)
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) base := testutil.NewBase(t)
reg.Setup(data, helpers) registry = testregistry.NewWithNoAuth(base, 0, false)
helpers.Ensure("pull", "--quiet", testutil.CommonImage) 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) helpers.Ensure("image", "encrypt", "--recipient=jwe:"+pub, testutil.CommonImage, encryptImageRef)
inspector := helpers.Capture("image", "inspect", "--mode=native", "--format={{len .Index.Manifests}}", encryptImageRef) inspector := helpers.Capture("image", "inspect", "--mode=native", "--format={{len .Index.Manifests}}", encryptImageRef)
assert.Equal(t, inspector, "1\n") 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/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/formatter"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -44,43 +43,6 @@ type historyObj struct {
Comment string 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) { func decode(stdout string) ([]historyObj, error) {
dec := json.NewDecoder(strings.NewReader(stdout)) dec := json.NewDecoder(strings.NewReader(stdout))
object := []historyObj{} object := []historyObj{}
@ -128,65 +90,65 @@ func TestImageHistory(t *testing.T) {
{ {
Description: "trunc, no quiet, human", Description: "trunc, no quiet, human",
Command: test.Command("image", "history", "--human=true", "--format=json", testutil.CommonImage), 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) history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail") assert.NilError(t, err, info)
assert.Equal(t, len(history), 2, "history should be 2 in length") assert.Equal(t, len(history), 2, info)
h0Time, _ := time.Parse(time.RFC3339, history[0].CreatedAt) localTimeL1, _ := time.Parse(time.RFC3339, "2021-03-31T10:21:23-07:00")
h1Time, _ := time.Parse(time.RFC3339, history[1].CreatedAt) localTimeL2, _ := time.Parse(time.RFC3339, "2021-03-31T10:21:21-07:00")
comp0Time, _ := time.Parse(time.RFC3339, expectedHistory[0].CreatedAt) compTime1, _ := time.Parse(time.RFC3339, history[0].CreatedAt)
comp1Time, _ := time.Parse(time.RFC3339, expectedHistory[1].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].Size, "0B", info)
assert.Equal(t, history[0].CreatedBy, expectedHistory[0].CreatedBy) assert.Equal(t, history[0].CreatedSince, formatter.TimeSinceInHuman(compTime1), info)
assert.Equal(t, history[0].Size, expectedHistory[0].Size) assert.Equal(t, history[0].Snapshot, "<missing>", info)
assert.Equal(t, history[0].CreatedSince, expectedHistory[0].CreatedSince) assert.Equal(t, history[0].Comment, "", info)
assert.Equal(t, history[0].Snapshot, expectedHistory[0].Snapshot)
assert.Equal(t, history[0].Comment, expectedHistory[0].Comment)
assert.Equal(t, h1Time.UTC().String(), comp1Time.UTC().String()) assert.Equal(t, history[1].Size, "5.947MB", info)
assert.Equal(t, history[1].CreatedBy, expectedHistory[1].CreatedBy) assert.Equal(t, history[1].CreatedSince, formatter.TimeSinceInHuman(compTime2), info)
assert.Equal(t, history[1].Size, expectedHistory[1].Size) assert.Equal(t, history[1].Snapshot, "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c…", info)
assert.Equal(t, history[1].CreatedSince, expectedHistory[1].CreatedSince) assert.Equal(t, history[1].Comment, "", info)
assert.Equal(t, history[1].Snapshot, expectedHistory[1].Snapshot)
assert.Equal(t, history[1].Comment, expectedHistory[1].Comment)
}), }),
}, },
{ {
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), 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) history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail") assert.NilError(t, err, info)
assert.Equal(t, history[0].Size, expectedHistoryNoTrunc[0].Size) assert.Equal(t, history[0].Size, "0", info)
assert.Equal(t, history[0].CreatedSince, history[0].CreatedAt) assert.Equal(t, history[0].CreatedSince, history[0].CreatedAt, info)
assert.Equal(t, history[1].Size, expectedHistoryNoTrunc[1].Size) assert.Equal(t, history[1].Size, "5947392", info)
assert.Equal(t, history[1].CreatedSince, history[1].CreatedAt) assert.Equal(t, history[1].CreatedSince, history[1].CreatedAt, info)
}), }),
}, },
{ {
Description: "no trunc - do not truncate sha or cmd", Description: "no trunc - do not truncate sha or cmd",
Command: test.Command("image", "history", "--human=false", "--no-trunc", "--format=json", testutil.CommonImage), 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) history, err := decode(stdout)
assert.NilError(t, err, "decode should not fail") assert.NilError(t, err, info)
assert.Equal(t, history[1].Snapshot, expectedHistoryNoTrunc[1].Snapshot) assert.Equal(t, history[1].Snapshot, "sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a")
assert.Equal(t, history[1].CreatedBy, expectedHistoryNoTrunc[1].CreatedBy) 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", 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), Command: test.Command("image", "history", "--human=false", "--no-trunc", "--quiet", 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) {
assert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+"\n"+expectedHistoryNoTrunc[1].Snapshot+"\n") assert.Equal(t, stdout, "<missing>\nsha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a\n")
}), }),
}, },
{ {
Description: "With quiet, trunc has no effect", Description: "With quiet, trunc has no effect",
Command: test.Command("image", "history", "--human=false", "--no-trunc", "--quiet", testutil.CommonImage), Command: test.Command("image", "history", "--human=false", "--no-trunc", "--quiet", 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) {
assert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+"\n"+expectedHistoryNoTrunc[1].Snapshot+"\n") 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/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -46,14 +45,14 @@ func TestImageInspectSimpleCases(t *testing.T) {
{ {
Description: "Contains some stuff", Description: "Contains some stuff",
Command: test.Command("image", "inspect", testutil.CommonImage), 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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n") assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
assert.Assert(t, len(dc[0].RootFS.Layers) > 0, "there should be at least one rootfs layer\n") assert.Assert(t, len(dc[0].RootFS.Layers) > 0, info)
assert.Assert(t, dc[0].Architecture != "", "architecture should be set\n") assert.Assert(t, dc[0].Architecture != "", info)
assert.Assert(t, dc[0].Size > 0, "size should be > 0 \n") assert.Assert(t, dc[0].Size > 0, info)
}), }),
}, },
{ {
@ -116,11 +115,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"), Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n") assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
reference := dc[0].ID reference := dc[0].ID
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:") sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
@ -141,11 +140,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"), Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n") assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
reference := dc[0].ID reference := dc[0].ID
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:") sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
@ -174,11 +173,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"), Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n") assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:") sha := strings.TrimPrefix(dc[0].RepoDigests[0], "busybox@sha256:")
for _, id := range []string{"doesnotexist", "doesnotexist:either", "busybox:bogustag"} { for _, id := range []string{"doesnotexist", "doesnotexist:either", "busybox:bogustag"} {
@ -197,11 +196,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox"), Command: test.Command("image", "inspect", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n") assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info)
for _, id := range []string{"∞∞∞∞∞∞∞∞∞∞", "busybox:∞∞∞∞∞∞∞∞∞∞"} { for _, id := range []string{"∞∞∞∞∞∞∞∞∞∞", "busybox:∞∞∞∞∞∞∞∞∞∞"} {
cmd := helpers.Command("image", "inspect", id) cmd := helpers.Command("image", "inspect", id)
@ -219,11 +218,11 @@ func TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {
Command: test.Command("image", "inspect", "busybox", "busybox"), Command: test.Command("image", "inspect", "busybox", "busybox"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var dc []dockercompat.Image
err := json.Unmarshal([]byte(stdout), &dc) err := json.Unmarshal([]byte(stdout), &dc)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, 2, len(dc), "Unexpectedly did not get 2 results\n") assert.Equal(t, 2, len(dc), "Unexpectedly did not get 2 results\n"+info)
reference := nerdtest.InspectImage(helpers, "busybox") reference := nerdtest.InspectImage(helpers, "busybox")
assert.Equal(t, dc[0].ID, reference.ID) assert.Equal(t, dc[0].ID, reference.ID)
assert.Equal(t, dc[1].ID, reference.ID) assert.Equal(t, dc[1].ID, reference.ID)

View File

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

View File

@ -18,6 +18,8 @@ package image
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -27,7 +29,6 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -71,7 +72,8 @@ func TestImagePrune(t *testing.T) {
`, testutil.CommonImage) `, testutil.CommonImage)
buildCtx := data.Temp().Path() 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", buildCtx)
// After we rebuild with tag, docker will no longer show the <none> version from above // After we rebuild with tag, docker will no longer show the <none> version from above
// Swapping order does not change anything. // Swapping order does not change anything.
@ -85,13 +87,13 @@ func TestImagePrune(t *testing.T) {
identifier := data.Identifier() identifier := data.Identifier()
return &test.Expected{ return &test.Expected{
Output: expect.All( Output: expect.All(
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, identifier)) 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") imgList := helpers.Capture("images")
assert.Assert(t, !strings.Contains(imgList, "<none>"), imgList) 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) `, testutil.CommonImage)
buildCtx := data.Temp().Path() 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", buildCtx)
helpers.Ensure("build", "-t", identifier, buildCtx) helpers.Ensure("build", "-t", identifier, buildCtx)
imgList := helpers.Capture("images") imgList := helpers.Capture("images")
@ -130,18 +133,18 @@ func TestImagePrune(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: expect.All( Output: expect.All(
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier())) 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") 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) assert.Assert(t, !strings.Contains(imgList, "<none>"), imgList)
helpers.Ensure("rm", "-f", data.Identifier()) helpers.Ensure("rm", "-f", data.Identifier())
removed := helpers.Capture("image", "prune", "--force", "--all") 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") 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 foo=bar
LABEL version=0.1`, testutil.CommonImage) LABEL version=0.1`, testutil.CommonImage)
buildCtx := data.Temp().Path() 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) helpers.Ensure("build", "-t", data.Identifier(), buildCtx)
imgList := helpers.Capture("images") imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()), "Missing "+data.Identifier()) 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{ return &test.Expected{
Output: expect.All( Output: expect.All(
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
assert.Assert(t, !strings.Contains(stdout, data.Identifier())) 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") 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") 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") 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" 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) CMD ["echo", "nerdctl-test-image-prune-until"]`, testutil.CommonImage)
buildCtx := data.Temp().Path() 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) helpers.Ensure("build", "-t", data.Identifier(), buildCtx)
imgList := helpers.Capture("images") imgList := helpers.Capture("images")
assert.Assert(t, strings.Contains(imgList, data.Identifier()), "Missing "+data.Identifier()) 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{ return &test.Expected{
Output: expect.All( Output: expect.All(
expect.DoesNotContain(data.Labels().Get("imageID")), expect.DoesNotContain(data.Labels().Get("imageID")),
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images") 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{ return &test.Expected{
Output: expect.All( Output: expect.All(
expect.Contains(data.Labels().Get("imageID")), expect.Contains(data.Labels().Get("imageID")),
func(stdout string, t tig.T) { func(stdout string, info string, t *testing.T) {
imgList := helpers.Capture("images") 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -130,8 +129,8 @@ CMD ["echo", "nerdctl-build-test-string"]
data.Temp().Save(dockerfile, "Dockerfile") data.Temp().Save(dockerfile, "Dockerfile")
reg = nerdtest.RegistryWithNoAuth(data, helpers, 80, false) reg = nerdtest.RegistryWithNoAuth(data, helpers, 80, false)
reg.Setup(data, helpers) reg.Setup(data, helpers)
testImageRef := fmt.Sprintf("%s/%s", testImageRef := fmt.Sprintf("%s/%s:%s",
reg.IP.String(), data.Identifier()) reg.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
buildCtx := data.Temp().Path() buildCtx := data.Temp().Path()
helpers.Ensure("build", "-t", testImageRef, buildCtx) helpers.Ensure("build", "-t", testImageRef, buildCtx)
@ -183,7 +182,7 @@ func TestImagePullSoci(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Custom("mount") cmd := helpers.Custom("mount")
cmd.Run(&test.Expected{ 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"))) 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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")) remoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get("remoteSnapshotsInitialCount"))
remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge") remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge")
assert.Equal(t, assert.Equal(t,
@ -219,7 +218,7 @@ func TestImagePullSoci(t *testing.T) {
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
cmd := helpers.Custom("mount") cmd := helpers.Custom("mount")
cmd.Run(&test.Expected{ 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"))) 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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")) remoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get("remoteSnapshotsInitialCount"))
remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge") remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge")
assert.Equal(t, assert.Equal(t,

View File

@ -20,53 +20,43 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"testing" "testing"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "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) { func TestPush(t *testing.T) {
nerdtest.Setup() nerdtest.Setup()
var registryNoAuthHTTPRandom, registryNoAuthHTTPDefault, registryTokenAuthHTTPSRandom *registry.Server var registryNoAuthHTTPRandom, registryNoAuthHTTPDefault, registryTokenAuthHTTPSRandom *testregistry.RegistryServer
var tokenServer *registry.TokenAuthServer
testCase := &test.Case{ testCase := &test.Case{
Require: require.All( Require: require.Linux,
require.Linux,
nerdtest.Registry,
),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
registryNoAuthHTTPRandom = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) base := testutil.NewBase(t)
registryNoAuthHTTPRandom.Setup(data, helpers) registryNoAuthHTTPRandom = testregistry.NewWithNoAuth(base, 0, false)
registryNoAuthHTTPDefault = nerdtest.RegistryWithNoAuth(data, helpers, 80, false) registryNoAuthHTTPDefault = testregistry.NewWithNoAuth(base, 80, false)
registryNoAuthHTTPDefault.Setup(data, helpers) registryTokenAuthHTTPSRandom = testregistry.NewWithTokenAuth(base, "admin", "badmin", 0, true)
registryTokenAuthHTTPSRandom, tokenServer = nerdtest.RegistryWithTokenAuth(data, helpers, "admin", "badmin", 0, true)
tokenServer.Setup(data, helpers)
registryTokenAuthHTTPSRandom.Setup(data, helpers)
}, },
Cleanup: func(data test.Data, helpers test.Helpers) { Cleanup: func(data test.Data, helpers test.Helpers) {
if registryNoAuthHTTPRandom != nil { if registryNoAuthHTTPRandom != nil {
registryNoAuthHTTPRandom.Cleanup(data, helpers) registryNoAuthHTTPRandom.Cleanup(nil)
} }
if registryNoAuthHTTPDefault != nil { if registryNoAuthHTTPDefault != nil {
registryNoAuthHTTPDefault.Cleanup(data, helpers) registryNoAuthHTTPDefault.Cleanup(nil)
} }
if registryTokenAuthHTTPSRandom != nil { if registryTokenAuthHTTPSRandom != nil {
registryTokenAuthHTTPSRandom.Cleanup(data, helpers) registryTokenAuthHTTPSRandom.Cleanup(nil)
}
if tokenServer != nil {
tokenServer.Cleanup(data, helpers)
} }
}, },
@ -75,8 +65,8 @@ func TestPush(t *testing.T) {
Description: "plain http", Description: "plain http",
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier()) registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
}, },
@ -95,8 +85,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier()) registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
}, },
@ -114,8 +104,8 @@ func TestPush(t *testing.T) {
Description: "plain http with localhost", Description: "plain http with localhost",
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
"127.0.0.1", registryNoAuthHTTPRandom.Port, data.Identifier()) "127.0.0.1", registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
}, },
@ -129,8 +119,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s/%s", testImageRef := fmt.Sprintf("%s/%s:%s",
registryNoAuthHTTPDefault.IP.String(), data.Identifier()) registryNoAuthHTTPDefault.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
}, },
@ -149,8 +139,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier()) registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
helpers.Ensure("--insecure-registry", "login", "-u", "admin", "-p", "badmin", helpers.Ensure("--insecure-registry", "login", "-u", "admin", "-p", "badmin",
@ -172,8 +162,8 @@ func TestPush(t *testing.T) {
Require: require.Not(nerdtest.Docker), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.CommonImage) helpers.Ensure("pull", "--quiet", testutil.CommonImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier()) registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier(), strings.Split(testutil.CommonImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.CommonImage, testImageRef) helpers.Ensure("tag", testutil.CommonImage, testImageRef)
helpers.Ensure("--hosts-dir", registryTokenAuthHTTPSRandom.HostsDir, "login", "-u", "admin", "-p", "badmin", 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), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage) helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier()) registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.NonDistBlobImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.NonDistBlobImage, 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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) blobURL := fmt.Sprintf("http://%s:%d/v2/%s/blobs/%s", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)
resp, err := http.Get(blobURL) resp, err := http.Get(blobURL)
assert.Assert(t, err, "error making http request") assert.Assert(t, err, "error making http request")
if resp.Body != nil { if resp.Body != nil {
_ = resp.Body.Close() resp.Body.Close()
} }
assert.Equal(t, resp.StatusCode, http.StatusNotFound, "non-distributable blob should not be available") 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), Require: require.Not(nerdtest.Docker),
Setup: func(data test.Data, helpers test.Helpers) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage) helpers.Ensure("pull", "--quiet", testutil.NonDistBlobImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier()) registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.NonDistBlobImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.NonDistBlobImage, 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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) blobURL := fmt.Sprintf("http://%s:%d/v2/%s/blobs/%s", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)
resp, err := http.Get(blobURL) resp, err := http.Get(blobURL)
assert.Assert(t, err, "error making http request") assert.Assert(t, err, "error making http request")
if resp.Body != nil { if resp.Body != nil {
_ = resp.Body.Close() resp.Body.Close()
} }
assert.Equal(t, resp.StatusCode, http.StatusOK, "non-distributable blob should be available") 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) { Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("pull", "--quiet", testutil.UbuntuImage) helpers.Ensure("pull", "--quiet", testutil.UbuntuImage)
testImageRef := fmt.Sprintf("%s:%d/%s", testImageRef := fmt.Sprintf("%s:%d/%s:%s",
registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier()) registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), strings.Split(testutil.UbuntuImage, ":")[1])
data.Labels().Set("testImageRef", testImageRef) data.Labels().Set("testImageRef", testImageRef)
helpers.Ensure("tag", testutil.UbuntuImage, 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "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/imgutil"
"github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil"
@ -64,7 +63,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("image is being used")}, 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{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName), Output: expect.Contains(repoName),
}) })
@ -84,7 +83,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage), Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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{ helpers.Command("images").Run(&test.Expected{
Output: expect.DoesNotContain(repoName), Output: expect.DoesNotContain(repoName),
}) })
@ -109,7 +108,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("image is being used")}, 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{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName), Output: expect.Contains(repoName),
}) })
@ -141,7 +140,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, Errors: []error{},
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains("<none>"), Output: expect.Contains("<none>"),
}) })
@ -163,7 +162,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("image is being used")}, 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{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName), Output: expect.Contains(repoName),
}) })
@ -185,7 +184,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage), Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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{ helpers.Command("images").Run(&test.Expected{
// a created container with removed image doesn't impact other `rmi` command // a created container with removed image doesn't impact other `rmi` command
Output: expect.DoesNotContain(repoName, nginxRepoName), Output: expect.DoesNotContain(repoName, nginxRepoName),
@ -213,7 +212,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("image is being used")}, 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{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName), Output: expect.Contains(repoName),
}) })
@ -247,7 +246,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, Errors: []error{},
Output: func(stdout string, t tig.T) { Output: func(stdout string, info string, t *testing.T) {
helpers.Command("images").Run(&test.Expected{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains("<none>"), Output: expect.Contains("<none>"),
}) })
@ -273,7 +272,7 @@ func TestRemove(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("image is being used")}, 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{ helpers.Command("images").Run(&test.Expected{
Output: expect.Contains(repoName), Output: expect.Contains(repoName),
}) })
@ -294,7 +293,7 @@ func TestRemove(t *testing.T) {
Command: test.Command("rmi", "-f", testutil.CommonImage), Command: test.Command("rmi", "-f", testutil.CommonImage),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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{ helpers.Command("images").Run(&test.Expected{
Output: expect.DoesNotContain(repoName), Output: expect.DoesNotContain(repoName),
}) })
@ -337,10 +336,10 @@ func TestIssue3016(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, 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{ helpers.Command("images", data.Labels().Get(tagIDKey)).Run(&test.Expected{
ExitCode: 0, 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) assert.Equal(t, len(strings.Split(stdout, "\n")), 2)
}, },
}) })
@ -379,17 +378,17 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, 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{ 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") 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{ 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") 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{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, 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{ 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") 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{ 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") 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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{ 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") 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{ 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") 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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{ helpers.Command("--kube-hide-dupe", "rmi", stdout[0:12]).Run(&test.Expected{
ExitCode: 1, ExitCode: 1,
Errors: []error{errors.New("multiple IDs found with provided prefix: ")}, Errors: []error{errors.New("multiple IDs found with provided prefix: ")},
@ -479,9 +478,9 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
ExitCode: 0, ExitCode: 0,
}) })
helpers.Command("images").Run(&test.Expected{ 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") 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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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") imgID := strings.Split(stdout, "\n")
helpers.Command("--kube-hide-dupe", "rmi", imgID[0]).Run(&test.Expected{ helpers.Command("--kube-hide-dupe", "rmi", imgID[0]).Run(&test.Expected{
ExitCode: 1, ExitCode: 1,
@ -510,9 +509,9 @@ func TestRemoveKubeWithKubeHideDupe(t *testing.T) {
ExitCode: 0, ExitCode: 0,
}) })
helpers.Command("images").Run(&test.Expected{ 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") 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 { func SaveCommand() *cobra.Command {
var cmd = &cobra.Command{ var cmd = &cobra.Command{
Use: "save [flags] IMAGE [IMAGE...]", Use: "save",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Short: "Save one or more images to a tar archive (streamed to STDOUT by default)", 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.", 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/expect"
"github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil" "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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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") rootfsPath := filepath.Join(data.Temp().Path(), "rootfs")
err := testhelpers.ExtractDockerArchive(filepath.Join(data.Temp().Path(), "out.tar"), rootfsPath) err := testhelpers.ExtractDockerArchive(filepath.Join(data.Temp().Path(), "out.tar"), rootfsPath)
assert.NilError(t, err) assert.NilError(t, err)
@ -189,7 +188,7 @@ func TestSaveMultipleImagesWithSameIDAndLoad(t *testing.T) {
return &test.Expected{ return &test.Expected{
ExitCode: 0, ExitCode: 0,
Errors: []error{}, 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) assert.Equal(t, strings.Count(stdout, data.Labels().Get("id")), 2)
}, },
} }

View File

@ -23,7 +23,6 @@ import (
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"github.com/containerd/nerdctl/mod/tigron/test" "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/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/testutil" "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 { Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &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 var inspectResult []json.RawMessage
err := json.Unmarshal([]byte(stdout), &inspectResult) err := json.Unmarshal([]byte(stdout), &inspectResult)
assert.NilError(t, err, "Unable to unmarshal output\n") assert.NilError(t, err, "Unable to unmarshal output\n"+info)
assert.Equal(t, len(inspectResult), 2, "Unexpectedly got multiple results\n") assert.Equal(t, len(inspectResult), 2, "Unexpectedly got multiple results\n"+info)
var dci dockercompat.Image var dci dockercompat.Image
err = json.Unmarshal(inspectResult[0], &dci) 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) 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 var dcc dockercompat.Container
err = json.Unmarshal(inspectResult[1], &dcc) 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()) 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