diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 433922ed207..77cd4aed6a5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,5 @@ -name: lint +# "Hide" the name from the GitHub check status line, as it just clutters the display +name: " " on: push: @@ -7,72 +8,42 @@ on: - 'release/**' pull_request: -env: - GO_VERSION: 1.23.x - jobs: - go: - timeout-minutes: 5 - name: "go | ${{ matrix.goos }} | ${{ matrix.canary }}" - runs-on: "${{ matrix.os }}" - defaults: - run: - shell: bash + # Source the common environment + environment: + name: " " + uses: ./.github/workflows/reusable_environment.yml + + # Linting go + lint-go: + # Define the matrix we want to lint on: every supported OS, with the current go version, and additionally go canary on linux strategy: matrix: + # The GOOS-es we run golint for, with no canary (eg: the base supported GO_VERSION) + goos: [linux, freebsd, windows] + # And no canary + canary: [false] 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 - canary: go-canary - env: - GOOS: "${{ matrix.goos }}" - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: Set GO env - run: | - # If canary is specified, get the latest available golang pre-release instead of the major version - if [ "$canary" != "" ]; 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 | imports order - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4.2.2 - 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 + # Only run canary on linux (note: the canary script will select any upcoming golang alpha/beta/RC when the `canary` param is set to a non-empty string) + - goos: linux + canary: true + + # If we do not "collapse" the name using a bogux matrix var, it will display all matrix parameters, which we do not want + name: "lint${{ matrix.c }}" + uses: ./.github/workflows/reusable_lint_go.yml + needs: environment + with: + goos: ${{ matrix.goos }} + canary: ${{ matrix.canary }} + os: ${{ needs.environment.outputs.HOST_UBUNTU_LTS }} + goversion: ${{ needs.environment.outputs.GO_VERSION }} + timeout-minutes: ${{ fromJSON(needs.environment.outputs.SHORT_TIMEOUT) }} + + # Linting other filetypes + lint-other: + name: "lint" + uses: ./.github/workflows/reusable_lint_other.yml + needs: environment + with: + os: ${{ needs.environment.outputs.HOST_UBUNTU_LTS }} + timeout-minutes: ${{ fromJSON(needs.environment.outputs.SHORT_TIMEOUT) }} diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml index ebeef72caec..ec5f391289f 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -8,24 +8,40 @@ on: pull_request: jobs: + # Source the common environment + environment: + name: " " + uses: ./.github/workflows/reusable_environment.yml + project: - name: checks - runs-on: ubuntu-24.04 - timeout-minutes: 20 + name: "checks" + timeout-minutes: ${{ fromJSON(needs.environment.outputs.SHORT_TIMEOUT) }} + runs-on: ${{ needs.environment.outputs.HOST_UBUNTU_LTS }} + needs: environment + steps: - - uses: actions/checkout@v4.2.2 + - name: "Checkout" + uses: actions/checkout@v4 with: path: src/github.com/containerd/nerdctl + # Fetch the last 100 commits fetch-depth: 100 - - uses: actions/setup-go@v5 + + - name: "Install go" + uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ needs.environment.outputs.GO_VERSION }} cache-dependency-path: src/github.com/containerd/nerdctl - - uses: containerd/project-checks@v1.1.0 + + - name: "Install and run default containerd project checks" + uses: containerd/project-checks@v1.1.0 with: working-directory: src/github.com/containerd/nerdctl - repo-access-token: ${{ secrets.GITHUB_TOKEN }} - - run: ./hack/verify-no-patent.sh + + - name: "Verify no patent" + run: ./hack/verify-no-patent.sh working-directory: src/github.com/containerd/nerdctl - - run: ./hack/verify-pkg-isolation.sh + + - name: "Verify package isolation" + run: ./hack/verify-pkg-isolation.sh working-directory: src/github.com/containerd/nerdctl diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4043288037c..d309393a839 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,43 +1,61 @@ # See https://github.com/containerd/nerdctl/blob/main/MAINTAINERS_GUIDE.md for how to make a release. name: Release + on: push: tags: - 'v*' - 'test-action-release-*' jobs: + # Source the common environment + environment: + name: " " + uses: ./.github/workflows/reusable_environment.yml + release: - runs-on: ubuntu-24.04 - timeout-minutes: 40 + name: "checks" + timeout-minutes: ${{ fromJSON(needs.environment.outputs.LONG_TIMEOUT) }} + runs-on: ${{ needs.environment.outputs.HOST_UBUNTU_LTS }} + needs: environment + steps: - - uses: actions/checkout@v4.2.2 - - uses: actions/setup-go@v5 - with: - go-version: 1.23.x - - name: "Compile binaries" - run: make artifacts - - name: "SHA256SUMS" - run: | - ( cd _output; sha256sum nerdctl-* ) | tee /tmp/SHA256SUMS - mv /tmp/SHA256SUMS _output/SHA256SUMS - - name: "The sha256sum of the SHA256SUMS file" - run: (cd _output; sha256sum SHA256SUMS) - - name: "Prepare the release note" - run: | - shasha=$(sha256sum _output/SHA256SUMS | awk '{print $1}') - cat <<-EOF | tee /tmp/release-note.txt - $(hack/generate-release-note.sh) - - - - - The binaries were built automatically on GitHub Actions. - The build log is available for 90 days: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - - The sha256sum of the SHA256SUMS file itself is \`${shasha}\` . - - - - - Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE]) - EOF - - name: "Create release" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - tag="${GITHUB_REF##*/}" - gh release create -F /tmp/release-note.txt --draft --title "${tag}" "${tag}" _output/* + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Install go" + uses: actions/setup-go@v5 + with: + go-version: ${{ needs.environment.outputs.GO_VERSION }} + check-latest: true + + - name: "Compile binaries" + run: make artifacts + + - name: "SHA256SUMS" + run: | + ( cd _output; sha256sum nerdctl-* ) | tee /tmp/SHA256SUMS + mv /tmp/SHA256SUMS _output/SHA256SUMS + + - name: "The sha256sum of the SHA256SUMS file" + run: (cd _output; sha256sum SHA256SUMS) + + - name: "Prepare the release note" + run: | + shasha=$(sha256sum _output/SHA256SUMS | awk '{print $1}') + cat <<-EOF | tee /tmp/release-note.txt + $(hack/generate-release-note.sh) + - - - + The binaries were built automatically on GitHub Actions. + The build log is available for 90 days: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + + The sha256sum of the SHA256SUMS file itself is \`${shasha}\` . + - - - + Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE]) + EOF + + - name: "Create release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + tag="${GITHUB_REF##*/}" + gh release create -F /tmp/release-note.txt --draft --title "${tag}" "${tag}" _output/* diff --git a/.github/workflows/reusable_environment.yml b/.github/workflows/reusable_environment.yml new file mode 100644 index 00000000000..1cf122b3755 --- /dev/null +++ b/.github/workflows/reusable_environment.yml @@ -0,0 +1,52 @@ +name: common_environment + +env: + GO_VERSION: 1.23.x + HOST_UBUNTU_LTS: ubuntu-24.04 + SHORT_TIMEOUT: 5 + LONG_TIMEOUT: 20 +# REGISTRY_SERVER: ghcr.io +# BUSYBOX_VERSION: 5ad83957fa74aafd061afbfb8da14ce3220659a9 +# REGISTRY_VERSION: v2.8.3 +# CURL_VERSION: 8.11.0_4 + +on: + workflow_call: + outputs: + GO_VERSION: + description: "The major golang version we are targeting" + value: ${{ jobs.environment.outputs.output_go }} + HOST_UBUNTU_LTS: + description: "The major LTS ubuntu host runner we run our tasks on" + value: ${{ jobs.environment.outputs.output_ubuntu_lts }} + SHORT_TIMEOUT: + description: "The timeout for tasks that are supposed to run fast (lint, etc)" + value: ${{ jobs.environment.outputs.output_short_timeout }} + LONG_TIMEOUT: + description: "The timeout for tasks that are going to run up to 20 minutes (building, integration, etc)" + value: ${{ jobs.environment.outputs.output_long_timeout }} +# REGISTRY_SERVER: +# description: "The second output string" +# value: ${{ jobs.environment.outputs.output_registry }} + +jobs: + environment: + name: "environ" + runs-on: ubuntu-24.04 + steps: + - id: go + run: echo "GO_VERSION=$GO_VERSION" >> $GITHUB_OUTPUT + - id: ubuntu_lts + run: echo "HOST_UBUNTU_LTS=$HOST_UBUNTU_LTS" >> $GITHUB_OUTPUT + - id: short_timeout + run: echo "SHORT_TIMEOUT=$SHORT_TIMEOUT" >> $GITHUB_OUTPUT + - id: long_timeout + run: echo "LONG_TIMEOUT=$LONG_TIMEOUT" >> $GITHUB_OUTPUT +# - id: registry_server +# run: echo "REGISTRY_SERVER=$REGISTRY_SERVER" >> $GITHUB_OUTPUT + outputs: + output_go: ${{ steps.go.outputs.GO_VERSION }} + output_ubuntu_lts: ${{ steps.ubuntu_lts.outputs.HOST_UBUNTU_LTS }} + output_short_timeout: ${{ steps.short_timeout.outputs.SHORT_TIMEOUT }} + output_long_timeout: ${{ steps.long_timeout.outputs.LONG_TIMEOUT }} +# output_registry: ${{ steps.registry.outputs.REGISTRY_SERVER }} diff --git a/.github/workflows/reusable_lint_go.yml b/.github/workflows/reusable_lint_go.yml new file mode 100644 index 00000000000..14d658d7d50 --- /dev/null +++ b/.github/workflows/reusable_lint_go.yml @@ -0,0 +1,69 @@ +# This defines a reusable golint job that will run `make lint-go` and `make lint-imports` +# See `inputs` for expected parameters +name: tasks_lint_go + +on: + workflow_call: + inputs: + os: + required: true + type: string + description: "the host runner we are going to use" + goos: + required: true + type: string + description: "the GOOS we want to lint for (linux/windows/freebsd)" + goversion: + required: true + type: string + description: "the golang version we want to use" + canary: + required: false + type: boolean + default: false + description: "whether we want to try and find an alpha/beta/RC version of golang instead of the default version" + timeout-minutes: + required: false + type: number + default: 100 + description: "the timeout in minutes for this task" + +jobs: + go: + name: "${{ inputs.goos }} ${{ inputs.canary && 'canary' || inputs.goversion }}" + timeout-minutes: ${{ inputs.timeout-minutes }} + runs-on: ${{ inputs.os }} + + env: + GOOS: "${{ inputs.goos }}" + GO_VERSION: "${{ inputs.goversion }}" + + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Set go version" + run: | + # If canary is specified, get the latest available golang pre-release instead of the major version + if [ "${{ inputs.canary }}" == true ]; then + . ./hack/build-integration-canary.sh + canary::golang::latest + fi + + - name: "Install go" + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + + - name: "Run golangci-lint" + uses: golangci/golangci-lint-action@v6 + with: + args: --verbose + + # Go imports ordering applies to all platforms, so, only run it once, for linux / no canary + - name: "Verify imports ordering" + if: ${{ inputs.goos == 'linux' && ! inputs.canary }} + run: | + go install github.com/incu6us/goimports-reviser/v3@latest + make lint-imports diff --git a/.github/workflows/reusable_lint_other.yml b/.github/workflows/reusable_lint_other.yml new file mode 100644 index 00000000000..d63493b3b74 --- /dev/null +++ b/.github/workflows/reusable_lint_other.yml @@ -0,0 +1,33 @@ +# This defines a reusable golint job that will run `make lint-go` and `make lint-imports` +# See `inputs` for expected parameters +name: tasks_lint_go + +on: + workflow_call: + inputs: + os: + required: true + type: string + description: "the host runner we are going to use" + timeout-minutes: + required: false + type: number + default: 100 + description: "the timeout in minutes for this task" + +jobs: + yaml_shell_etc: + # TODO: we might want to add some markdown linter, and maybe a dockerfile linter in the future + name: "yaml | shell" + timeout-minutes: ${{ inputs.timeout-minutes }} + runs-on: ${{ inputs.os }} + + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Lint yaml files" + run: make lint-yaml + + - name: "Lint shellscripts" + run: make lint-shell diff --git a/.github/workflows/test-canary.yml b/.github/workflows/test-canary.yml deleted file mode 100644 index 152097cd0fc..00000000000 --- a/.github/workflows/test-canary.yml +++ /dev/null @@ -1,99 +0,0 @@ -# This pipeline purpose is solely meant to run a subset of our test suites against upcoming or unreleased dependencies versions -name: canary - -on: - push: - branches: - - main - - 'release/**' - pull_request: - paths-ignore: - - '**.md' - -env: - UBUNTU_VERSION: "24.04" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - linux: - runs-on: "ubuntu-24.04" - timeout-minutes: 40 - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: "Prepare integration test environment" - run: | - . ./hack/build-integration-canary.sh - canary::build::integration - - name: "Remove snap loopback devices (conflicts with our loopback devices in TestRunDevice)" - run: | - sudo systemctl disable --now snapd.service snapd.socket - sudo apt-get purge -y snapd - sudo losetup -Dv - sudo losetup -lv - - name: "Register QEMU (tonistiigi/binfmt)" - run: | - # `--install all` will only install emulation for architectures that cannot be natively executed - # Since some arm64 platforms do provide native fallback execution for 32 bits, - # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`. - # To avoid that, we explicitly list the architectures we do want emulation for. - docker run --privileged --rm tonistiigi/binfmt --install linux/amd64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - - name: "Run unit tests" - run: go test -v ./pkg/... - - name: "Run integration tests" - run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=false - - name: "Run integration tests (flaky)" - run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=true - - windows: - timeout-minutes: 30 - runs-on: windows-latest - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: Set GO env - run: | - # Get latest containerd - args=(curl --proto '=https' --tlsv1.2 -fsSL -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28") - [ "${GITHUB_TOKEN:-}" == "" ] && { - >&2 printf "GITHUB_TOKEN is not set - you might face rate limitations with the Github API\n" - } || args+=(-H "Authorization: Bearer $GITHUB_TOKEN") - ctd_v="$("${args[@]}" https://api.github.com/repos/containerd/containerd/tags | jq -rc .[0].name)" - echo "CONTAINERD_VERSION=${ctd_v:1}" >> "$GITHUB_ENV" - - . ./hack/build-integration-canary.sh - canary::golang::latest - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - 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.2 - with: - repository: containerd/containerd - ref: "v1.7.24" - path: containerd - fetch-depth: 1 - - name: "Set up CNI" - working-directory: containerd - run: GOPATH=$(go env GOPATH) script/setup/install-cni-windows - # Windows setup script can only use released versions - - name: "Set up containerd" - env: - ctrdVersion: ${{ env.CONTAINERD_VERSION }} - run: powershell hack/configure-windows-ci.ps1 - - name: "Run integration tests" - run: ./hack/test-integration.sh -test.only-flaky=false - - name: "Run integration tests (flaky)" - run: ./hack/test-integration.sh -test.only-flaky=true diff --git a/.github/workflows/test-kube.yml b/.github/workflows/test-kube.yml deleted file mode 100644 index 2bd0d00f28c..00000000000 --- a/.github/workflows/test-kube.yml +++ /dev/null @@ -1,27 +0,0 @@ -# This pipeline purpose is solely meant to run a subset of our test suites against a kubernetes cluster -name: kubernetes - -on: - push: - branches: - - main - - 'release/**' - pull_request: - paths-ignore: - - '**.md' - -jobs: - linux: - runs-on: "ubuntu-24.04" - timeout-minutes: 40 - env: - ROOTFUL: true - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: "Run Kubernetes integration tests" - # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization - run: | - ./hack/build-integration-kubernetes.sh - sudo ./_output/nerdctl exec nerdctl-test-control-plane bash -c -- 'export TMPDIR="$HOME"/tmp; mkdir -p "$TMPDIR"; cd /nerdctl-source; /usr/local/go/bin/go test -p 1 ./cmd/nerdctl/... -test.only-kubernetes' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index feba1ca4c26..00000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,421 +0,0 @@ -name: test - -on: - push: - branches: - - main - - 'release/**' - pull_request: - paths-ignore: - - '**.md' - -env: - GO_VERSION: 1.23.x - SHORT_TIMEOUT: 5 - LONG_TIMEOUT: 60 - -jobs: - # This job builds the dependency target of the test docker image for all supported architectures and cache it in GHA - build-dependencies: - timeout-minutes: 15 - name: dependencies | ${{ matrix.containerd }} | ${{ matrix.arch }} - runs-on: "${{ matrix.runner }}" - strategy: - fail-fast: false - matrix: - include: - - runner: ubuntu-24.04 - containerd: v1.6.36 - arch: amd64 - - runner: ubuntu-24.04 - containerd: v1.7.24 - arch: amd64 - - runner: ubuntu-24.04 - containerd: v2.0.0 - arch: amd64 - - runner: arm64-8core-32gb - containerd: v2.0.0 - arch: arm64 - env: - CONTAINERD_VERSION: "${{ matrix.containerd }}" - ARCH: "${{ matrix.arch }}" - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: "Expose GitHub Runtime variables for gha" - uses: crazy-max/ghaction-github-runtime@v3 - - name: "Build dependencies for the integration test environment image" - run: | - docker buildx create --name with-gha --use - docker buildx build \ - --output=type=docker \ - --cache-to type=gha,mode=max,scope=${ARCH}-${CONTAINERD_VERSION} \ - --cache-from type=gha,scope=${ARCH}-${CONTAINERD_VERSION} \ - --target build-dependencies --build-arg CONTAINERD_VERSION=${CONTAINERD_VERSION} . - - test-unit: - # FIXME: - # 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: 10 - name: unit | ${{ matrix.goos }} - runs-on: "${{ matrix.os }}" - defaults: - run: - shell: bash - strategy: - matrix: - include: - - os: windows-2022 - goos: windows - - os: ubuntu-24.04 - goos: linux - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - check-latest: true - cache: true - - if: ${{ matrix.goos=='windows' }} - uses: actions/checkout@v4.2.2 - with: - repository: containerd/containerd - ref: v1.7.24 - path: containerd - fetch-depth: 1 - - if: ${{ matrix.goos=='windows' }} - name: "Set up CNI" - working-directory: containerd - run: GOPATH=$(go env GOPATH) script/setup/install-cni-windows - - name: "Run unit tests" - run: make test-unit - - test-integration: - needs: build-dependencies - timeout-minutes: 30 - name: rootful | ${{ matrix.containerd }} | ${{ matrix.runner }} - runs-on: "${{ matrix.runner }}" - strategy: - fail-fast: false - matrix: - # ubuntu-20.04: cgroup v1, ubuntu-22.04 and later: cgroup v2 - include: - - ubuntu: 20.04 - containerd: v1.6.36 - runner: "ubuntu-20.04" - arch: amd64 - - ubuntu: 22.04 - containerd: v1.7.24 - runner: "ubuntu-22.04" - arch: amd64 - - ubuntu: 24.04 - containerd: v2.0.0 - runner: "ubuntu-24.04" - arch: amd64 - - ubuntu: 24.04 - containerd: v2.0.0 - runner: arm64-8core-32gb - arch: arm64 - env: - CONTAINERD_VERSION: "${{ matrix.containerd }}" - ARCH: "${{ matrix.arch }}" - UBUNTU_VERSION: "${{ matrix.ubuntu }}" - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: "Expose GitHub Runtime variables for gha" - uses: crazy-max/ghaction-github-runtime@v3 - - name: "Prepare integration test environment" - run: | - docker buildx create --name with-gha --use - docker buildx build \ - --output=type=docker \ - --cache-from type=gha,scope=${ARCH}-${CONTAINERD_VERSION} \ - -t test-integration --target test-integration --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} --build-arg CONTAINERD_VERSION=${CONTAINERD_VERSION} . - - name: "Remove snap loopback devices (conflicts with our loopback devices in TestRunDevice)" - run: | - sudo systemctl disable --now snapd.service snapd.socket - sudo apt-get purge -y snapd - sudo losetup -Dv - sudo losetup -lv - - name: "Register QEMU (tonistiigi/binfmt)" - run: | - # `--install all` will only install emulation for architectures that cannot be natively executed - # Since some arm64 platforms do provide native fallback execution for 32 bits, - # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`. - # To avoid that, we explicitly list the architectures we do want emulation for. - docker run --privileged --rm tonistiigi/binfmt --install linux/amd64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - - name: "Run integration tests" - run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=false - - name: "Run integration tests (flaky)" - run: docker run -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=true - - test-integration-ipv6: - needs: build-dependencies - timeout-minutes: 15 - name: ipv6 | ${{ matrix.containerd }} | ${{ matrix.ubuntu }} - runs-on: "ubuntu-${{ matrix.ubuntu }}" - strategy: - fail-fast: false - matrix: - include: - - ubuntu: 24.04 - containerd: v2.0.0 - arch: amd64 - env: - CONTAINERD_VERSION: "${{ matrix.containerd }}" - ARCH: "${{ matrix.arch }}" - UBUNTU_VERSION: "${{ matrix.ubuntu }}" - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: Enable ipv4 and ipv6 forwarding - run: | - sudo sysctl -w net.ipv6.conf.all.forwarding=1 - sudo sysctl -w net.ipv4.ip_forward=1 - - name: "Expose GitHub Runtime variables for gha" - uses: crazy-max/ghaction-github-runtime@v3 - - name: Enable IPv6 for Docker, and configure docker to use containerd for gha - run: | - sudo mkdir -p /etc/docker - echo '{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64", "experimental": true, "ip6tables": true}' | sudo tee /etc/docker/daemon.json - sudo systemctl restart docker - - name: "Prepare integration test environment" - run: | - docker buildx create --name with-gha --use - docker buildx build \ - --output=type=docker \ - --cache-from type=gha,scope=${ARCH}-${CONTAINERD_VERSION} \ - -t test-integration --target test-integration --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} --build-arg CONTAINERD_VERSION=${CONTAINERD_VERSION} . - - name: "Remove snap loopback devices (conflicts with our loopback devices in TestRunDevice)" - run: | - sudo systemctl disable --now snapd.service snapd.socket - sudo apt-get purge -y snapd - sudo losetup -Dv - sudo losetup -lv - - name: "Register QEMU (tonistiigi/binfmt)" - run: | - # `--install all` will only install emulation for architectures that cannot be natively executed - # Since some arm64 platforms do provide native fallback execution for 32 bits, - # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`. - # To avoid that, we explicitly list the architectures we do want emulation for. - docker run --privileged --rm tonistiigi/binfmt --install linux/amd64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - - name: "Run integration tests" - # The nested IPv6 network inside docker and qemu is complex and needs a bunch of sysctl config. - # Therefore, it's hard to debug why the IPv6 tests fail in such an isolation layer. - # On the other side, using the host network is easier at configuration. - # Besides, each job is running on a different instance, which means using host network here - # is safe and has no side effects on others. - run: docker run --network host -t --rm --privileged test-integration ./hack/test-integration.sh -test.only-ipv6 - - test-integration-rootless: - needs: build-dependencies - timeout-minutes: 30 - name: "${{ matrix.target }} | ${{ matrix.containerd }} | ${{ matrix.rootlesskit }} | ${{ matrix.ubuntu }}" - runs-on: "ubuntu-${{ matrix.ubuntu }}" - strategy: - fail-fast: false - matrix: - # ubuntu-20.04: cgroup v1, ubuntu-22.04 and later: cgroup v2 - include: - - ubuntu: 20.04 - containerd: v1.6.36 - rootlesskit: v1.1.1 # Deprecated - target: rootless - arch: amd64 - - ubuntu: 22.04 - containerd: v1.7.24 - rootlesskit: v2.3.1 - target: rootless - arch: amd64 - - ubuntu: 24.04 - containerd: v2.0.0 - rootlesskit: v2.3.1 - target: rootless - arch: amd64 - - ubuntu: 24.04 - containerd: v1.7.24 - rootlesskit: v2.3.1 - target: rootless-port-slirp4netns - arch: amd64 - env: - CONTAINERD_VERSION: "${{ matrix.containerd }}" - ARCH: "${{ matrix.arch }}" - UBUNTU_VERSION: "${{ matrix.ubuntu }}" - ROOTLESSKIT_VERSION: "${{ matrix.rootlesskit }}" - TEST_TARGET: "test-integration-${{ matrix.target }}" - steps: - - name: "Set up AppArmor" - if: matrix.ubuntu == '24.04' - run: | - cat <, - include - - /usr/local/bin/rootlesskit flags=(unconfined) { - userns, - - # Site-specific additions and overrides. See local/README for details. - include if exists - } - EOT - sudo systemctl restart apparmor.service - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - name: "Register QEMU (tonistiigi/binfmt)" - run: | - # `--install all` will only install emulation for architectures that cannot be natively executed - # Since some arm64 platforms do provide native fallback execution for 32 bits, - # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`. - # To avoid that, we explicitly list the architectures we do want emulation for. - docker run --privileged --rm tonistiigi/binfmt --install linux/amd64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - - name: "Expose GitHub Runtime variables for gha" - uses: crazy-max/ghaction-github-runtime@v3 - - name: "Prepare (network driver=slirp4netns, port driver=builtin)" - run: | - docker buildx create --name with-gha --use - docker buildx build \ - --output=type=docker \ - --cache-from type=gha,scope=${ARCH}-${CONTAINERD_VERSION} \ - -t ${TEST_TARGET} --target ${TEST_TARGET} --build-arg UBUNTU_VERSION=${UBUNTU_VERSION} --build-arg CONTAINERD_VERSION=${CONTAINERD_VERSION} --build-arg ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION} . - - name: "Disable BuildKit for RootlessKit v1 (workaround for issue #622)" - run: | - # https://github.com/containerd/nerdctl/issues/622 - WORKAROUND_ISSUE_622= - if echo "${ROOTLESSKIT_VERSION}" | grep -q v1; then - WORKAROUND_ISSUE_622=1 - fi - echo "WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622}" >> "$GITHUB_ENV" - - name: "Test (network driver=slirp4netns, port driver=builtin)" - 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=false - - 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=true - - build: - timeout-minutes: 5 - name: "build | ${{ matrix.go-version }}" - runs-on: ubuntu-24.04 - strategy: - matrix: - go-version: ["1.22.x", "1.23.x"] - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - cache: true - check-latest: true - - name: "build" - run: GO_VERSION="$(echo ${{ matrix.go-version }} | sed -e s/.x//)" make binaries - - test-integration-docker-compatibility: - timeout-minutes: 30 - name: docker - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - check-latest: true - - name: "Register QEMU (tonistiigi/binfmt)" - run: | - # `--install all` will only install emulation for architectures that cannot be natively executed - # Since some arm64 platforms do provide native fallback execution for 32 bits, - # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`. - # To avoid that, we explicitly list the architectures we do want emulation for. - docker run --privileged --rm tonistiigi/binfmt --install linux/amd64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 - docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - - name: "Prepare integration test environment" - run: | - sudo apt-get install -y expect - go install -v gotest.tools/gotestsum@v1 - - name: "Ensure that the integration test suite is compatible with Docker" - run: WITH_SUDO=true ./hack/test-integration.sh -test.target=docker - - name: "Ensure that the IPv6 integration test suite is compatible with Docker" - run: WITH_SUDO=true ./hack/test-integration.sh -test.target=docker -test.only-ipv6 - - name: "Ensure that the integration test suite is compatible with Docker (flaky only)" - run: WITH_SUDO=true ./hack/test-integration.sh -test.target=docker -test.only-flaky - - test-integration-windows: - timeout-minutes: 30 - name: windows - runs-on: windows-2022 - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - check-latest: true - - run: go install ./cmd/nerdctl - - run: go install -v gotest.tools/gotestsum@v1 - - uses: actions/checkout@v4.2.2 - with: - repository: containerd/containerd - ref: v1.7.24 - path: containerd - fetch-depth: 1 - - name: "Set up CNI" - working-directory: containerd - run: GOPATH=$(go env GOPATH) script/setup/install-cni-windows - - name: "Set up containerd" - env: - ctrdVersion: 1.7.24 - run: powershell hack/configure-windows-ci.ps1 - - name: "Run integration tests" - run: ./hack/test-integration.sh -test.only-flaky=false - - name: "Run integration tests (flaky)" - run: ./hack/test-integration.sh -test.only-flaky=true - - test-integration-freebsd: - timeout-minutes: 30 - name: FreeBSD - # ubuntu-24.04 lacks the vagrant package - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4.2.2 - with: - fetch-depth: 1 - - uses: actions/cache@v4 - with: - path: /root/.vagrant.d - key: vagrant-${{ matrix.box }} - - name: Set up vagrant - run: | - sudo apt-get update - sudo apt-get install -y libvirt-daemon libvirt-daemon-system vagrant vagrant-libvirt - sudo systemctl enable --now libvirtd - - name: Boot VM - run: | - ln -sf Vagrantfile.freebsd Vagrantfile - sudo vagrant up --no-tty - - name: test-unit - run: sudo vagrant up --provision-with=test-unit - - name: test-integration - run: sudo vagrant up --provision-with=test-integration diff --git a/Makefile b/Makefile index ae4e18c94f3..097699747ae 100644 --- a/Makefile +++ b/Makefile @@ -67,13 +67,17 @@ clean: find . -name \#\* -delete rm -rf $(CURDIR)/_output/* $(MAKEFILE_DIR)/vendor -lint: lint-go lint-imports lint-yaml lint-shell +lint: lint-go-all lint-imports lint-yaml lint-shell -lint-go: - cd $(MAKEFILE_DIR) && GOOS=linux golangci-lint run $(VERBOSE_FLAG_LONG) ./... && \ +lint-go-all: + cd $(MAKEFILE_DIR) && \ + GOOS=linux golangci-lint run $(VERBOSE_FLAG_LONG) ./... && \ GOOS=windows golangci-lint run $(VERBOSE_FLAG_LONG) ./... && \ GOOS=freebsd golangci-lint run $(VERBOSE_FLAG_LONG) ./... +lint-go: + cd $(MAKEFILE_DIR) && golangci-lint run $(VERBOSE_FLAG_LONG) ./... + lint-imports: cd $(MAKEFILE_DIR) && ./hack/lint-imports.sh diff --git a/cmd/nerdctl/main.go b/cmd/nerdctl/main.go index 50797e5b804..1a831a460a1 100644 --- a/cmd/nerdctl/main.go +++ b/cmd/nerdctl/main.go @@ -49,6 +49,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/errutil" "github.com/containerd/nerdctl/v2/pkg/logging" "github.com/containerd/nerdctl/v2/pkg/rootlessutil" + "github.com/containerd/nerdctl/v2/pkg/store" "github.com/containerd/nerdctl/v2/pkg/version" ) @@ -239,6 +240,16 @@ Config file ($NERDCTL_TOML): %s return fmt.Errorf("invalid cgroup-manager %q (supported values: \"systemd\", \"cgroupfs\", \"none\")", cgroupManager) } } + + // Since we store containers' stateful information on the filesystem per namespace, we need namespaces to be + // valid, safe path segments. This is enforced by store.ValidatePathComponent. + // Note that the container runtime will further enforce additional restrictions on namespace names + // (containerd treats namespaces as valid identifiers - eg: alphanumericals + dash, starting with a letter) + // See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#path-segment-names for + // considerations about path segments identifiers. + if err = store.ValidatePathComponent(globalOptions.Namespace); err != nil { + return err + } if appNeedsRootlessParentMain(cmd, args) { // reexec /proc/self/exe with `nsenter` into RootlessKit namespaces return rootlessutil.ParentMain(globalOptions.HostGatewayIP) diff --git a/pkg/containerutil/containerutil.go b/pkg/containerutil/containerutil.go index 8f215ecf679..4f21fa70546 100644 --- a/pkg/containerutil/containerutil.go +++ b/pkg/containerutil/containerutil.go @@ -46,7 +46,6 @@ import ( "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/labels/k8slabels" - "github.com/containerd/nerdctl/v2/pkg/nsutil" "github.com/containerd/nerdctl/v2/pkg/portutil" "github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/signalutil" @@ -529,9 +528,6 @@ func Unpause(ctx context.Context, client *containerd.Client, id string) error { // ContainerStateDirPath returns the path to the Nerdctl-managed state directory for the container with the given ID. func ContainerStateDirPath(ns, dataStore, id string) (string, error) { - if err := nsutil.ValidateNamespaceName(ns); err != nil { - return "", fmt.Errorf("invalid namespace name %q for determining state dir of container %q: %s", ns, id, err) - } return filepath.Join(dataStore, "containers", ns, id), nil } diff --git a/pkg/nsutil/nsutil.go b/pkg/nsutil/nsutil.go deleted file mode 100644 index 9cde4583c87..00000000000 --- a/pkg/nsutil/nsutil.go +++ /dev/null @@ -1,47 +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 nsutil provides utilities for namespaces. -package nsutil - -import ( - "fmt" - "strings" -) - -// Ensures the provided namespace name is valid. -// Namespace names cannot be path-like strings or pre-defined aliases such as "..". -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#path-segment-names -func ValidateNamespaceName(nsName string) error { - if nsName == "" { - return fmt.Errorf("namespace name cannot be empty") - } - - // Slash and '$' for POSIX and backslash and '%' for Windows. - pathSeparators := "/\\%$" - if strings.ContainsAny(nsName, pathSeparators) { - return fmt.Errorf("namespace name cannot contain any special characters (%q): %s", pathSeparators, nsName) - } - - specialAliases := []string{".", "..", "~"} - for _, alias := range specialAliases { - if nsName == alias { - return fmt.Errorf("namespace name cannot be special path alias %q", alias) - } - } - - return nil -} diff --git a/pkg/nsutil/nsutil_test.go b/pkg/nsutil/nsutil_test.go deleted file mode 100644 index 31d2fdffdc1..00000000000 --- a/pkg/nsutil/nsutil_test.go +++ /dev/null @@ -1,60 +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 nsutil_test - -import ( - "testing" - - "gotest.tools/v3/assert" - - "github.com/containerd/nerdctl/v2/pkg/nsutil" -) - -func TestValidateNamespaceName(t *testing.T) { - testCases := []struct { - inputs []string - errSubstr string - }{ - { - []string{"test", "test-hyphen", ".start.dot", "mid.dot", "end.dot."}, - "", - }, - { - []string{".", "..", "~"}, - "namespace name cannot be special path alias", - }, - { - []string{"$$", "a$VARiable", "a%VAR%iable", "\\.", "\\%", "\\$"}, - "namespace name cannot contain any special characters", - }, - { - []string{"/start", "mid/dle", "end/", "\\start", "mid\\dle", "end\\"}, - "namespace name cannot contain any special characters", - }, - } - - for _, tc := range testCases { - for _, input := range tc.inputs { - err := nsutil.ValidateNamespaceName(input) - if tc.errSubstr == "" { - assert.NilError(t, err) - } else { - assert.ErrorContains(t, err, tc.errSubstr) - } - } - } -} diff --git a/pkg/store/filestore.go b/pkg/store/filestore.go index ec0d98b3585..312155230fa 100644 --- a/pkg/store/filestore.go +++ b/pkg/store/filestore.go @@ -204,7 +204,7 @@ func (vs *fileStore) List(key ...string) ([]string, error) { // Unlike Get, Set and Delete, List can have zero length key for _, k := range key { - if err := validatePathComponent(k); err != nil { + if err := ValidatePathComponent(k); err != nil { return nil, err } } @@ -333,8 +333,8 @@ func (vs *fileStore) GroupSize(key ...string) (int64, error) { return size, nil } -// validatePathComponent will enforce os specific filename restrictions on a single path component -func validatePathComponent(pathComponent string) error { +// ValidatePathComponent will enforce os specific filename restrictions on a single path component +func ValidatePathComponent(pathComponent string) error { // https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits if len(pathComponent) > 255 { return errors.Join(ErrInvalidArgument, errors.New("identifiers must be stricly shorter than 256 characters")) @@ -358,7 +358,7 @@ func validateAllPathComponents(pathComponent ...string) error { } for _, key := range pathComponent { - if err := validatePathComponent(key); err != nil { + if err := ValidatePathComponent(key); err != nil { return err } } diff --git a/pkg/store/filestore_test.go b/pkg/store/filestore_test.go index 6840443b35f..58f4eebeef0 100644 --- a/pkg/store/filestore_test.go +++ b/pkg/store/filestore_test.go @@ -267,12 +267,12 @@ func TestFileStoreFilesystemRestrictions(t *testing.T) { } for _, v := range invalid { - err := validatePathComponent(v) + err := ValidatePathComponent(v) assert.ErrorIs(t, err, ErrInvalidArgument, v) } for _, v := range valid { - err := validatePathComponent(v) + err := ValidatePathComponent(v) assert.NilError(t, err, v) }