name: CI
on:
  # When added to a merge queue.
  # See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions
  merge_group:
  pull_request:
    branches: ['main', 'release/**']

permissions: # added using https://github.com/step-security/secure-workflows
  contents: read

jobs:
  #
  # golangci-lint
  #
  linters:
    permissions:
      contents: read # for actions/checkout to fetch code
      pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
    name: Linters
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10

    strategy:
      matrix:
        os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
        exclude:
          - os: ${{ github.event.repository.private && 'ubuntu-24.04-arm' || '' }}


    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go
      - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9  # v8.0.0
        with:
          version: v2.9.0
          skip-cache: true
          args: --timeout=8m

  #
  # Project checks
  #
  project:
    name: Project Checks
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          path: src/github.com/containerd/containerd
          fetch-depth: 100

      - uses: ./src/github.com/containerd/containerd/.github/actions/install-go

      - uses: containerd/project-checks@d7751f3c375b8fe4a84c02a068184ee4c1f59bc4 # v1.2.2
        if: github.repository == 'containerd/containerd'
        with:
          working-directory: src/github.com/containerd/containerd
          repo-access-token: ${{ secrets.GITHUB_TOKEN }}
          # github.com/cyphar/filepath-securejoin: MPL-2.0 AND BSD-3-Clause
          # Approval: https://github.com/cncf/foundation/issues/1154#issuecomment-3562385979
          go-licenses-ignore: |
            github.com/cyphar/filepath-securejoin

      - name: verify go modules and vendor directory
        run: |
          sudo apt-get install -y jq
          make verify-vendor
        working-directory: src/github.com/containerd/containerd

  #
  # Protobuf checks
  #
  protos:
    name: Protobuf
    runs-on: ubuntu-latest
    timeout-minutes: 5

    defaults:
      run:
        working-directory: src/github.com/containerd/containerd

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          path: src/github.com/containerd/containerd

      - uses: ./src/github.com/containerd/containerd/.github/actions/install-go

      - name: Set env
        shell: bash
        run: |
          echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV
          echo "${{ github.workspace }}/bin" >> $GITHUB_PATH

      - name: Install protobuf
        run: |
          sudo -E PATH=$PATH script/setup/install-protobuf
          sudo chmod +x /usr/local/bin/protoc
          sudo chmod og+rx /usr/local/include/google /usr/local/include/google/protobuf /usr/local/include/google/protobuf/compiler
          sudo chmod -R og+r /usr/local/include/google/protobuf/
          protoc --version

      - run: script/setup/install-dev-tools
      - run: make proto-fmt
      - run: make check-protos check-api-descriptors

  man:
    name: Manpages
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go
      - run: go install github.com/cpuguy83/go-md2man/v2@v2.0.7
      - run: make man

  # Make sure binaries compile with other minor platforms.
  # Well-known architectures are covered in release.yml.
  crossbuild:
    name: Crossbuild Binaries
    needs: [project, linters, protos, man]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        include:
          - goos: linux
            goarch: arm
            goarm: "7"
          - goos: linux
            goarch: arm
            goarm: "5"
          - goos: freebsd
            goarch: amd64
          - goos: freebsd
            goarch: arm64

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go
      - run: |
          set -e -x

          packages=""
          platform="${{matrix.goos}}/${{matrix.goarch}}"
          if [ -n "${{matrix.goarm}}" ]; then
            platform+="/v${{matrix.goarm}}"
          fi

          case "${platform}" in
          linux/arm/v5)
            packages+=" crossbuild-essential-armel"
            echo "CGO_ENABLED=1" >> $GITHUB_ENV
            echo "CC=arm-linux-gnueabi-gcc" >> $GITHUB_ENV
            ;;
          linux/arm/v7)
            packages+=" crossbuild-essential-armhf"
            echo "CGO_ENABLED=1" >> $GITHUB_ENV
            echo "CC=arm-linux-gnueabihf-gcc" >> $GITHUB_ENV
            ;;
          windows/arm/v7)
            echo "CGO_ENABLED=0" >> $GITHUB_ENV
            ;;
          esac

          if [ -n "${packages}" ]; then
            sudo apt-get update && sudo apt-get install -y ${packages}
          fi
        name: Install deps
      - name: Build
        env:
          GOOS: ${{matrix.goos}}
          GOARCH: ${{matrix.goarch}}
          GOARM: ${{matrix.goarm}}
        run: |
          make build
          make binaries

  #
  # Build containerd binaries
  #
  binaries:
    name: Binaries
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    needs: [project, linters, protos, man]

    strategy:
      matrix:
        os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
        go-version: ["1.25.10", "1.26.3"]
        exclude:
          - os: ${{ github.event.repository.private && 'ubuntu-24.04-arm' || '' }}
    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go
        with:
          go-version: ${{ matrix.go-version }}
      - name: Make
        run: |
          make build
          make binaries

  #
  # Integration and CRI tests
  #
  integration-windows:
    name: Windows Integration
    runs-on: ${{ matrix.os }}
    timeout-minutes: 90
    needs: [project, linters, protos, man]
    env:
      GOTEST: gotestsum --

    strategy:
      fail-fast: false
      matrix:
        os: [windows-2022]
        cgroup_driver: [cgroupfs]

    defaults:
      run:
        shell: bash
        working-directory: src/github.com/containerd/containerd

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          path: src/github.com/containerd/containerd

      - uses: ./src/github.com/containerd/containerd/.github/actions/install-go

      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          repository: kubernetes-sigs/cri-tools
          path: src/github.com/kubernetes-sigs/cri-tools
          fetch-depth: 0

      - name: Set env
        run: |
          echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV
          echo "${{ github.workspace }}/bin" >> $GITHUB_PATH
          echo "${{ github.workspace }}/src/github.com/containerd/containerd/bin" >> $GITHUB_PATH
          echo "${{ github.workspace }}/src/github.com/kubernetes-sigs/cri-tools/build/bin/windows/amd64" >> $GITHUB_PATH

      - run: script/setup/install-dev-tools

      - name: Binaries
        shell: bash
        env:
          CGO_ENABLED: 1
        run: |
          set -o xtrace
          mingw32-make.exe binaries
          CRITEST_VERSION=$(cat script/setup/critools-version)
          cd ../../kubernetes-sigs/cri-tools
          git checkout "${CRITEST_VERSION}"
          make critest

      - run: script/setup/install-cni-windows

      - env:
          TEST_IMAGE_LIST: ${{github.workspace}}/repolist.toml
          CRI_TEST_IMAGES: ${{github.workspace}}/cri-test-images.yaml
          BUSYBOX_TESTING_IMAGE_REF: "registry.k8s.io/e2e-test-images/busybox:1.29-2"
          RESOURCE_CONSUMER_TESTING_IMAGE_REF: "registry.k8s.io/e2e-test-images/resource-consumer:1.10"
          WEBSERVER_TESTING_IMAGE_REF: "registry.k8s.io/e2e-test-images/nginx:1.14-2"
        run: |
          cat > "${{ env.TEST_IMAGE_LIST }}" << EOF
          busybox = "${{ env.BUSYBOX_TESTING_IMAGE_REF }}"
          ResourceConsumer = "${{ env.RESOURCE_CONSUMER_TESTING_IMAGE_REF }}"
          EOF
          cat > "${{ env.CRI_TEST_IMAGES }}" << EOF
          defaultTestContainerImage: ${{ env.BUSYBOX_TESTING_IMAGE_REF }}
          webServerTestImage: ${{ env.WEBSERVER_TESTING_IMAGE_REF }}
          EOF

      - name: Get crictl tool
        shell: powershell
        run: |
          # Get critctl tool. Used for cri-integration tests
          $CRICTL_DOWNLOAD_URL="https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.26.0/crictl-v1.26.0-windows-amd64.tar.gz"
          curl.exe -L $CRICTL_DOWNLOAD_URL -o c:\crictl.tar.gz
          tar -xvf c:\crictl.tar.gz
          mv crictl.exe "${{ github.workspace }}/bin/crictl.exe" # Move crictl somewhere in path

      - run: script/setup/install-gotestsum
      - run: script/setup/install-teststat
      - name: Tests
        env:
          CGO_ENABLED: 1
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-unit-root.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-unit-root-gotest.json
        run: mingw32-make.exe test root-test
      - run: if [ -f *-gotest.json ]; then echo '# Root Test' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh ${TESTFILE}
        env:
          TESTFILE: ${{github.workspace}}/test-unit-root-gotest.json
        if: always()

      - name: Integration 1
        env:
          CGO_ENABLED: 1
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-integration-serial-junit.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-integration-serial-gotest.json
          EXTRA_TESTFLAGS: "-timeout=20m"
        run: mingw32-make.exe integration
      - run: if [ -f *-gotest.json ]; then echo '# Integration 1' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh "$TESTFILE"
        env:
          TESTFILE: ${{github.workspace}}/test-integration-serial-gotest.json
        if: success()

      # Run the integration suite a second time. See discussion in github.com/containerd/containerd/pull/1759
      - name: Integration 2
        env:
          TESTFLAGS_PARALLEL: 1
          EXTRA_TESTFLAGS: "-short"
          CGO_ENABLED: 1
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-integration-parallel-junit.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-integration-parallel-gotest.json
        run: mingw32-make.exe integration
      - run: if [ -f *-gotest.json ]; then echo '# Integration 2' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh "$TESTFILE"
        env:
          TESTFILE: ${{github.workspace}}/test-integration-parallel-gotest.json
        if: success()

      - name: CRI Integration Test
        env:
          TEST_IMAGE_LIST: ${{github.workspace}}/repolist.toml
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        run: |
          make cri-integration

      - name: cri-tools critest
        env:
          CRI_TEST_IMAGES: ${{github.workspace}}/cri-test-images.yaml
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        shell: powershell
        run: |
          Start-Process -FilePath containerd.exe -NoNewWindow -RedirectStandardError true -PassThru
          get-process | sls containerd
          start-sleep 5
          if( '${{ matrix.os }}' -eq 'windows-2022' )
          {
            $skipTests = @(
              # This test is exceedingly flaky only on ws2022 so skip for now to keep CI happy.
              # Info: https://github.com/containerd/containerd/issues/6652
              "runtime should support exec with tty=true and stdin=true",

              # Skip tests that are known to be flaky on Windows
              # See https://github.com/containerd/containerd/issues/12580
              "public image with tag should be pulled and removed",
              "public image without tag should be pulled and removed",
              "image status should support all kinds of references",
              "listImage should get exactly 3 image in the result list",
              "listImage should get exactly 3 repoTags in the result image"
            )
            $skip = $skipTests -join "|"
          }
          Write-Host "Skip parameters: $skip"
          critest.exe --runtime-endpoint=npipe://.//pipe//containerd-containerd --test-images-file='${{env.CRI_TEST_IMAGES}}' --report-dir='${{github.workspace}}/critestreport' --ginkgo.skip="$skip"

      - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
        if: always()
        with:
          name: TestResults ${{ matrix.os }} ${{ matrix.cgroup_driver }}
          path: |
            ${{github.workspace}}/*-junit.xml
            ${{github.workspace}}/*-gotest.json
            ${{github.workspace}}/report/*.log

  integration-linux:
    name: Linux Integration
    runs-on: ${{ matrix.os }}
    timeout-minutes: 60
    needs: [project, linters, protos, man]

    strategy:
      fail-fast: false
      matrix:
        runtime:
          - io.containerd.runc.v2
        runc: [runc]  # crun can be added here to debug crun issues
        os: [ubuntu-22.04, ubuntu-24.04, ubuntu-24.04-arm]
        cgroup_driver: [cgroupfs, systemd]
        exclude:
          - os: ${{ github.event.repository.private && 'ubuntu-24.04-arm' || '' }}

    env:
      GOTEST: gotestsum --
    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go

      - name: Install containerd dependencies
        env:
          RUNC_FLAVOR: ${{ matrix.runc }}
        run: |
          sudo apt-get update
          sudo apt-get install -y gperf dmsetup strace xfsprogs
          script/setup/install-seccomp
          script/setup/install-runc
          script/setup/install-cni $(grep containernetworking/plugins go.mod | awk '{print $2}')
          script/setup/install-critools
          script/setup/install-failpoint-binaries

      # Disable criu testing on arm64 until we can solve the consistent failures of restore testing
      - if: matrix.os != 'ubuntu-24.04-arm'
        name: Install criu
        run: |
          sudo add-apt-repository -y ppa:criu/ppa
          sudo apt-get update
          sudo apt-get install -y criu

      # "apt-get install erofs-utils" installs an older version (1.7.1 as of 2025-09-18)
      # Build and install erofs-utils 1.8.8 from source to get a newer version
      - name: Build and install erofs-utils from source
        run: |
          # Install dependencies
          sudo apt-get update
          sudo apt-get install -y wget make gcc libz-dev liblz4-dev libfuse-dev autoconf libtool pkg-config uuid-dev

          # Create temporary build directory
          BUILD_DIR="$(mktemp -d)"
          cd "$BUILD_DIR"

          # get erofs-utils-1.8.8.tar.gz
          wget https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git/snapshot/erofs-utils-1.8.8.tar.gz
          tar -xzf erofs-utils-1.8.8.tar.gz
          cd erofs-utils-1.8.8

          # Build and install erofs-utils
          ./autogen.sh
          ./configure --enable-lz4 --enable-fuse --with-uuid
          make -j"$(nproc)"
          sudo make install
          sudo ldconfig

          # Clean up
          cd -
          rm -rf "$BUILD_DIR"

          # Verify installation
          mkfs.erofs --version

      - name: Load EROFS kernel module
        run: |
          sudo modprobe erofs

      - name: Install containerd
        env:
          CGO_ENABLED: 1
        run: |
          make binaries GO_BUILD_FLAGS="-mod=vendor"
          sudo -E PATH=$PATH make install

      - run: script/setup/install-gotestsum
      - run: script/setup/install-teststat
      - name: Tests
        env:
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-unit-root-junit.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-unit-root-gotest.json
        run: |
          make test
          sudo -E PATH=$PATH make root-test
      - run: if [ -f *-gotest.json ]; then echo '# Root Test' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh *-gotest.json
        if: always()

      - name: Integration 1
        env:
          TEST_RUNTIME: ${{ matrix.runtime }}
          RUNC_FLAVOR: ${{ matrix.runc }}
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-integration-serial-junit.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-integration-serial-gotest.json
        run: |
          extraflags=""
          [ "${RUNC_FLAVOR}" == "crun" ] && {
                  extraflags="EXTRA_TESTFLAGS=-no-criu";
          }
          sudo -E PATH=$PATH make integration ${extraflags} TESTFLAGS_RACE=-race
      - run: if [ -f *-gotest.json ]; then echo '# Integration 1' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh *-gotest.json
        if: always()

      # Run the integration suite a second time. See discussion in github.com/containerd/containerd/pull/1759
      - name: Integration 2
        env:
          TEST_RUNTIME: ${{ matrix.runtime }}
          RUNC_FLAVOR: ${{ matrix.runc }}
          GOTESTSUM_JUNITFILE: ${{github.workspace}}/test-integration-parallel-junit.xml
          GOTESTSUM_JSONFILE: ${{github.workspace}}/test-integration-parallel-gotest.json
        run: |
          extraflags=""
          [ "${RUNC_FLAVOR}" == "crun" ] && {
                  extraflags="EXTRA_TESTFLAGS=-no-criu";
          }
          sudo -E PATH=$PATH TESTFLAGS_PARALLEL=1 make integration ${extraflags}
      - run: if [ -f *-gotest.json ]; then echo '# Integration 2' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh *-gotest.json
        if: always()

      - name: CRI Integration Test
        env:
          TEST_RUNTIME: ${{ matrix.runtime }}
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
          RUNC_FLAVOR: ${{ matrix.runc }}
        run: |
          cat /sys/fs/cgroup/cgroup.controllers
          systemctl status
          [ "${RUNC_FLAVOR}" == "crun" ] && {
             export XDG_RUNTIME_DIR=/run/user/$(id -u)
          }
          runc --version
          CONTAINERD_RUNTIME=$TEST_RUNTIME make cri-integration

      - name: cri-tools critest
        env:
          TEST_RUNTIME: ${{ matrix.runtime }}
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        run: |
          env
          sudo -E PATH=$PATH ./script/critest.sh "${{github.workspace}}/report"

      - name: Install tools
        # The GitHub Actions image already has buildah and podman included. Not the
        # ubuntu-24.04-arm images. buildah and podman  is needed to convert the
        # Kubernetes checkpoint archive to an OCI image.
        # See contrib/checkpoint/checkpoint-restore-cri-test.sh
        if: matrix.os == 'ubuntu-24.04-arm'
        run: sudo apt-get install -y buildah podman

      - if: matrix.os != 'ubuntu-24.04-arm'
        name: Checkpoint/Restore via CRI
        env:
          TEST_RUNTIME: ${{ matrix.runtime }}
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        run: |
          env
          sudo -E PATH=$PATH ./contrib/checkpoint/checkpoint-restore-cri-test.sh

      # Log the status of this VM to investigate issues like
      # https://github.com/containerd/containerd/issues/4969
      - name: Host Status
        if: always()
        run: |
          set -x
          mount
          df
          losetup -l
      - name: Kernel Message
        if: failure()
        run: |
          sudo lsmod
          sudo dmesg -T -f kern

      - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
        if: always()
        with:
          name: TestResults ${{ matrix.runtime }} ${{matrix.runc}} ${{ matrix.os }} ${{ matrix.cgroup_driver }}
          path: |
            *-junit.xml
            *-gotest.json
            ${{github.workspace}}/report/*.xml
            ${{github.workspace}}/report/*.log

  integration-vagrant:
    name: Vagrant integration
    runs-on: ubuntu-latest
    timeout-minutes: 60
    needs: [project, linters, protos, man]

    strategy:
      fail-fast: false
      matrix:
        include:
          - box: fedora/43-cloud-base
            cgroup_driver: cgroupfs
            runc: runc
          - box: fedora/43-cloud-base
            cgroup_driver: systemd
            runc: runc
          - box: fedora/43-cloud-base
            cgroup_driver: cgroupfs
            runc: crun
          - box: fedora/43-cloud-base
            cgroup_driver: systemd
            runc: crun
            # We have to keep EL8 to test old glibc, cgroup, kernel, etc.
            # The image was changed from rockylinux/8 to almalinux/8,
            # as the former one no longer works:
            # https://github.com/containerd/containerd/pull/10297
          - box: almalinux/8
            cgroup_driver: cgroupfs
            runc: runc
          - box: almalinux/8
            cgroup_driver: systemd
            runc: runc
          - box: almalinux/9
            cgroup_driver: cgroupfs
            runc: runc
          - box: almalinux/9
            cgroup_driver: systemd
            runc: runc
    env:
      BOX: ${{ matrix.box }}
      CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
      RUNC_FLAVOR: ${{ matrix.runc }}
    steps:
      - name: Show the host info
        run: |
          set -x
          uname -a
          cat /etc/os-release
          cat /proc/cpuinfo
          free -mt
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: /root/.vagrant.d
          key: vagrant-${{ matrix.box }}
      - name: Set up vagrant
        run: |
          # Canonical's Vagrant 2.2.19 dpkg cannot download Fedora 38 image: https://bugs.launchpad.net/vagrant/+bug/2017828
          # So we have to install Vagrant >= 2.3.1 from the upstream: https://github.com/opencontainers/runc/blob/v1.1.8/.cirrus.yml#L41-L49
          curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
          echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
          sudo sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
          sudo apt-get update
          sudo apt-get install -y libvirt-daemon libvirt-daemon-system vagrant ovmf
          # https://github.com/vagrant-libvirt/vagrant-libvirt/issues/1725#issuecomment-1454058646
          sudo cp /usr/share/OVMF/OVMF_VARS_4M.fd /var/lib/libvirt/qemu/nvram/
          sudo systemctl enable --now libvirtd
          sudo apt-get build-dep -y ruby-libvirt
          sudo apt-get install -y --no-install-recommends libxslt-dev libxml2-dev libvirt-dev ruby-bundler ruby-dev zlib1g-dev
          sudo env VAGRANT_DISABLE_STRICT_DEPENDENCY_ENFORCEMENT=1 vagrant plugin install vagrant-libvirt
      - name: Boot VM
        run: |
          if  [ "$BOX" = "fedora/43-cloud-base" ]; then
            # fedora/41-cloud-base seems the last version available in https://portal.cloud.hashicorp.com/vagrant/discover/fedora
            sudo vagrant box add fedora/43-cloud-base https://download.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt-43-1.6.x86_64.vagrant.libvirt.box
          fi
          sudo BOX=$BOX RUNC_FLAVOR=$RUNC_FLAVOR vagrant up --no-tty
      - name: test-integration
        run: sudo BOX=$BOX RUNC_FLAVOR=$RUNC_FLAVOR vagrant up --provision-with=selinux,install-runc,install-gotestsum,test-integration
      - name: test-cri-integration
        run: sudo BOX=$BOX CGROUP_DRIVER=$CGROUP_DRIVER RUNC_FLAVOR=$RUNC_FLAVOR vagrant up --provision-with=selinux,install-runc,install-gotestsum,test-cri-integration
      - name: test-cri
        run: sudo BOX=$BOX CGROUP_DRIVER=$CGROUP_DRIVER RUNC_FLAVOR=$RUNC_FLAVOR vagrant up --provision-with=selinux,install-runc,install-gotestsum,test-cri
      - name: dump-dmesg
        if: always()
        run: sudo BOX=$BOX vagrant ssh -c "sudo dmesg -T -f kern"

  tests-cri-in-userns:
    name: "CRI-in-UserNS"

    runs-on: ubuntu-latest
    timeout-minutes: 40
    needs: [project, linters, protos, man]

    strategy:
      fail-fast: false
      matrix:
        cgroup_driver: [cgroupfs, systemd]

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - name: Set up cgroup v2 delegation
        run: |
          sudo mkdir -p /etc/systemd/system/user@.service.d
          cat <<EOF | sudo tee /etc/systemd/system/user@.service.d/delegate.conf
          [Service]
          Delegate=cpu cpuset io memory pids
          EOF
          sudo systemctl daemon-reload
      - name: Build cri-in-userns image
        env:
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        run: |
          if [ "$CGROUP_DRIVER" = "systemd" ];then
            podman build --target cri-in-userns-systemd -t cri-in-userns-systemd -f ./contrib/Dockerfile.test .
          else
            podman build --target cri-in-userns -t cri-in-userns -f ./contrib/Dockerfile.test .
          fi

      - name: Run cri-in-userns image
        env:
          CGROUP_DRIVER: ${{ matrix.cgroup_driver }}
        # Rootless Podman is used for testing CRI-in-UserNS
        # (We could use rootless Docker or rootless nerdctl, but we are using Podman here because it is preinstalled)
        run: |
          if [ "$CGROUP_DRIVER" = "systemd" ];then
              set +e
              touch ./critest_exit_code.txt
              podman run --rm --privileged --group-add keep-groups -v ./critest_exit_code.txt:/tmp/critest_exit_code.txt cri-in-userns-systemd
              exit_code=`cat ./critest_exit_code.txt`
              echo "exit_code:"$exit_code
              if [ "$exit_code" -gt 0 ]; then
                exit 1
              else
                exit 0
              fi
          else
            podman run --rm --privileged cri-in-userns
          fi

  tests-mac-os:
    name: MacOS unit tests
    runs-on: macos-latest
    timeout-minutes: 10
    needs: [project, linters, protos, man]
    env:
      GOTEST: gotestsum --

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      - uses: ./.github/actions/install-go
      - run: script/setup/install-gotestsum
      - run: script/setup/install-teststat
      - name: Tests
        env:
          GOTESTSUM_JUNITFILE: "${{ github.workspace }}/macos-test-junit.xml"
          GOTESTSUM_JSONFILE: "${{ github.workspace }}/macos-test-gotest.json"
        run: make test
      - run: if [ -f *-gotest.json ]; then echo '# Unit Tests' >> $GITHUB_STEP_SUMMARY; teststat -markdown *-gotest.json >> $GITHUB_STEP_SUMMARY; fi
        if: always()
      - run: script/test/test2annotation.sh *-gotest.json
        if: always()
      - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
        if: always()
        with:
          name: TestResults MacOS
          path: |
            *-junit.xml
            *-gotest.json

  node-e2e:
    name: E2E
    uses: ./.github/workflows/node-e2e.yml

  # Currently Github actions UI supports no masks to mark matrix jobs as required to pass status checks.
  # This means that every time version of Go, containerd, or OS is changed, a corresponding job should
  # be added to the list of required checks. Which is not very convenient.
  # To workaround this, a special job is added to report statuses of all other jobs, with fixed title.
  # So it needs to be added to the list of required checks only once.
  #
  # See https://github.com/orgs/community/discussions/26822
  results:
    name: Report required job statuses
    runs-on: ubuntu-latest
    # List job dependencies which are required to pass status checks in order to be merged via merge queue.
    needs: [linters, project, protos, binaries, integration-linux, integration-windows, tests-cri-in-userns, tests-mac-os, node-e2e]
    if: ${{ always() }}
    steps:
      - run: exit 1
        if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
