Compare commits

...

221 Commits
v2.1.1 ... main

Author SHA1 Message Date
Akihiro Suda 507bfee191
Merge pull request #4408 from containerd/dependabot/go_modules/docker-0e145687d2
build(deps): bump the docker group with 2 updates
2025-07-14 17:26:39 +09:00
Akihiro Suda 38596c8b22
Merge pull request #4409 from containerd/dependabot/go_modules/golang-x-00b2bb0006
build(deps): bump the golang-x group across 1 directory with 6 updates
2025-07-12 23:07:05 +09:00
dependabot[bot] 6157151749
build(deps): bump the golang-x group across 1 directory with 6 updates
Bumps the golang-x group with 2 updates in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto) and [golang.org/x/net](https://github.com/golang/net).


Updates `golang.org/x/crypto` from 0.39.0 to 0.40.0
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

Updates `golang.org/x/net` from 0.41.0 to 0.42.0
- [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0)

Updates `golang.org/x/sync` from 0.15.0 to 0.16.0
- [Commits](https://github.com/golang/sync/compare/v0.15.0...v0.16.0)

Updates `golang.org/x/sys` from 0.33.0 to 0.34.0
- [Commits](https://github.com/golang/sys/compare/v0.33.0...v0.34.0)

Updates `golang.org/x/term` from 0.32.0 to 0.33.0
- [Commits](https://github.com/golang/term/compare/v0.32.0...v0.33.0)

Updates `golang.org/x/text` from 0.26.0 to 0.27.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/net
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/sync
  dependency-version: 0.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/sys
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/term
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/text
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-10 22:04:45 +00:00
dependabot[bot] 248d28a764
build(deps): bump the docker group with 2 updates
Bumps the docker group with 2 updates: [github.com/docker/cli](https://github.com/docker/cli) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/cli` from 28.3.1+incompatible to 28.3.2+incompatible
- [Commits](https://github.com/docker/cli/compare/v28.3.1...v28.3.2)

Updates `github.com/docker/docker` from 28.3.1+incompatible to 28.3.2+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.1...v28.3.2)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 28.3.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-09 22:08:24 +00:00
Akihiro Suda ff9323859a
Merge pull request #4404 from AkihiroSuda/dev
update containerd (2.1.3), BuildKit (0.23.2), slirp4netns (1.3.3), etc.
2025-07-08 09:33:58 +09:00
Akihiro Suda 2da141b283
update Kubo (0.35.0)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:22:00 +09:00
Akihiro Suda d3a009f139
update Nydus (2.3.2)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:21:39 +09:00
Akihiro Suda fb86ede0a2
update gotestsum (1.12.3)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:21:21 +09:00
Akihiro Suda 2894a94c12
update slirp4netns (1.3.3)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:19:03 +09:00
Akihiro Suda 27b707f152
update BuildKit (0.23.2)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:17:52 +09:00
Akihiro Suda f450c33b75
update containerd (2.1.3)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-07-07 17:15:59 +09:00
Akihiro Suda e6429167ae
Merge pull request #4399 from ChengyuZhu6/refactor
Rename flag variables to descriptive boolean names
2025-07-07 16:31:41 +09:00
Akihiro Suda 6c1728eae2
Merge pull request #4401 from Mr-Sunglasses/fix/#4313
feat: Add completions for 'nerdctl network create -o'
2025-07-07 16:23:21 +09:00
Kanishk Pachauri d9d2f0b1b4
feat: Add completions for 'nerdctl network create -o'
Signed-off-by: Kanishk Pachauri <itskanishkp.py@gmail.com>

fix: remove unimplemented networks

Signed-off-by: Kanishk Pachauri <itskanishkp.py@gmail.com>

fix: remove unimplemented networks

Signed-off-by: Kanishk Pachauri <itskanishkp.py@gmail.com>
2025-07-06 11:50:07 +05:30
Akihiro Suda a3e753580e
Merge pull request #4402 from containerd/dependabot/go_modules/docker-adc901a5e6
build(deps): bump the docker group with 2 updates
2025-07-04 13:01:28 +09:00
dependabot[bot] a7b01be272
build(deps): bump the docker group with 2 updates
Bumps the docker group with 2 updates: [github.com/docker/cli](https://github.com/docker/cli) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/cli` from 28.3.0+incompatible to 28.3.1+incompatible
- [Commits](https://github.com/docker/cli/compare/v28.3.0...v28.3.1)

Updates `github.com/docker/docker` from 28.3.0+incompatible to 28.3.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.0...v28.3.1)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 28.3.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 22:36:39 +00:00
ChengyuZhu6 2eb82b6c61 rename flag variables to descriptive boolean names
Improves code readability by replacing short flag names (flagA, flagI, flagT, flagD)
with descriptive boolean names (isAttach, isInteractive, isTerminal, isDetach).

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-07-03 21:41:50 +08:00
Akihiro Suda bc05100159
Merge pull request #4398 from ChengyuZhu6/save
cmd/image: update save command usage string
2025-07-03 11:53:23 +09:00
Akihiro Suda 14a59a2b76
Merge pull request #4378 from swagatbora90/add-global-dns-config
Enable setting DNS options through global nerdctl config
2025-07-03 11:49:43 +09:00
ChengyuZhu6 0b068a559d cmd/image: update save command usage string
Show proper syntax with flags and image arguments in help text.

Fixes: #4397

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-07-03 10:17:32 +08:00
Akihiro Suda bae71e646d
Merge pull request #4300 from coderbirju/add-soci-convert
add soci to nerdctl image convert
2025-07-03 11:14:20 +09:00
Arjun Raja Yogidas 337f5a134e fix golangci-lint
Signed-off-by: Arjun Raja Yogidas <arjunry@amazon.com>
2025-07-02 20:49:30 +00:00
Swagat Bora 1ac8bb4708 Enable setting DNS options through global nerdctl config
Signed-off-by: Swagat Bora <sbora@amazon.com>
2025-07-02 16:48:02 +00:00
Arjun Raja Yogidas b4bcc305ed check soci version
Signed-off-by: Arjun Raja Yogidas <arjunry@amazon.com>
2025-07-02 16:40:57 +00:00
Arjun Raja Yogidas 445fb7b140 add soci convert feature
Signed-off-by: Arjun Raja Yogidas <arjunry@amazon.com>
2025-07-02 16:40:57 +00:00
Akihiro Suda 75640408c5
Merge pull request #4396 from Shubhranshu153/update-soci-version
update soci version to latest
2025-07-02 16:43:16 +09:00
Shubhranshu Mahapatra 824af52753 update soci base version to latest
Signed-off-by: Shubhranshu Mahapatra <shubhum@amazon.com>
2025-07-02 00:33:18 -07:00
Akihiro Suda 5c256f9ea1
Merge pull request #4388 from ChengyuZhu6/zstd
commit: Add support for converting writable layers to zstdchunked blobs
2025-07-02 16:07:51 +09:00
Akihiro Suda ece3f19a66
Merge pull request #4393 from Shubhranshu153/fix-soci-test
fix: allow soci v1 pulls for existing tests
2025-07-02 16:07:10 +09:00
Mahapatra 71a1d7bcaa fix: allow soci v1 pulls for existing tests
Signed-off-by: Shubhranshu Mahapatra <shubhum@amazon.com>
2025-07-01 20:44:19 -07:00
fahed dorgaa 02cfb57857
Merge pull request #4392 from swagatbora90/wait_for_process_io
exec: wait for I/O completion before return
2025-07-01 20:43:47 +02:00
Swagat Bora a59d0ac61a exec: wait for I/O completion before return
Signed-off-by: Swagat Bora <sbora@amazon.com>
2025-07-01 17:41:54 +00:00
ChengyuZhu6 2baeb05d7c commit: support zstdchunked conversion with writable layer in container commit
support zstdchunked conversion with writable layer in container commit

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-07-01 14:54:25 +08:00
Akihiro Suda 0b89b6bb00
Merge pull request #4390 from containerd/dependabot/go_modules/go.yaml.in/yaml/v3-3.0.4
build(deps): bump go.yaml.in/yaml/v3 from 3.0.3 to 3.0.4
2025-07-01 14:46:03 +09:00
Akihiro Suda 07cc69cb30
Merge pull request #4389 from containerd/dependabot/go_modules/github.com/compose-spec/compose-go/v2-2.7.1
build(deps): bump github.com/compose-spec/compose-go/v2 from 2.6.5 to 2.7.1
2025-07-01 14:21:09 +09:00
dependabot[bot] 671b7fe99a
build(deps): bump go.yaml.in/yaml/v3 from 3.0.3 to 3.0.4
Bumps [go.yaml.in/yaml/v3](https://github.com/yaml/go-yaml) from 3.0.3 to 3.0.4.
- [Commits](https://github.com/yaml/go-yaml/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: go.yaml.in/yaml/v3
  dependency-version: 3.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 02:14:28 +00:00
dependabot[bot] 42b1c2eb9d
build(deps): bump github.com/compose-spec/compose-go/v2
Bumps [github.com/compose-spec/compose-go/v2](https://github.com/compose-spec/compose-go) from 2.6.5 to 2.7.1.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v2.6.5...v2.7.1)

---
updated-dependencies:
- dependency-name: github.com/compose-spec/compose-go/v2
  dependency-version: 2.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 01:19:12 +00:00
Akihiro Suda 8e777577fd
Merge pull request #4381 from AkihiroSuda/no_ipfs
Add build tag `no_ipfs`
2025-06-30 16:31:16 +09:00
Akihiro Suda b66e7fa843
Merge pull request #4386 from containerd/dependabot/go_modules/github.com/Masterminds/semver/v3-3.4.0
build(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 to 3.4.0
2025-06-30 15:18:29 +09:00
Akihiro Suda c731c259a9
Add build tag `no_ipfs`
```
BUILDTAGS=no_ipfs make
```

Discussed in issue 1986

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-06-30 14:49:44 +09:00
dependabot[bot] 53ee7d84d2
build(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 to 3.4.0
Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/semver/v3
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 02:00:55 +00:00
Akihiro Suda b31cbf7a01
Merge pull request #4385 from haytok/followup_of_issue_4027
fix: allow storing additional network info in network-config.json
2025-06-30 10:58:45 +09:00
Akihiro Suda c555e0228c
Merge pull request #4383 from ningmingxiao/fix_health_check
bugfix:wait health check log complete
2025-06-30 10:58:21 +09:00
Hayato Kiwata 59890cbd87 fix: allow storing additional network info in network-config.json
When we specify the `-p` option with `nerdctl run`, etc., the port mapping
information is stored in the following `network-config.json`.

```
<DATAROOT>/<ADDRHASH>/containers/<NAMESPACE>/<CID>/network-config.json
```

However, the current implementation can only store port mapping
information and does not have the extensibility to store other
network-related information.

This point was feedback in the following PR.

- https://github.com/containerd/nerdctl/pull/4376

Therefore, this commit updates the `network-config.json` to allow for the
expansion of the information that can be stored.

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-27 17:47:01 +00:00
ningmingxiao 7f8422b33f bugfix:wait health check log complete
Signed-off-by: ningmingxiao <ning.mingxiao@zte.com.cn>
2025-06-27 16:06:20 +08:00
Akihiro Suda 90e0caaf46
Merge pull request #4358 from apostasie/2025-06-fix-4343
[CI]: make healthcheck tests easier to debug
2025-06-26 15:35:30 +09:00
Akihiro Suda e832586924
Merge pull request #4372 from apostasie/2025-06-compose-exec-unlock
Allow compose exec concurrency
2025-06-26 15:33:55 +09:00
Akihiro Suda b25edf5694
Merge pull request #4354 from apostasie/2025-06-fixes
Hardening: internal/filesystem: prevent inode change on failure
2025-06-26 09:43:52 +09:00
Akihiro Suda 6fb2b45a0e
Merge pull request #4375 from containerd/dependabot/go_modules/docker-5b59b55a2a
build(deps): bump the docker group with 2 updates
2025-06-26 09:43:05 +09:00
dependabot[bot] 9f3eab454f
build(deps): bump the docker group with 2 updates
Bumps the docker group with 2 updates: [github.com/docker/cli](https://github.com/docker/cli) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/cli` from 28.2.2+incompatible to 28.3.0+incompatible
- [Commits](https://github.com/docker/cli/compare/v28.2.2...v28.3.0)

Updates `github.com/docker/docker` from 28.2.2+incompatible to 28.3.0+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.2.2...v28.3.0)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 28.3.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 22:13:21 +00:00
Akihiro Suda eb6b492937
Merge pull request #4290 from haytok/issue_4027
fix: allow containers to start using a large numbers of ports
2025-06-26 06:03:41 +09:00
apostasie b6ceafb667
Allow compose exec concurrency
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-25 13:36:08 -07:00
Akihiro Suda c1dcfa4ad3
Merge pull request #4359 from apostasie/2025-06-fix-4146
[CI]: rewrite compose test
2025-06-26 03:30:35 +09:00
apostasie 53e69a3800
Rewrite compose test (fix 4146)
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-25 10:31:53 -07:00
apostasie f7a92b1c3d
Improve debugging for healthcheck tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-25 09:53:01 -07:00
apostasie dc7971fb43
internal/filesystem minor fix
In case of error during write, the destination is being removed (before being possibly restored).
This may lead to certain (failure) scenarios where the inode would change, effectively breaking
container mounted files.

Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-25 08:33:54 -07:00
Akihiro Suda 599681f519
Merge pull request #4348 from apostasie/2025-05-ci-proofing-2
[CI] Fix broken image ref parsing and hard-coded data in tests
2025-06-25 23:04:36 +09:00
Akihiro Suda 15b2cc3a4e
Merge pull request #4355 from apostasie/2025-06-image-history
[CI] Cleanup TestImageHistory
2025-06-25 19:58:45 +09:00
Hayato Kiwata c13417d74a docs: add network-config.json description to dir.md
Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-25 09:34:46 +00:00
Hayato Kiwata 7d768be794 fix: allow containers to start using a large number of ports
Suppose we have a compose.yaml that allocates a large numbers of ports as
follows.

```
> cat compose.yaml
services:
  svc0:
    image: alpine
    command: "sleep infinity"
    ports:
      - '32000-32060:32000-32060'
```

When we run `nerdctl compose up -d` using this compose.yaml, we will get
the following error.

```
FATA[0000] create container failed validation: containers.Labels: label key and value length (4711 bytes) greater than maximum size (4096 bytes), key: nerdctl/ports: invalid argument
FATA[0000] error while creating container haytok-svc0-1: error while creating container haytok-svc0-1: exit status 1
```

This issue is reported in the following issue.

- https://github.com/containerd/nerdctl/issues/4027

This issue is considered to be the same as the one with errors when
trying to perform many port mappings, such as `nerdctl run -p 80:80 -p 81:81 ~ -p 1000:1000 ...`

The current implementation is processing to create a container with the
information specified in -p to the label.
And as can be seen from the error message, as the number of ports to be
port mapped increases, the creation of the container fails because it
violates the limit of the maximum number of bytes on the containerd side
that can be allocated for a label.

Therefore, this PR modifies the container creation process so that
containers can be launched without having to assign the information
specified in the -p option to the labels.

Specifically, port mapping information is stored in the following path,
and when port mapping information is required, it is retrieved from this
file.

```
<DATAROOT>/<ADDRHASH>/containers/<NAMESPACE>/<CID>/network-config.json
```

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-25 09:33:58 +00:00
Akihiro Suda debc5f6241
Merge pull request #4368 from pendo324/use-envvar-for-buildkit
refactor: move BUILDKIT_HOST to buildkitutil
2025-06-25 14:23:44 +09:00
Akihiro Suda d43f1d537d
Merge pull request #4360 from apostasie/2025-06-4322
[CI] Add delay for windows network test
2025-06-25 14:22:58 +09:00
Akihiro Suda 781f98b75d
Merge pull request #4369 from apostasie/2025-06-hc
[CI]: fix healthcheck test delay
2025-06-25 13:19:55 +09:00
apostasie 96ae9256ba
Fix broken image parsing and hard-coded data in tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-24 20:10:56 -07:00
apostasie b3a87c3938
Add DoesNotMatch comparator
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-24 20:10:55 -07:00
Akihiro Suda a7f4976a9d
Merge pull request #4362 from apostasie/2025-06-remove-lock
Simplify healthcheck file handling + fix locking
2025-06-25 11:34:55 +09:00
Akihiro Suda 0a5cb1dd4f
Merge pull request #4352 from ChengyuZhu6/estargz
commit: Add support for converting writable layers to eStargz blobs
2025-06-25 10:33:09 +09:00
Akihiro Suda 1345f246a8
Merge pull request #4327 from apostasie/2025-05-more-tests-2
[CI] Update registry testing tooling
2025-06-25 10:24:59 +09:00
apostasie 792e7f9cae
Fix healthcheck test
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-24 10:25:02 -07:00
Justin Alvarez 5e5e96e743
refactor: move BUILDKIT_HOST to buildkitutil
Signed-off-by: Justin Alvarez <alvajus@amazon.com>
2025-06-24 16:44:38 +00:00
ChengyuZhu6 9fa9071745 commit: support estargz conversion with writable layer in container commit
support estargz conversion with writable layer in container commit

Fixes: #4351

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-06-24 14:13:59 +08:00
Akihiro Suda d0d1c2d804
Merge pull request #4357 from apostasie/2025-06-fix-4106
[CI] Kube test: wait for image
2025-06-24 12:47:52 +09:00
Akihiro Suda fd4477a84a
Merge pull request #4356 from apostasie/2025-06-build-examples
[CI] Build examples
2025-06-24 12:04:42 +09:00
ChengyuZhu6 fe8d6afd79 image: extract image format options into separate structs
Reorganize `ImageConvertOptions` by extracting format-specific options
into dedicated structs (EstargzOptions, ZstdOptions, ZstdChunkedOptions,
NydusOptions, OverlaybdOptions) and embedding them for better code
organization and maintainability.

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-06-24 09:56:37 +08:00
apostasie 1791615612
Kube test: wait for image
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 18:20:43 -07:00
apostasie 421872fee8
Simplify healthcheck file handling
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 18:00:34 -07:00
apostasie 228be771f1
Add delay for windows network test
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 18:00:05 -07:00
apostasie 554005ccc9
Build examples and enable on CI
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:58:09 -07:00
apostasie 08b026a1c0
Cleanup TestImageHistory
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:50:26 -07:00
apostasie 4fa13e2342
Fixes for TestImageConvertNydusVerify
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:45:46 -07:00
apostasie 599aa39d89
Update testutil HTTPGet refresh strategy
1. extend the refresh interval from 0.1 second to 1 second
2. normalize all calls to HTTPGet to repeat 5 times (instead of the mix of 10, 30, 5 etc)

We can in the future assess case by case if some tests need more time.
Note that the replacement does not reduce any existing test timeout (30 * 0.1 < 5)

Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:45:44 -07:00
apostasie 875ae4c9da
Move tests to new registry testing infrastructure
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:44:48 -07:00
apostasie 040bdead82
Fix broken tigron testca
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-23 17:44:47 -07:00
Akihiro Suda 62477fbe5b
Merge pull request #4341 from apostasie/2025-06-tigron-api-changes
[Tigron] API cleanup
2025-06-24 09:28:52 +09:00
Akihiro Suda 410b0f290b
Merge pull request #4330 from ChengyuZhu6/commit-format
commit: Add an option to nerdctl commit command for media type selection
2025-06-23 23:11:03 +09:00
fahed dorgaa 102476051b
Merge pull request #4349 from apostasie/2025-06-fix-healthcheck-tests
Ensure container started on healthcheck tests
2025-06-23 11:01:09 +02:00
ChengyuZhu6 df145ce245 commit: Add an option to nerdctl commit command for media type selection
Fixes: #4329

Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
2025-06-23 14:56:31 +08:00
Akihiro Suda 9c56cda790
Merge pull request #4346 from apostasie/2025-06-canary-timeout
Bump CI timeout
2025-06-23 15:45:53 +09:00
Akihiro Suda 90ec23f0f3
Merge pull request #4350 from apostasie/2025-06-canary-token
Pass along GITHUB_TOKEN to canary
2025-06-23 15:45:16 +09:00
apostasie d232567a66
Pass along GITHUB_TOKEN
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 22:30:52 -07:00
apostasie df3df359a1
Ensure container started on healthcheck tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 22:15:03 -07:00
Akihiro Suda c24ee86b25
Merge pull request #4342 from apostasie/2025-06-yaml-dep
Move gopkg.in/yaml.v3 to maintained fork
2025-06-23 11:43:08 +09:00
Akihiro Suda 43543d97e7
Merge pull request #4345 from apostasie/2025-06-fix-tigron-lint
Re-enable tigron lint on the CI & makefile copy-pasta fix
2025-06-23 11:41:39 +09:00
Akihiro Suda 35eea81fb1
Merge pull request #4340 from apostasie/2025-06-as-a-lib-examples
Add example of using nerdctl as a library
2025-06-23 11:40:51 +09:00
apostasie 299b2968da
Move tigron funcs from *testing.T to tig.T
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 18:56:15 -07:00
apostasie 2f8c0c0914
Bump CI timeout
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 17:45:53 -07:00
apostasie 787bba68fb
Fix copy-paste error in Makefile
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 17:16:20 -07:00
apostasie ee8c5c1d97
Re-enable tigron lint on the CI
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 17:16:16 -07:00
apostasie 1784864c45
Remove deprecated info parameter in tigron funcs
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 15:40:26 -07:00
apostasie e08d320fef
xMove gopkg.in/yaml.v3 to maintained fork
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 14:31:39 -07:00
apostasie d53e06bf0c
Add example of using nerdctl as a library
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-22 13:50:34 -07:00
Akihiro Suda 4c0dd00232
Merge pull request #4194 from apostasie/2025-05-ci-proofing
Test images: cleanup and centralize
2025-06-23 05:47:34 +09:00
Akihiro Suda 3d44bd1197
Merge pull request #4240 from apostasie/2025-05-fs
internal/filesystem: finish moving os.WriteFile and os.ReadFile (and blacklist future use)
2025-06-23 05:46:16 +09:00
apostasie cfb5a16711
Add support for external embed test image list
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-21 15:41:04 -07:00
apostasie 5827adbe6c
Move leftover os.WriteFile and os.ReadFile to `filesystem`
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-21 15:32:30 -07:00
Akihiro Suda 87a6ab93ae
Merge pull request #4319 from tushar5526/use-stricter-regex-filter-match-network-labels
feat: use stricter regex to filter network labels on the container.
2025-06-21 22:51:30 +09:00
Akihiro Suda d828f4f351
Merge pull request #4335 from containerd/dependabot/go_modules/github.com/compose-spec/compose-go/v2-2.6.5
build(deps): bump github.com/compose-spec/compose-go/v2 from 2.6.4 to 2.6.5
2025-06-21 22:48:34 +09:00
Akihiro Suda 50adb44db9
Merge pull request #4336 from containerd/dependabot/go_modules/github.com/containerd/containerd/v2-2.1.3
build(deps): bump github.com/containerd/containerd/v2 from 2.1.2 to 2.1.3
2025-06-21 21:27:12 +09:00
Tushar Gupta b2f3b6cd0d feat: use stricter regex to filter network labels on the container.
This allows us to skip double checking the network names on the
container with an additional helper function.

Signed-off-by: Tushar Gupta <codingid6@gmail.com>
2025-06-21 16:28:07 +05:30
dependabot[bot] d516485683
build(deps): bump github.com/containerd/containerd/v2
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.1.2 to 2.1.3.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.1.2...v2.1.3)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-20 22:47:15 +00:00
dependabot[bot] 4c39ab5a7a
build(deps): bump github.com/compose-spec/compose-go/v2
Bumps [github.com/compose-spec/compose-go/v2](https://github.com/compose-spec/compose-go) from 2.6.4 to 2.6.5.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v2.6.4...v2.6.5)

---
updated-dependencies:
- dependency-name: github.com/compose-spec/compose-go/v2
  dependency-version: 2.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-20 22:47:03 +00:00
Akihiro Suda 190a71390d
Merge pull request #4324 from apostasie/2025-06-fs-3
internal/filesystem consolidation, part 3 (no rename WriteFile, disaster recovery and rollbacks)
2025-06-21 07:18:47 +09:00
apostasie 84894d9aae
WriteFile with rollback and no inode change
WriteFile now:
- no longer relies on Rename, hence no longer changing files inodes
- have disaster recovery and rollback

Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-20 12:58:08 -07:00
Akihiro Suda 36f9236855
Merge pull request #4326 from jedevc/name-filter-regexp
feat: allow regexp name filters to match docker
2025-06-21 04:30:52 +09:00
Akihiro Suda c46f1f4c17
Merge pull request #4302 from subashkotha/health_checks
feat: Add healthcheck command in nerdctl
2025-06-21 03:22:26 +09:00
Justin Chadwell 27c25728fb lint: bump cognitive-complexity
Signed-off-by: Justin Chadwell <me@jedevc.com>
2025-06-20 11:44:10 +01:00
Akihiro Suda b60fbeaad1
Merge pull request #4312 from containerd/dependabot/github_actions/actions/attest-build-provenance-2.4.0
build(deps): bump actions/attest-build-provenance from 2.3.0 to 2.4.0
2025-06-20 12:50:41 +09:00
Subash Kotha c5805c5bed feat: Add healthcheck command in nerdctl
Signed-off-by: Subash Kotha <subash.kotha2@gmail.com>
2025-06-19 13:43:12 -07:00
dependabot[bot] c459113113
build(deps): bump actions/attest-build-provenance from 2.3.0 to 2.4.0
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](db473fddc0...e8998f9491)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-19 18:15:41 +00:00
Justin Chadwell f073052058 feat: allow regexp name filters to match docker
Signed-off-by: Justin Chadwell <me@jedevc.com>
2025-06-19 16:16:35 +01:00
Akihiro Suda 4bde79d58a
Merge pull request #4314 from containerd/dependabot/go_modules/github.com/containerd/containerd/v2-2.1.2
build(deps): bump github.com/containerd/containerd/v2 from 2.1.1 to 2.1.2
2025-06-19 18:23:15 +09:00
Akihiro Suda 5be808b41c
Merge pull request #4325 from containerd/dependabot/github_actions/docker/setup-buildx-action-3.11.1
build(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1
2025-06-19 18:22:19 +09:00
Akihiro Suda 30857ef14c
Merge pull request #4308 from apostasie/2025-06-fs
internal/filesystem consolidation, part 2
2025-06-19 17:32:24 +09:00
dependabot[bot] 98aa61891b
build(deps): bump github.com/containerd/containerd/v2
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.1.1...v2.1.2)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-19 03:30:53 +00:00
Akihiro Suda 7ad308c342
Merge pull request #4320 from containerd/dependabot/go_modules/github.com/go-viper/mapstructure/v2-2.3.0
build(deps): bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0
2025-06-19 12:29:00 +09:00
dependabot[bot] e2db1e1e01
build(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.10.0 to 3.11.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](b5ca514318...e468171a9d)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 22:12:33 +00:00
apostasie f3dc0eee52
Forbidigo: prevent os fs operations
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-18 12:56:39 -07:00
apostasie 21e112424d
Move from os filesystem operations to internal/filesystem
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-18 12:56:29 -07:00
apostasie f38310bb9e
Fix typo in `across`.
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-06-18 10:03:17 -07:00
dependabot[bot] b074e5b04f
build(deps): bump github.com/go-viper/mapstructure/v2
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 06:20:50 +00:00
Akihiro Suda 81c6a301c2
Merge pull request #4318 from Shubhranshu153/update-go-license
fix: go-license dependency and fixes for provenance/sbom tests.
2025-06-18 15:19:16 +09:00
shubhranshu153 0eee948d0c fix: sbom and provenance tests
Signed-off-by: shubhranshu153 <shubhum@amazon.com>
2025-06-17 18:41:10 -07:00
shubhranshu153 9df1527422 fix:go-license dependency
Signed-off-by: shubhranshu153 <shubhum@amazon.com>
2025-06-17 18:40:47 -07:00
Akihiro Suda 401800a2f3
Merge pull request #4310 from haytok/refactor_network_inspect_test.go
test: refactor TestNetworkInspect in network_inspect_test.go
2025-06-11 11:48:00 +09:00
Hayato Kiwata fee517ad2f test: add one test case to TestNetworkInspect in network_inspect_test.go
Suppose that a `container A` is running in the network
`some-network-as-well`.

In this case, this commit adds a test to ensure that `container A` isn't
displayed in the `Containers` section of the output of
`nerdctl network inspect some-network`.

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-09 15:44:47 +00:00
Hayato Kiwata 3086d71238 test: refactor TestNetworkInspect in network_inspect_test.go
Based on the following three pieces of advice received in the merged PR,
this commit refactors the tests.

- https://github.com/containerd/nerdctl/pull/4309#discussion_r2134362878
- https://github.com/containerd/nerdctl/pull/4309#discussion_r2134362909
- https://github.com/containerd/nerdctl/pull/4309#discussion_r2134363162

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-09 15:23:29 +00:00
fahed dorgaa 221e3e0091
Merge pull request #4309 from haytok/fix_network_results_by_nerdctl_network_inspect
fix: display containers belonging to multiple networks in nerdctl network inspect
2025-06-07 20:39:29 +02:00
Hayato Kiwata a4957197c3 fix: display containers belonging to multiple networks in nerdctl network inspect
When a container belongs to multiple networks, running the nerdctl network
inspect command on the network to which the container belongs does not
display the container in the current implementation.

Specifically, it is displayed as follows.

```
$ sudo nerdctl run -d --name net --net=foo --net=bar nginx
d88e878f0c60823bd0c361bad250f27b19ad117fb3336fcf18fa26ab1910c367

$ sudo nerdctl network inspect foo | jq .[0].Containers
{}

$ sudo nerdctl network inspect bar | jq .[0].Containers
{}
```

Ideally, running the nerdctl network inspect command on the networks to
which the contaienr belongs should display the container name as follows.

```
$ sudo nerdctl network inspect foo | jq .[0].Containers
{
  "d88e878f0c60823bd0c361bad250f27b19ad117fb3336fcf18fa26ab1910c367": {
    "Name": "net"
  }
}

$ sudo nerdctl network inspect bar | jq .[0].Containers
{
  "d88e878f0c60823bd0c361bad250f27b19ad117fb3336fcf18fa26ab1910c367": {
    "Name": "net"
  }
}
```

Therefore, this behaviour is fixed in this PR.

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-06-07 17:02:15 +00:00
Akihiro Suda d937248ac5
Merge pull request #4097 from swagatbora90/fix-port-check-vsiravar
[Bugfix] Check if port is used in publish flag [duplicate of #2190]
2025-06-07 01:38:33 +09:00
Akihiro Suda e5db88c1e6
Merge pull request #4307 from containerd/dependabot/go_modules/golang-x-40d93c76e9
build(deps): bump the golang-x group with 4 updates
2025-06-06 15:13:08 +09:00
Vishwas Siravara 04f836d7f9 Check if port is used in publish flag
Signed-off-by: Swagat Bora <sbora@amazon.com>

Co-authored-by: Vishwas Siravara <vsiravara@gmail.com>
2025-06-06 00:02:16 +00:00
dependabot[bot] 3fa39407f1
build(deps): bump the golang-x group with 4 updates
Bumps the golang-x group with 4 updates: [golang.org/x/crypto](https://github.com/golang/crypto), [golang.org/x/net](https://github.com/golang/net), [golang.org/x/sync](https://github.com/golang/sync) and [golang.org/x/text](https://github.com/golang/text).


Updates `golang.org/x/crypto` from 0.38.0 to 0.39.0
- [Commits](https://github.com/golang/crypto/compare/v0.38.0...v0.39.0)

Updates `golang.org/x/net` from 0.40.0 to 0.41.0
- [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0)

Updates `golang.org/x/sync` from 0.14.0 to 0.15.0
- [Commits](https://github.com/golang/sync/compare/v0.14.0...v0.15.0)

Updates `golang.org/x/text` from 0.25.0 to 0.26.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/net
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/sync
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
- dependency-name: golang.org/x/text
  dependency-version: 0.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang-x
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 22:05:24 +00:00
Akihiro Suda 32077c5065
Merge pull request #4255 from fahedouch/clean-up-iptables-in-oci-post-hook
fix: clean up iptables rules for stopped containers
2025-06-05 22:26:39 +09:00
fahed dorgaa 7eab2ceb20
Merge pull request #4303 from swagatbora90/fix-gomodjail-iptables
mark go-iptables with gomodjail unconfined directive
2025-06-04 19:02:06 +02:00
Swagat Bora a27427fd29 mark go-iptables as gomodjail unconfined
Signed-off-by: Swagat Bora <sbora@amazon.com>
2025-06-03 17:47:03 +00:00
fahed dorgaa e9ac446742
fix: improve network settings application and enhance iptables rule deletion
Signed-off-by: fahed dorgaa <fahed.dorgaa@gmail.com>
2025-06-03 15:19:57 +02:00
Akihiro Suda b8c4b3d852
Merge pull request #4301 from containerd/dependabot/go_modules/docker-3094adeb80
build(deps): bump the docker group with 2 updates
2025-05-31 11:49:12 +09:00
dependabot[bot] 5bfcf9f243
build(deps): bump the docker group with 2 updates
Bumps the docker group with 2 updates: [github.com/docker/cli](https://github.com/docker/cli) and [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/cli` from 28.1.1+incompatible to 28.2.0+incompatible
- [Commits](https://github.com/docker/cli/compare/v28.1.1...v28.2.0)

Updates `github.com/docker/docker` from 28.1.1+incompatible to 28.2.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.1.1...v28.2.1)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 28.2.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker
- dependency-name: github.com/docker/docker
  dependency-version: 28.2.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-30 22:42:39 +00:00
Akihiro Suda d247c5049b
Merge pull request #4295 from containerd/dependabot/go_modules/github.com/containerd/nydus-snapshotter-0.15.2
build(deps): bump github.com/containerd/nydus-snapshotter from 0.15.1 to 0.15.2
2025-05-30 15:47:49 +09:00
fahed dorgaa 7fc539bd71
Merge pull request #4284 from zzzzzzzzzy9/calculateCPU
<fix> stats: CPU perusage use the wrong systemUsage
2025-05-29 23:28:19 +02:00
fahed dorgaa 9bd346eb1b
Merge pull request #4298 from containerd/dependabot/github_actions/docker/build-push-action-6.18.0
build(deps): bump docker/build-push-action from 6.17.0 to 6.18.0
2025-05-28 18:24:11 +02:00
fahed dorgaa 5118b48fe3
Merge pull request #4299 from containerd/dependabot/github_actions/lima-vm/lima-actions-1.0.1
build(deps): bump lima-vm/lima-actions from 1.0.0 to 1.0.1
2025-05-28 10:42:30 +02:00
dependabot[bot] c71cfb65c3
build(deps): bump lima-vm/lima-actions from 1.0.0 to 1.0.1
Bumps [lima-vm/lima-actions](https://github.com/lima-vm/lima-actions) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/lima-vm/lima-actions/releases)
- [Commits](be564a1408...03b96d6195)

---
updated-dependencies:
- dependency-name: lima-vm/lima-actions
  dependency-version: 1.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-27 22:13:33 +00:00
dependabot[bot] 1c9e0d33c8
build(deps): bump docker/build-push-action from 6.17.0 to 6.18.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.17.0 to 6.18.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](1dc7386353...263435318d)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-27 22:13:31 +00:00
zzzzzzzzzy9 5fcbccc4c1 <fix> stats: CPU perusage use the wrong systemUsage
Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
2025-05-27 11:23:34 +08:00
dependabot[bot] 1e28020823
build(deps): bump github.com/containerd/nydus-snapshotter
Bumps [github.com/containerd/nydus-snapshotter](https://github.com/containerd/nydus-snapshotter) from 0.15.1 to 0.15.2.
- [Release notes](https://github.com/containerd/nydus-snapshotter/releases)
- [Commits](https://github.com/containerd/nydus-snapshotter/compare/v0.15.1...v0.15.2)

---
updated-dependencies:
- dependency-name: github.com/containerd/nydus-snapshotter
  dependency-version: 0.15.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 22:22:34 +00:00
Akihiro Suda aee274f18f
Merge pull request #4273 from haytok/update_portutil_test_on_the_last
test: update test functions in portutil_test.go
2025-05-26 19:17:56 +09:00
Hayato Kiwata 02b16a30c6 test: update test functions in portutil_test.go
- update the logic of TestParseFlagP
- rename TestTestParseFlagPWithPlatformSpec to the appropriate function
  name

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-05-25 13:47:35 +00:00
Akihiro Suda 8cafefbb56
Merge pull request #4289 from apostasie/2025-05-empty-names
Prevent --name="" for container
2025-05-24 18:03:53 +09:00
Akihiro Suda 76b786b5ae
Merge pull request #4265 from apostasie/2025-05-github-raw-429
[CI]: move away from raw github domain to API
2025-05-24 18:01:48 +09:00
Akihiro Suda 3ac14a9029
Merge pull request #4286 from containerd/dependabot/go_modules/github.com/compose-spec/compose-go/v2-2.6.4
build(deps): bump github.com/compose-spec/compose-go/v2 from 2.6.3 to 2.6.4
2025-05-24 18:00:57 +09:00
apostasie dcbf14bc4e
Prevent empty container names
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-23 23:16:41 -07:00
dependabot[bot] 87a4206dcc
build(deps): bump github.com/compose-spec/compose-go/v2
Bumps [github.com/compose-spec/compose-go/v2](https://github.com/compose-spec/compose-go) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: github.com/compose-spec/compose-go/v2
  dependency-version: 2.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-23 23:00:45 +00:00
fahed dorgaa 694c405909
Merge pull request #4061 from lengrongfu/master
[Feat] Add commit compression type support
2025-05-22 10:40:37 +02:00
Akihiro Suda 4d415b4ad3
Merge pull request #4278 from yankay/cleanup-for-QF1012
[cleanup]: golang linter QF1012 to enforce fmt.Fprintf usage
2025-05-22 13:32:32 +09:00
rongfu.leng 41e1a56453 add commit compression type support
Signed-off-by: rongfu.leng <rongfu.leng@daocloud.io>
2025-05-22 11:30:24 +08:00
Kay Yan b0307c0898
cleanup for golang linter QF1012
Signed-off-by: Kay Yan <kay.yan@daocloud.io>
2025-05-21 11:59:41 +00:00
Akihiro Suda 1d88823a28
Merge pull request #4266 from apostasie/2025-05-more-tests
[CI] more tests cleanup
2025-05-21 18:15:49 +09:00
apostasie 01c83c7302
Use data.Temp() for assets
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-20 15:29:50 -07:00
apostasie 15914f1acb
Remove useless port bindings
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-20 15:29:49 -07:00
apostasie 40bdc2bcd1
Move from Alpine to Common for compose tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-20 15:29:48 -07:00
apostasie b5fa00b0f2
Remove version: from test compose yaml files
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-20 15:29:47 -07:00
apostasie 3b891956bc
Move away from raw github domain to API
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-20 15:29:33 -07:00
Akihiro Suda 2a2834dd1c
Merge pull request #4275 from AkihiroSuda/dev
update containerd (2.1.1), containerd-fuse-overlayfs (2.1.6), buildg (0.5.3)
2025-05-21 03:56:36 +09:00
Akihiro Suda c9f1cf6b14
Merge pull request #4274 from containerd/dependabot/go_modules/github.com/containerd/containerd/v2-2.1.1
build(deps): bump github.com/containerd/containerd/v2 from 2.1.0 to 2.1.1
2025-05-21 03:40:48 +09:00
Akihiro Suda 15cd2e0b97
update buildg (0.5.3)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-05-21 03:17:04 +09:00
Akihiro Suda 24eaa88bef
update containerd-fuse-overlayfs (2.1.6)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-05-21 03:12:14 +09:00
Akihiro Suda 4d68bfc5fe
update containerd (2.1.1)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-05-21 03:11:29 +09:00
dependabot[bot] f396316397
build(deps): bump github.com/containerd/containerd/v2
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 18:03:56 +00:00
Akihiro Suda 1226b898b2
Merge pull request #4271 from ningmingxiao/write_cgroup
[feature] enable --security-opt writable-cgroups=true|false as an option
2025-05-21 02:56:15 +09:00
Akihiro Suda 68c4acfd42
Merge pull request #4203 from apostasie/2025-05-compose-tests-1
[CI]: rewrite some compose tests
2025-05-21 02:55:49 +09:00
Akihiro Suda 6466b731fd
Merge pull request #4270 from containerd/dependabot/go_modules/github.com/fluent/fluent-logger-golang-1.10.0
build(deps): bump github.com/fluent/fluent-logger-golang from 1.9.0 to 1.10.0
2025-05-21 01:26:15 +09:00
Akihiro Suda 7c6f380496
Merge pull request #4269 from containerd/dependabot/go_modules/github.com/containerd/console-1.0.5
build(deps): bump github.com/containerd/console from 1.0.4 to 1.0.5
2025-05-21 01:22:27 +09:00
ningmingxiao 166ea55fd8 [feature] enable --security-opt writable-cgroups=true|false as an option
Signed-off-by: ningmingxiao <ning.mingxiao@zte.com.cn>
2025-05-20 21:54:53 +08:00
dependabot[bot] 11054f7019
build(deps): bump github.com/fluent/fluent-logger-golang
Bumps [github.com/fluent/fluent-logger-golang](https://github.com/fluent/fluent-logger-golang) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/fluent/fluent-logger-golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/fluent/fluent-logger-golang/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/fluent/fluent-logger-golang
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 09:41:09 +00:00
dependabot[bot] 901769d8ef
build(deps): bump github.com/containerd/console from 1.0.4 to 1.0.5
Bumps [github.com/containerd/console](https://github.com/containerd/console) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/containerd/console/releases)
- [Commits](https://github.com/containerd/console/compare/v1.0.4...v1.0.5)

---
updated-dependencies:
- dependency-name: github.com/containerd/console
  dependency-version: 1.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 09:41:06 +00:00
Akihiro Suda 82cdd4056a
Merge pull request #4209 from apostasie/2025-05-fix-logs-test
[CI]: rewrite logs tests
2025-05-20 17:44:22 +09:00
Akihiro Suda f207d83e9e
Merge pull request #4263 from haytok/refactor_portutil_test.go
test: refactor TestTestParseFlagPWithPlatformSpec and TestParsePortsL…
2025-05-20 15:01:27 +09:00
Akihiro Suda 721a193e05
Merge pull request #4256 from apostasie/2025-05-fs-2
[REFACTOR] Filesystem, part 2: revamp locks
2025-05-20 15:00:54 +09:00
fahed dorgaa 3c8411b339
Merge pull request #4267 from containerd/dependabot/go_modules/github.com/compose-spec/compose-go/v2-2.6.3
build(deps): bump github.com/compose-spec/compose-go/v2 from 2.6.2 to 2.6.3
2025-05-20 02:05:35 +02:00
dependabot[bot] 42367649a4
build(deps): bump github.com/compose-spec/compose-go/v2
Bumps [github.com/compose-spec/compose-go/v2](https://github.com/compose-spec/compose-go) from 2.6.2 to 2.6.3.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v2.6.2...v2.6.3)

---
updated-dependencies:
- dependency-name: github.com/compose-spec/compose-go/v2
  dependency-version: 2.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 22:25:23 +00:00
Hayato Kiwata 0365d39441 test: refactor TestTestParseFlagPWithPlatformSpec and TestParsePortsLabel
Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-05-18 09:27:17 +00:00
apostasie 8bd7a7dc99
Rewrite logs tests and fix flakyness
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-17 12:13:16 -07:00
apostasie a91b1bb35b
Reduce RunSigProxyContainer refresh frequency
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-17 12:13:15 -07:00
apostasie 2c26839d13
Rewrite some compose tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-17 12:10:50 -07:00
apostasie 721e285b0d
Revamp lock
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-17 11:44:48 -07:00
Akihiro Suda 848062ece5
Merge pull request #4261 from haytok/update_portutil_test_v2
test: update test logic in TestTestParseFlagPWithPlatformSpec
2025-05-18 01:30:27 +09:00
Hayato Kiwata 1c74a704e3 test: update test logic in TestTestParseFlagPWithPlatformSpec
This test does not run in the rootfull Linux environment.
Also, there are some cases where the test does not run.
Therefore, this commit modifies all tests to perform the evaluation.

Note that when the host ports are not specified in the -p option of
`nerdctl run`, ports are randomly assigned, but it is not known what port
number will be assigned in each test.
Therefore, this commit modifies not to compare host ports.

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-05-17 14:25:14 +00:00
Akihiro Suda 93c72b5bfb
Merge pull request #4258 from apostasie/2025-05-make-compose-people-happy
Add support for compose AdditionalContexts
2025-05-17 21:01:03 +09:00
Akihiro Suda 1cf746580d
Merge pull request #4257 from apostasie/2025-05-runtime-opt
Relax --runtime restrictions
2025-05-17 19:01:45 +09:00
Akihiro Suda e0367ae325
Merge pull request #4228 from AkihiroSuda/reviewer-Shubhranshu153
MAINTAINERS: add Shubharanshu Mahapatra (Shubhranshu153) as a REVIEWER
2025-05-17 18:39:37 +09:00
Akihiro Suda 66a553a1b6
Merge pull request #4259 from apostasie/2025-05-one-build-base
Use only one build base in Dockerfile
2025-05-17 18:33:46 +09:00
Akihiro Suda b7a46d6d20
Merge pull request #4260 from haytok/update_portutil_test
test: update test logic in TestParsePortsLabel
2025-05-17 18:31:21 +09:00
Hayato Kiwata cbd4ef2360 test: update test logic in TestParsePortsLabel
The current implementation does not compare []cni.PortMapping{} obtained
from labelMap and ParsePortsLabel() and want.
Therefore, this commit will update so that the evaluation is performed.

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
2025-05-17 07:27:47 +00:00
apostasie d166c3b0d8
Use only one build base in Dockerfile
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-16 22:03:22 -07:00
apostasie c0e32debae
Add support for compose AdditionalContexts
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-16 20:56:16 -07:00
apostasie cd1a24243e
Relax --runtime restrictions
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-16 19:40:03 -07:00
Akihiro Suda 772bb09ba5
Merge pull request #4247 from apostasie/2025-05-net-test-cleanup
[CI]: tests cleanup
2025-05-16 15:27:59 +09:00
Akihiro Suda 3914f637a8
Merge pull request #4245 from apostasie/2025-05-fs-b1
[REFACTOR]: consolidate filesystem ops into internal pkg
2025-05-16 10:24:56 +09:00
Akihiro Suda 3c333bb0da
Merge pull request #4248 from apostasie/2025-05-enable-tests
[CI]: re-enable tests following fixes
2025-05-16 08:42:51 +09:00
Akihiro Suda 9807c4d151
Merge pull request #4252 from containerd/dependabot/github_actions/docker/build-push-action-6.17.0
build(deps): bump docker/build-push-action from 6.16.0 to 6.17.0
2025-05-16 08:42:03 +09:00
Akihiro Suda 25f320f39a
Merge pull request #4250 from apostasie/2025-05-fix-non-unique-id
[CI]: fix use of non unique identifier
2025-05-16 08:41:42 +09:00
dependabot[bot] d28b664b37
build(deps): bump docker/build-push-action from 6.16.0 to 6.17.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](14487ce63c...1dc7386353)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-15 22:04:43 +00:00
apostasie d4cfdd94c5
Fix use on non unique identifier
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-15 13:52:55 -07:00
apostasie 3388ef6a22
Re-enable tests following fixes
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-15 12:02:33 -07:00
apostasie d9cf528510
Cleanup containers tests
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-15 11:15:37 -07:00
apostasie ff0e8ec807
Cleanup leftover on build test
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-15 10:15:49 -07:00
apostasie 368d2e27c2
Consolidate filesystem ops into pkg
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
2025-05-14 10:07:00 -07:00
Akihiro Suda 0c2e76bb7c
Merge pull request #4176 from subashkotha/add_attach_options
feat: add --no-stdin flag to container attach
2025-05-13 14:36:27 +09:00
Subash Kotha 8586da2ae3 feat: add --no-stdin flag to container attach
Signed-off-by: Subash Kotha <subash.kotha2@gmail.com>
2025-05-12 10:18:02 -07:00
Akihiro Suda 9cb28b4a31
MAINTAINERS: add Shubharanshu Mahapatra (Shubhranshu153) as a REVIEWER
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-05-10 00:26:00 +09:00
269 changed files with 8544 additions and 2710 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

22
BUILDING.md Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
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

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

View File

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

View File

@ -1,6 +0,0 @@
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

@ -0,0 +1,6 @@
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

@ -1,7 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,85 @@
/*
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,6 +37,7 @@ import (
"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil"
@ -156,7 +157,7 @@ func TestRunExitCode(t *testing.T) {
Output: expect.All(
expect.Match(regexp.MustCompile("Exited [(]123[)][A-Za-z0-9 ]+"+data.Identifier("exit123"))),
expect.Match(regexp.MustCompile("Exited [(]0[)][A-Za-z0-9 ]+"+data.Identifier("exit0"))),
func(stdout, info string, t *testing.T) {
func(stdout string, t tig.T) {
assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit0")).State.Status, "exited")
assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit123")).State.Status, "exited")
},
@ -837,3 +838,223 @@ 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,6 +24,7 @@ import (
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@ -222,12 +223,13 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
Output: func(stdout string, t tig.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Fatalf("Failed to get container host UID: %v", err)
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
},
}
},
@ -249,12 +251,13 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
Output: func(stdout string, t tig.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Fatalf("Failed to get container host UID: %v", err)
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
},
}
},
@ -295,12 +298,13 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
Output: func(stdout string, t tig.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Fatalf("Failed to get container host UID: %v", err)
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
},
}
},
@ -322,12 +326,13 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
Output: func(stdout string, t tig.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Fatalf("Failed to get container host UID: %v", err)
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
}
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"), info)
assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID"))
},
}
},
@ -367,12 +372,13 @@ func TestUsernsMappingRunCmd(t *testing.T) {
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, info string, t *testing.T) {
Output: func(stdout string, t tig.T) {
actualHostUID, err := getContainerHostUID(helpers, data.Identifier())
if err != nil {
t.Fatalf("Failed to get container host UID: %v", err)
t.Log(fmt.Sprintf("Failed to get container host UID: %v", err))
t.FailNow()
}
assert.Assert(t, actualHostUID == "0", info)
assert.Assert(t, actualHostUID == "0")
},
}
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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