diff --git a/.github/workflows/ghcr-image-build-and-publish.yml b/.github/workflows/ghcr-image-build-and-publish.yml index ebb35de53cb..1337a47c007 100644 --- a/.github/workflows/ghcr-image-build-and-publish.yml +++ b/.github/workflows/ghcr-image-build-and-publish.yml @@ -1,4 +1,4 @@ -name: Container Image Build +name: image # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by @@ -21,9 +21,8 @@ env: # github.repository as / IMAGE_NAME: ${{ github.repository }} - jobs: - build: + publish: runs-on: ubuntu-24.04 permissions: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..2b2cdadfc76 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,82 @@ +name: lint + +on: + push: + branches: + - main + - 'release/**' + pull_request: + +env: + GO_VERSION: 1.23.x + +jobs: + go: + timeout-minutes: 5 + name: "go | ${{ matrix.goos }} | ${{ matrix.gonext }}" + runs-on: "${{ matrix.os }}" + strategy: + matrix: + include: + - os: ubuntu-24.04 + goos: linux + - os: ubuntu-24.04 + goos: freebsd + # FIXME: this is currently failing in a non-sensical way, so, running on linux instead... + # - os: windows-2022 + - os: ubuntu-24.04 + goos: windows + - os: ubuntu-24.04 + goos: linux + # This allows the canary script to select any upcoming golang alpha/beta/RC + gonext: next + - os: ubuntu-24.04 + goos: freebsd + gonext: next + - os: ubuntu-24.04 + goos: windows + gonext: next + env: + GOOS: "${{ matrix.goos }}" + steps: + - uses: actions/checkout@v4.2.1 + with: + fetch-depth: 1 + - name: Set GO env + run: | + # If gonext is specified, get the latest available golang pre-release instead of the major version + if [ "$gonext" != "" ]; then + . ./hack/build-integration-canary.sh + canary::golang::latest + fi + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + cache: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + args: --verbose + + other: + timeout-minutes: 5 + name: yaml, shell, import order + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4.2.1 + with: + fetch-depth: 1 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + cache: true + - name: yaml + run: make lint-yaml + - name: shell + run: make lint-shell + - name: go imports ordering + run: | + go install -v github.com/incu6us/goimports-reviser/v3@latest + make lint-imports diff --git a/.github/workflows/test-canary.yml b/.github/workflows/test-canary.yml index cee60a360b9..12dc4abe045 100644 --- a/.github/workflows/test-canary.yml +++ b/.github/workflows/test-canary.yml @@ -15,27 +15,6 @@ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - lint: - runs-on: "ubuntu-24.04" - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4.2.1 - with: - fetch-depth: 1 - - name: Set GO env - run: | - . ./hack/build-integration-canary.sh - canary::golang::latest - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - cache: true - - name: golangci-lint - uses: golangci/golangci-lint-action@v6.1.1 - with: - args: --verbose - linux: runs-on: "ubuntu-24.04" timeout-minutes: 40 @@ -66,10 +45,12 @@ jobs: run: go test -v ./pkg/... - name: "Run integration tests" run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh + - name: "Run integration tests (flaky)" + run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky windows: - runs-on: windows-latest timeout-minutes: 30 + runs-on: windows-latest defaults: run: shell: bash @@ -95,6 +76,7 @@ jobs: cache: true check-latest: true - run: go install ./cmd/nerdctl + - run: go install -v gotest.tools/gotestsum@v1 # This here is solely to get the cni install script, which has not been modified in 3+ years. # There is little to no reason to update this to latest containerd - uses: actions/checkout@v4.2.1 @@ -112,5 +94,6 @@ jobs: ctrdVersion: ${{ env.CONTAINERD_VERSION }} run: powershell hack/configure-windows-ci.ps1 - name: "Run integration tests" - # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization - run: go test -p 1 -v ./cmd/nerdctl/... + run: ./hack/test-integration.sh + - name: "Run integration tests (flaky)" + run: ./hack/test-integration.sh -test.only-flaky diff --git a/.github/workflows/test-kube.yml b/.github/workflows/test-kube.yml index c8e2ccda405..3c6faaaa457 100644 --- a/.github/workflows/test-kube.yml +++ b/.github/workflows/test-kube.yml @@ -10,13 +10,12 @@ on: paths-ignore: - '**.md' -env: - ROOTFUL: true - jobs: linux: runs-on: "ubuntu-24.04" timeout-minutes: 40 + env: + ROOTFUL: true steps: - uses: actions/checkout@v4.2.1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f3b9331278..26a6e42b37e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,62 +15,10 @@ env: LONG_TIMEOUT: 60 jobs: - lint-go: + test-unit: # Supposed to work: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#example-returning-a-json-data-type # Apparently does not # timeout-minutes: ${{ fromJSON(env.SHORT_TIMEOUT) }} - timeout-minutes: 5 - name: lint-go ${{ matrix.goos }} - runs-on: "${{ matrix.os }}" - strategy: - matrix: - include: - - os: ubuntu-24.04 - goos: linux - - os: ubuntu-24.04 - goos: freebsd - # FIXME: this is currently failing in a non-sensical way, so, running on linux instead... - # - os: windows-2022 - - os: ubuntu-24.04 - goos: windows - env: - GOOS: "${{ matrix.goos }}" - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - cache: true - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - args: --verbose - - lint-other: - timeout-minutes: 5 - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - cache: true - - name: yaml - run: make lint-yaml - - name: shell - run: make lint-shell - - name: go imports ordering - run: | - go install -v github.com/incu6us/goimports-reviser/v3@latest - make lint-imports - - test-unit: timeout-minutes: 5 name: unit ${{ matrix.goos }} runs-on: "${{ matrix.os }}" @@ -83,7 +31,7 @@ jobs: - os: ubuntu-24.04 goos: linux steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - uses: actions/setup-go@v5 @@ -96,7 +44,7 @@ jobs: test-integration: timeout-minutes: 60 - name: integration ${{ matrix.containerd }} ${{ matrix.runner }} + name: rootful | ${{ matrix.containerd }} | ${{ matrix.runner }} runs-on: "${{ matrix.runner }}" strategy: fail-fast: false @@ -119,7 +67,7 @@ jobs: UBUNTU_VERSION: "${{ matrix.ubuntu }}" CONTAINERD_VERSION: "${{ matrix.containerd }}" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - name: "Prepare integration test environment" @@ -148,7 +96,7 @@ jobs: test-integration-ipv6: timeout-minutes: 60 - name: ipv6 ${{ matrix.containerd }} ${{ matrix.ubuntu }} + name: ipv6 | ${{ matrix.containerd }} | ${{ matrix.ubuntu }} runs-on: "ubuntu-${{ matrix.ubuntu }}" strategy: fail-fast: false @@ -157,11 +105,13 @@ jobs: include: - ubuntu: 24.04 containerd: v1.7.22 + - ubuntu: 24.04 + containerd: v2.0.0-rc.5 env: UBUNTU_VERSION: "${{ matrix.ubuntu }}" CONTAINERD_VERSION: "${{ matrix.containerd }}" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - name: Enable ipv4 and ipv6 forwarding @@ -200,7 +150,7 @@ jobs: test-integration-rootless: timeout-minutes: 60 - name: rootless ${{ matrix.containerd }} ${{ matrix.rootlesskit }} ${{ matrix.ubuntu }} ${{ matrix.target }} + name: "${{ matrix.target }} | ${{ matrix.containerd }} | ${{ matrix.rootlesskit }} | ${{ matrix.ubuntu }}" runs-on: "ubuntu-${{ matrix.ubuntu }}" strategy: fail-fast: false @@ -210,24 +160,24 @@ jobs: - ubuntu: 20.04 containerd: v1.6.36 rootlesskit: v1.1.1 # Deprecated - target: test-integration-rootless + target: rootless - ubuntu: 22.04 containerd: v1.7.22 rootlesskit: v2.3.1 - target: test-integration-rootless + target: rootless - ubuntu: 24.04 containerd: v2.0.0-rc.5 rootlesskit: v2.3.1 - target: test-integration-rootless + target: rootless - ubuntu: 24.04 containerd: v1.7.22 rootlesskit: v2.3.1 - target: test-integration-rootless-port-slirp4netns + target: rootless-port-slirp4netns env: UBUNTU_VERSION: "${{ matrix.ubuntu }}" CONTAINERD_VERSION: "${{ matrix.containerd }}" ROOTLESSKIT_VERSION: "${{ matrix.rootlesskit }}" - TEST_TARGET: "${{ matrix.target }}" + TEST_TARGET: "test-integration-${{ matrix.target }}" steps: - name: "Set up AppArmor" if: matrix.ubuntu == '24.04' @@ -244,7 +194,7 @@ jobs: } EOT sudo systemctl restart apparmor.service - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - name: "Register QEMU (tonistiigi/binfmt)" @@ -271,14 +221,14 @@ jobs: - name: "Test (network driver=slirp4netns, port driver=builtin) (flaky)" run: docker run -t --rm --privileged -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622} ${TEST_TARGET} /test-integration-rootless.sh ./hack/test-integration.sh -test.only-flaky - cross: + build: timeout-minutes: 5 runs-on: ubuntu-24.04 strategy: matrix: go-version: ["1.22.x", "1.23.x"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - uses: actions/setup-go@v5 @@ -286,14 +236,15 @@ jobs: go-version: ${{ matrix.go-version }} cache: true check-latest: true - - name: "Cross" + - name: "build" run: GO_VERSION="$(echo ${{ matrix.go-version }} | sed -e s/.x//)" make binaries test-integration-docker-compatibility: timeout-minutes: 60 + name: docker runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - uses: actions/setup-go@v5 @@ -324,16 +275,17 @@ jobs: - name: "Ensure that the IPv6 integration test suite is compatible with Docker" run: ./hack/test-integration.sh -test.target=docker -test.only-ipv6 - name: "Ensure that the integration test suite is compatible with Docker (flaky only)" - run: ./hack/test-integration.sh -test.target=docker -test.only-flaky + run: go test -p 1 -timeout 20m -v -exec sudo ./cmd/nerdctl/... -args -test.target=docker -test.allow-kill-daemon -test.only-flaky test-integration-windows: - timeout-minutes: 60 + timeout-minutes: 30 + name: windows runs-on: windows-2022 defaults: run: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: fetch-depth: 1 - uses: actions/setup-go@v5 @@ -343,7 +295,7 @@ jobs: check-latest: true - run: go install ./cmd/nerdctl - run: go install -v gotest.tools/gotestsum@v1 - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 with: repository: containerd/containerd ref: v1.7.22 @@ -357,11 +309,9 @@ jobs: ctrdVersion: 1.7.22 run: powershell hack/configure-windows-ci.ps1 - name: "Run integration tests" - run: | - ./hack/test-integration.sh + run: ./hack/test-integration.sh - name: "Run integration tests (flaky)" - run: | - ./hack/test-integration.sh -test.only-flaky + run: ./hack/test-integration.sh -test.only-flaky test-integration-freebsd: timeout-minutes: 60 @@ -370,7 +320,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.1 - uses: actions/cache@v4 with: path: /root/.vagrant.d diff --git a/cmd/nerdctl/container/container_commit_linux_test.go b/cmd/nerdctl/container/container_commit_linux_test.go index 8a4af41fdcd..05802a1f7c9 100644 --- a/cmd/nerdctl/container/container_commit_linux_test.go +++ b/cmd/nerdctl/container/container_commit_linux_test.go @@ -21,76 +21,69 @@ import ( "testing" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) -/* -This test below is meant to assert that https://github.com/containerd/nerdctl/issues/827 is NOT fixed. -Obviously, once we fix the issue, it should be replaced by something that assert it works. -Unfortunately, this is flaky. -It will regularly succeed or fail, making random PR fail the Kube check. -*/ - -func TestKubeCommitPush(t *testing.T) { - t.Parallel() - - base := testutil.NewBaseForKubernetes(t) - tID := testutil.Identifier(t) - - var containerID string - // var registryIP string - - setup := func() { - testutil.KubectlHelper(base, "run", "--image", testutil.CommonImage, tID, "--", "sleep", "Inf"). - AssertOK() - - testutil.KubectlHelper(base, "wait", "pod", tID, "--for=condition=ready", "--timeout=1m"). - AssertOK() - - testutil.KubectlHelper(base, "exec", tID, "--", "mkdir", "-p", "/tmp/whatever"). - AssertOK() - - cmd := testutil.KubectlHelper(base, "get", "pods", tID, "-o", "jsonpath={ .status.containerStatuses[0].containerID }") - cmd.Run() - containerID = strings.TrimPrefix(cmd.Out(), "containerd://") - - // This below is missing configuration to allow for plain http communication - // This is left here for future work to successfully start a registry usable in the cluster - /* - // Start a registry - testutil.KubectlHelper(base, "run", "--port", "5000", "--image", testutil.RegistryImageStable, "testregistry"). - AssertOK() - - testutil.KubectlHelper(base, "wait", "pod", "testregistry", "--for=condition=ready", "--timeout=1m"). - AssertOK() - - cmd = testutil.KubectlHelper(base, "get", "pods", tID, "-o", "jsonpath={ .status.hostIPs[0].ip }") - cmd.Run() - registryIP = cmd.Out() - - cmd = testutil.KubectlHelper(base, "apply", "-f", "-", fmt.Sprintf(`apiVersion: v1 - kind: ConfigMap - metadata: - name: local-registry - namespace: nerdctl-test - data: - localRegistryHosting.v1: | - host: "%s:5000" - help: "https://kind.sigs.k8s.io/docs/user/local-registry/" - `, registryIP)) - */ +func TestKubeCommitSave(t *testing.T) { + testCase := nerdtest.Setup() + + testCase.Require = nerdtest.OnlyKubernetes + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + containerID := "" + nerdtest.KubeCtlCommand(helpers, "run", "--image", testutil.CommonImage, data.Identifier(), "--", "sleep", "Inf").Run(&test.Expected{}) + nerdtest.KubeCtlCommand(helpers, "wait", "pod", data.Identifier(), "--for=condition=ready", "--timeout=1m").Run(&test.Expected{}) + nerdtest.KubeCtlCommand(helpers, "exec", data.Identifier(), "--", "mkdir", "-p", "/tmp/whatever").Run(&test.Expected{}) + nerdtest.KubeCtlCommand(helpers, "get", "pods", data.Identifier(), "-o", "jsonpath={ .status.containerStatuses[0].containerID }").Run(&test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + containerID = strings.TrimPrefix(stdout, "containerd://") + }, + }) + data.Set("containerID", containerID) + } + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + nerdtest.KubeCtlCommand(helpers, "delete", "pod", "--all").Run(nil) } - tearDown := func() { - testutil.KubectlHelper(base, "delete", "pod", "--all").Run() + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure("commit", data.Get("containerID"), "testcommitsave") + return helpers.Command("save", "testcommitsave") } - tearDown() - t.Cleanup(tearDown) - setup() + testCase.Expected = test.Expects(0, nil, nil) + + testCase.Run(t) + + t.Parallel() - t.Run("test commit / push on Kube (https://github.com/containerd/nerdctl/issues/827)", func(t *testing.T) { - base.Cmd("commit", containerID, "testcommitsave").AssertOK() - base.Cmd("save", "testcommitsave").AssertOK() - }) + // This below is missing configuration to allow for plain http communication + // This is left here for future work to successfully start a registry usable in the cluster + /* + // Start a registry + nerdtest.KubeCtlCommand(helpers, "run", "--port", "5000", "--image", testutil.RegistryImageStable, "testregistry"). + Run(&test.Expected{}) + + nerdtest.KubeCtlCommand(helpers, "wait", "pod", "testregistry", "--for=condition=ready", "--timeout=1m"). + AssertOK() + + 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) { + registryIP = stdout + }, + }) + + cmd = nerdtest.KubeCtlCommand(helpers, "apply", "-f", "-", fmt.Sprintf(`apiVersion: v1 + kind: ConfigMap + metadata: + name: local-registry + namespace: nerdctl-test + data: + localRegistryHosting.v1: | + host: "%s:5000" + help: "https://kind.sigs.k8s.io/docs/user/local-registry/" + `, registryIP)) + */ } diff --git a/cmd/nerdctl/container/container_diff_linux_test.go b/cmd/nerdctl/container/container_diff_linux_test.go deleted file mode 100644 index 7de8c302ed5..00000000000 --- a/cmd/nerdctl/container/container_diff_linux_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package container - -import ( - "testing" - - "github.com/containerd/nerdctl/v2/pkg/testutil" -) - -func TestDiff(t *testing.T) { - // It is unclear why this is failing with docker when run in parallel - // Obviously some other container test is interfering - if testutil.GetTarget() != testutil.Docker { - 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", "touch /a; touch /bin/b; rm /bin/base64").AssertOK() - // nerdctl contains more output "C /etc", "A /etc/resolv.conf" unlike docker - base.Cmd("diff", containerName).AssertOutContainsAll( - "A /a", - "C /bin", - "A /bin/b", - "D /bin/base64", - ) - base.Cmd("rm", "-f", containerName).AssertOK() -} diff --git a/cmd/nerdctl/container/container_diff_test.go b/cmd/nerdctl/container/container_diff_test.go new file mode 100644 index 00000000000..6914dee5303 --- /dev/null +++ b/cmd/nerdctl/container/container_diff_test.go @@ -0,0 +1,59 @@ +/* + 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 ( + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" + "testing" + + "github.com/containerd/nerdctl/v2/pkg/testutil" +) + +func TestDiff(t *testing.T) { + testCase := nerdtest.Setup() + + // It is unclear why this is failing with docker when run in parallel + // Obviously some other container test is interfering + if nerdtest.IsDocker() { + testCase.NoParallel = true + } + + testCase.Require = test.Not(test.Windows) + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, + "sh", "-euxc", "touch /a; touch /bin/b; rm /bin/base64") + } + + 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("diff", data.Identifier()) + } + + testCase.Expected = test.Expects(0, nil, test.All( + test.Contains("A /a"), + test.Contains("C /bin"), + test.Contains("A /bin/b"), + test.Contains("D /bin/base64"), + )) + + testCase.Run(t) +} diff --git a/cmd/nerdctl/container/container_run_restart_linux_test.go b/cmd/nerdctl/container/container_run_restart_linux_test.go index c3411c6aaeb..3d531f61c50 100644 --- a/cmd/nerdctl/container/container_run_restart_linux_test.go +++ b/cmd/nerdctl/container/container_run_restart_linux_test.go @@ -70,6 +70,7 @@ func TestRunRestart(t *testing.T) { } assert.NilError(t, check(30)) + base.EnsureDaemonActive() base.KillDaemon() base.EnsureDaemonActive() diff --git a/cmd/nerdctl/image/image_history_test.go b/cmd/nerdctl/image/image_history_test.go index 5086b3edb9f..f3f8ceec1b9 100644 --- a/cmd/nerdctl/image/image_history_test.go +++ b/cmd/nerdctl/image/image_history_test.go @@ -76,11 +76,16 @@ func TestImageHistory(t *testing.T) { test.Not(test.Windows), // XXX Currently, history does not work on non-native platform, so, we cannot test reliably on other platforms test.Arm64, - // XXX this here is very likely breaking other tests because of one of the variants of - // https://github.com/containerd/nerdctl/issues/3513 so, making it private to try avoid that + // Because of issues with multi-platform code, temp-reduced-image, this platform specific image here will + // introduce flaky behavior to other tests. + // There is likely complex issues at play, around https://github.com/containerd/nerdctl/issues/3513 nerdtest.Private, ), Setup: func(data test.Data, helpers test.Helpers) { + // XXX: despite efforts to isolate this test, it keeps on having side effects linked to + // https://github.com/containerd/nerdctl/issues/3512 + // Isolating it into a completely different root is the last ditched attempt at avoiding the issue + helpers.Write(nerdtest.DataRoot, test.ConfigValue(data.TempDir())) helpers.Ensure("pull", "--platform", "linux/arm64", testutil.CommonImage) }, SubTests: []*test.Case{ diff --git a/cmd/nerdctl/image/image_list_test.go b/cmd/nerdctl/image/image_list_test.go index b769c4a7810..71e754484bf 100644 --- a/cmd/nerdctl/image/image_list_test.go +++ b/cmd/nerdctl/image/image_list_test.go @@ -127,7 +127,6 @@ func TestImagesFilter(t *testing.T) { Description: "TestImagesFilter", Require: test.Require( nerdtest.Build, - nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3512"), ), Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("pull", testutil.CommonImage) @@ -192,6 +191,7 @@ RUN echo "actually creating a layer so that docker sets the createdAt time" }, { Description: "label=version", + Require: nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3512"), Command: test.Command("images", "--filter", "label=version"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ diff --git a/cmd/nerdctl/image/image_push_linux_test.go b/cmd/nerdctl/image/image_push_linux_test.go index 75c2e75bf60..9d3ae4d8598 100644 --- a/cmd/nerdctl/image/image_push_linux_test.go +++ b/cmd/nerdctl/image/image_push_linux_test.go @@ -51,8 +51,11 @@ func TestPush(t *testing.T) { Cleanup: func(data test.Data, helpers test.Helpers) { if registryNoAuthHTTPRandom != nil { registryNoAuthHTTPRandom.Cleanup(nil) - // XXX might crash + } + if registryNoAuthHTTPDefault != nil { registryNoAuthHTTPDefault.Cleanup(nil) + } + if registryTokenAuthHTTPSRandom != nil { registryTokenAuthHTTPSRandom.Cleanup(nil) } }, diff --git a/cmd/nerdctl/issues/issues_linux_test.go b/cmd/nerdctl/issues/issues_linux_test.go index 0a59126fc13..a371aaa6c0f 100644 --- a/cmd/nerdctl/issues/issues_linux_test.go +++ b/cmd/nerdctl/issues/issues_linux_test.go @@ -24,23 +24,24 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry" "github.com/containerd/nerdctl/v2/pkg/testutil/test" - "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) func TestIssue3425(t *testing.T) { nerdtest.Setup() - var registry *testregistry.RegistryServer + var reg *registry.Server testCase := &test.Case{ + Require: nerdtest.Registry, Setup: func(data test.Data, helpers test.Helpers) { - base := testutil.NewBase(t) - registry = testregistry.NewWithNoAuth(base, 0, false) + reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) + reg.Setup(data, helpers) }, Cleanup: func(data test.Data, helpers test.Helpers) { - if registry != nil { - registry.Cleanup(nil) + if reg != nil { + reg.Cleanup(data, helpers) } }, SubTests: []*test.Case{ @@ -52,14 +53,14 @@ func TestIssue3425(t *testing.T) { helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage) helpers.Ensure("image", "rm", "-f", testutil.CommonImage) helpers.Ensure("image", "pull", testutil.CommonImage) - helpers.Ensure("tag", testutil.CommonImage, fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + helpers.Ensure("tag", testutil.CommonImage, fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Cleanup: func(data test.Data, helpers test.Helpers) { helpers.Anyhow("rm", "-f", data.Identifier()) - helpers.Anyhow("rmi", "-f", fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + helpers.Anyhow("rmi", "-f", fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("push", fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + return helpers.Command("push", fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Expected: test.Expects(0, nil, nil), }, @@ -71,14 +72,14 @@ func TestIssue3425(t *testing.T) { helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "touch", "/something") helpers.Ensure("image", "rm", "-f", testutil.CommonImage) helpers.Ensure("image", "pull", testutil.CommonImage) - helpers.Ensure("commit", data.Identifier(), fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + helpers.Ensure("commit", data.Identifier(), fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Cleanup: func(data test.Data, helpers test.Helpers) { helpers.Anyhow("rm", "-f", data.Identifier()) - helpers.Anyhow("rmi", "-f", fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + helpers.Anyhow("rmi", "-f", fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("push", fmt.Sprintf("localhost:%d/%s", registry.Port, data.Identifier())) + return helpers.Command("push", fmt.Sprintf("localhost:%d/%s", reg.Port, data.Identifier())) }, Expected: test.Expects(0, nil, nil), }, diff --git a/cmd/nerdctl/volume/volume_inspect_test.go b/cmd/nerdctl/volume/volume_inspect_test.go index a1c729b5c52..a7ec478b55a 100644 --- a/cmd/nerdctl/volume/volume_inspect_test.go +++ b/cmd/nerdctl/volume/volume_inspect_test.go @@ -51,7 +51,7 @@ func TestVolumeInspect(t *testing.T) { &test.Requirement{ Check: func(data test.Data, helpers test.Helpers) (bool, string) { isDocker, _ := nerdtest.Docker.Check(data, helpers) - return !isDocker || test.IsRoot(), "docker cli needs to be run as root" + return !isDocker || os.Geteuid() == 0, "docker cli needs to be run as root" }, }) diff --git a/cmd/nerdctl/volume/volume_list_test.go b/cmd/nerdctl/volume/volume_list_test.go index 77954ee5b19..d48b35809c3 100644 --- a/cmd/nerdctl/volume/volume_list_test.go +++ b/cmd/nerdctl/volume/volume_list_test.go @@ -18,6 +18,7 @@ package volume import ( "fmt" + "os" "strings" "testing" @@ -94,7 +95,7 @@ func TestVolumeLsFilter(t *testing.T) { &test.Requirement{ Check: func(data test.Data, helpers test.Helpers) (bool, string) { isDocker, _ := nerdtest.Docker.Check(data, helpers) - return !isDocker || test.IsRoot(), "docker cli needs to be run as root" + return !isDocker || os.Geteuid() == 0, "docker cli needs to be run as root" }, }) diff --git a/hack/build-integration-kubernetes.sh b/hack/build-integration-kubernetes.sh index e41f13fcf7f..7fcea04c5cb 100755 --- a/hack/build-integration-kubernetes.sh +++ b/hack/build-integration-kubernetes.sh @@ -104,8 +104,10 @@ main(){ # Hack to get go into kind control plane exec::nerdctl rm -f go-kind 2>/dev/null || true - exec::nerdctl run -d --name go-kind golang:"$GO_VERSION" sleep Inf + exec::nerdctl pull --quiet golang:"$GO_VERSION" + exec::nerdctl run -d --pull never --name go-kind golang:"$GO_VERSION" sleep Inf exec::nerdctl cp go-kind:/usr/local/go /tmp/go + exec::nerdctl rm -f go-kind # Create fresh cluster log::info "Creating new cluster" diff --git a/hack/test-integration.sh b/hack/test-integration.sh index cc319f22f59..74f8b81f448 100755 --- a/hack/test-integration.sh +++ b/hack/test-integration.sh @@ -21,7 +21,7 @@ readonly root readonly timeout="60m" # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization -args=(--format=testname --jsonfile /tmp/test-integration.log --packages="$root"/cmd/nerdctl/...) +args=(--format=testname --jsonfile /tmp/test-integration.log --packages="$root"/../cmd/nerdctl/...) for arg in "$@"; do if [ "$arg" == "-test.only-flaky" ]; then @@ -32,5 +32,5 @@ done gotestsum "${args[@]}" -- -timeout="$timeout" -p 1 -args -test.allow-kill-daemon "$@" -echo "These are the tests that took more than 10 seconds:" -gotestsum tool slowest --threshold 10s --jsonfile /tmp/test-integration.log +echo "These are the tests that took more than 20 seconds:" +gotestsum tool slowest --threshold 20s --jsonfile /tmp/test-integration.log diff --git a/pkg/testutil/nerdtest/requirements.go b/pkg/testutil/nerdtest/requirements.go index 32878ce1cda..24297ccc9f5 100644 --- a/pkg/testutil/nerdtest/requirements.go +++ b/pkg/testutil/nerdtest/requirements.go @@ -68,6 +68,11 @@ var OnlyKubernetes = &test.Requirement{ if !ret { mess = "runner skips Kubernetes compatible tests in the non-Kubernetes environment" } + _, err := exec.LookPath("kubectl") + if err != nil { + ret = false + mess = fmt.Sprintf("kubectl is not in the path: %+v", err) + } return ret, mess }, } diff --git a/pkg/testutil/nerdtest/third-party.go b/pkg/testutil/nerdtest/third-party.go index 21199f3a815..eecacbc25d6 100644 --- a/pkg/testutil/nerdtest/third-party.go +++ b/pkg/testutil/nerdtest/third-party.go @@ -35,6 +35,14 @@ func BuildCtlCommand(helpers test.Helpers, args ...string) test.TestableCommand return cmd } +func KubeCtlCommand(helpers test.Helpers, args ...string) test.TestableCommand { + kubectl, _ := exec.LookPath("kubectl") + cmd := helpers.Custom(kubectl) + cmd.WithArgs("--namespace=" + defaultNamespace) + cmd.WithArgs(args...) + return cmd +} + func RegistryWithTokenAuth(data test.Data, helpers test.Helpers, user, pass string, port int, tls bool) (*registry.Server, *registry.TokenAuthServer) { rca := ca.New(data, helpers.T()) as := registry.NewCesantaAuthServer(data, helpers, rca, 0, user, pass, tls) diff --git a/pkg/testutil/test/utilities.go b/pkg/testutil/test/utilities.go index b12715d7b82..26da36bdbaa 100644 --- a/pkg/testutil/test/utilities.go +++ b/pkg/testutil/test/utilities.go @@ -20,14 +20,8 @@ import ( "crypto/rand" "encoding/base64" "fmt" - "os" ) -// IsRoot returns true if we are root... simple -func IsRoot() bool { - return os.Geteuid() == 0 -} - // RandomStringBase64 generates a base64 encoded random string func RandomStringBase64(n int) string { b := make([]byte, n) diff --git a/pkg/testutil/testregistry/testregistry_linux.go b/pkg/testutil/testregistry/testregistry_linux.go index 72701d5998d..d6610f9046a 100644 --- a/pkg/testutil/testregistry/testregistry_linux.go +++ b/pkg/testutil/testregistry/testregistry_linux.go @@ -17,8 +17,6 @@ package testregistry import ( - "crypto/rand" - "encoding/base64" "fmt" "net" "os" @@ -249,75 +247,6 @@ func (ba *BasicAuth) Params(base *testutil.Base) []string { return ret } -func NewIPFSRegistry(base *testutil.Base, ca *testca.CA, port int, auth Auth, boundCleanup func(error)) *RegistryServer { - EnsureImages(base) - - name := testutil.Identifier(base.T) - // listen on 0.0.0.0 to enable 127.0.0.1 - listenIP := net.ParseIP("0.0.0.0") - hostIP, err := nettestutil.NonLoopbackIPv4() - assert.NilError(base.T, err, fmt.Errorf("failed finding ipv4 non loopback interface: %w", err)) - port, err = portlock.Acquire(port) - assert.NilError(base.T, err, fmt.Errorf("failed acquiring port: %w", err)) - - containerName := fmt.Sprintf("ipfs-registry-%s-%d", name, port) - // Cleanup possible leftovers first - base.Cmd("rm", "-f", containerName).Run() - - args := []string{ - "run", - "--pull=never", - "-d", - "-p", fmt.Sprintf("%s:%d:%d", listenIP, port, port), - "--name", containerName, - "--entrypoint=/bin/sh", - testutil.KuboImage, - "-c", "--", - fmt.Sprintf("ipfs init && ipfs config Addresses.API /ip4/0.0.0.0/tcp/%d && ipfs daemon --offline", port), - } - - cleanup := func(err error) { - result := base.Cmd("rm", "-f", containerName).Run() - errPortRelease := portlock.Release(port) - if boundCleanup != nil { - boundCleanup(err) - } - if err == nil { - assert.NilError(base.T, result.Error, fmt.Errorf("failed removing container: %w", err)) - assert.NilError(base.T, errPortRelease, fmt.Errorf("failed releasing port: %w", err)) - } - } - - scheme := "http" - - err = func() error { - cmd := base.Cmd(args...).Run() - if cmd.Error != nil { - base.T.Logf("%s:\n%s\n%s\n-------\n%s", containerName, cmd.Cmd, cmd.Stdout(), cmd.Stderr()) - return cmd.Error - } - - if _, err = nettestutil.HTTPGet(fmt.Sprintf("%s://%s:%s/api/v0", scheme, hostIP.String(), strconv.Itoa(port)), 30, true); err != nil { - return err - } - - return nil - }() - - assert.NilError(base.T, err, fmt.Errorf("failed starting IPFS registry container in a timely manner: %w", err)) - - return &RegistryServer{ - IP: hostIP, - Port: port, - Scheme: scheme, - ListenIP: listenIP, - Cleanup: cleanup, - Logs: func() { - base.T.Logf("%s: %q", containerName, base.Cmd("logs", containerName).Run().String()) - }, - } -} - func NewRegistry(base *testutil.Base, ca *testca.CA, port int, auth Auth, boundCleanup func(error)) *RegistryServer { EnsureImages(base) @@ -469,29 +398,3 @@ func NewWithNoAuth(base *testutil.Base, port int, tls bool) *RegistryServer { } return NewRegistry(base, ca, port, &NoAuth{}, nil) } - -func NewWithBasicAuth(base *testutil.Base, user, pass string, port int, tls bool) *RegistryServer { - auth := &BasicAuth{ - Username: user, - Password: pass, - } - var ca *testca.CA - if tls { - ca = testca.New(base.T) - } - return NewRegistry(base, ca, port, auth, nil) -} - -func SafeRandomString(n int) string { - b := make([]byte, n) - l, err := rand.Read(b) - if err != nil { - panic(err) - } - if l != n { - panic(fmt.Errorf("expected %d bytes, got %d bytes", n, l)) - } - // XXX WARNING there is something in the registry (or more likely in the way we generate htpasswd files) - // that is broken and does not resist truly random strings - return base64.URLEncoding.EncodeToString(b) -} diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 46f4459934b..f34f233bb65 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -180,9 +180,7 @@ func (b *Base) EnsureDaemonActive() { sleep = 3 * time.Second ) for i := 0; i < maxRetry; i++ { - cmd := exec.Command("systemctl", - append(systemctlArgs, - []string{"is-active", target}...)...) + cmd := exec.Command("systemctl", append(systemctlArgs, "is-active", target)...) out, err := cmd.CombinedOutput() b.T.Logf("(retry=%d) %s", i, string(out)) if err == nil { @@ -204,10 +202,7 @@ func (b *Base) DumpDaemonLogs(minutes int) { b.T.Helper() target := b.systemctlTarget() cmd := exec.Command("journalctl", - append(b.systemctlArgs(), - []string{"-u", target, - "--no-pager", - "-S", fmt.Sprintf("%d min ago", minutes)}...)...) + append(b.systemctlArgs(), "-u", target, "--no-pager", "-S", fmt.Sprintf("%d min ago", minutes))...) b.T.Logf("===== %v =====", cmd.Args) out, err := cmd.CombinedOutput() if err != nil { @@ -767,14 +762,6 @@ func NewBaseWithIPv6Compatible(t *testing.T) *Base { return newBase(t, Namespace, true, false) } -func NewBaseForKubernetes(t *testing.T) *Base { - base := newBase(t, "k8s.io", false, true) - // NOTE: kubectl namespaces are not the same as containerd namespaces. - // We still want kube test objects segregated in their own Kube API namespace. - KubectlHelper(base, "create", "namespace", Namespace).Run() - return base -} - func NewBase(t *testing.T) *Base { return newBase(t, Namespace, false, false) } @@ -853,16 +840,6 @@ func RegisterBuildCacheCleanup(t *testing.T) { }) } -func KubectlHelper(base *Base, args ...string) *Cmd { - base.T.Helper() - icmdCmd := icmd.Command("kubectl", append([]string{"--namespace", Namespace}, args...)...) - icmdCmd.Env = base.Env - return &Cmd{ - Cmd: icmdCmd, - Base: base, - } -} - // SetupDockerContainerBuilder creates a Docker builder using the docker-container driver // and adds cleanup steps to test cleanup. The builder name is returned as output. //