name: critest containerd
on:
  push:
    tags:
      - "*"
    branches:
      - master
  pull_request:
jobs:
  #
  # Run CRI tests against containerd
  #
  build-and-critest-containerd:
    strategy:
      fail-fast: false
      matrix:
        # ╔══════════════════╤═══════════╤═════════╗
        # ║ master / release │ ubuntu    │ windows ║
        # ╠══════════════════╪═══════════╪═════════╣
        # ║ v1 shim          │ runc      │         ║
        # ╟──────────────────┼───────────┼─────────╢
        # ║ v2 shim          │ runc/crun │         ║
        # ╟──────────────────┼───────────┼─────────╢
        # ║ hcshim           │           │ runhcs  ║
        # ╚══════════════════╧═══════════╧═════════╝
        os: [ubuntu-22.04, windows-2025]
        # not every command likes the slash in branch name.
        # So will use format command to replace to either `_` or '/'
        # There is no LTS version of 2.0+ containerd. So main testing covers the latest.
        version: [main, "release{0}1.7"]
        runtime:
          [
            io.containerd.runtime.v1.linux,
            io.containerd.runc.v1,
            io.containerd.runc.v2,
            containerd-shim-runhcs-v1,
          ]
        runc: [runc, crun]
        exclude:
          - runtime: io.containerd.runc.v1
            os: windows-2025
          - runtime: io.containerd.runc.v2
            os: windows-2025
          - runtime: io.containerd.runtime.v1.linux
            os: windows-2025
          - runtime: containerd-shim-runhcs-v1
            os: ubuntu-22.04
          - runtime: io.containerd.runc.v1
            runc: crun
          - runtime: io.containerd.runtime.v1.linux
            runc: crun
          - runc: crun
            os: windows-2025
    name: ${{ format(matrix.version, '/') }} / ${{ matrix.os }} / ${{matrix.runtime}} / ${{matrix.runc}}
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout cri-tools for this commit
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          path: ${{github.workspace}}/src/github.com/kubernetes-sigs/cri-tools

      - name: Install Go
        uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
        with:
          go-version-file: ${{github.workspace}}/src/github.com/kubernetes-sigs/cri-tools/go.mod
          cache: false

      - name: Set env
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV
          echo "${{ github.workspace }}/bin" >> $GITHUB_PATH

      - name: Checkout containerd/containerd ${{ format(matrix.version, '/') }}
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          path: ${{ github.workspace }}/src/github.com/containerd/containerd
          repository: containerd/containerd
          ref: ${{ format(matrix.version, '/') }}

      - name: Checkout Microsoft/hcsshim
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        if: startsWith(matrix.os, 'windows')
        with:
          repository: Microsoft/hcsshim
          path: src/github.com/Microsoft/hcsshim

      - name: Cache go modules and build cache
        uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
        with:
          path: |
            ~/go/pkg/mod
            ~/.cache/go-build
            '%LocalAppData%\go-build' # Windows
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

      - name: disable ipv6
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          sudo sysctl net.ipv6.conf.all.disable_ipv6=0

      - name: Install Linux dependencies for containerd
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            btrfs-progs \
            libbtrfs-dev \
            libseccomp2 \
            libseccomp-dev \
            socat

      - name: Install containerd on Linux
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          make
          sudo -E PATH=$PATH make install
        working-directory: ${{ github.workspace }}/src/github.com/containerd/containerd

      - name: Install containerd on Windows
        shell: bash
        if: startsWith(matrix.os, 'windows')
        run: |
          mingw32-make.exe
          mingw32-make.exe install
        working-directory: ${{ github.workspace }}/src/github.com/containerd/containerd

      - name: Install cni on Linux (release-1.7)
        # TODO: Remove this step once containerd release/1.7 is no longer supported.
        if: startsWith(matrix.os, 'ubuntu') && matrix.version == 'release{0}1.7'
        shell: bash
        # See https://github.com/containerd/containerd/pull/12660
        run: |
          sudo -E PATH=$PATH script/setup/install-cni
        working-directory: src/github.com/containerd/containerd
      - name: Install cni on Linux
        if: startsWith(matrix.os, 'ubuntu') && matrix.version != 'release{0}1.7'
        shell: bash
        run: |
          sudo -E PATH=$PATH script/setup/install-cni $(grep 'github.com/containernetworking/plugins' go.mod | awk '{print $2}')
        working-directory: src/github.com/containerd/containerd

      - name: Install cni on Windows
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          set -o errexit
          set -o nounset
          set -o pipefail

          # WINCNI_BIN_DIR is the cni plugin directory
          WINCNI_BIN_DIR="${WINCNI_BIN_DIR:-"C:\\Program Files\\containerd\\cni\\bin"}"
          WINCNI_PKG=github.com/Microsoft/windows-container-networking
          WINCNI_VERSION=aa10a0b31e9f72937063436454def1760b858ee2

          # Create a temporary GOPATH for cni installation.
          GOPATH="$(mktemp -d /tmp/cri-install-cni.XXXX)"

          # Install cni
          win_cni_src="${GOPATH}/src/${WINCNI_PKG}"
          mkdir -p ${win_cni_src}
          git clone https://${WINCNI_PKG} ${win_cni_src}
          cd ${win_cni_src}
          git checkout ${WINCNI_VERSION}
          make all
          install -D -m 755 "out/nat.exe" "${WINCNI_BIN_DIR}/nat.exe"
          install -D -m 755 "out/sdnbridge.exe" "${WINCNI_BIN_DIR}/sdnbridge.exe"
          install -D -m 755 "out/sdnoverlay.exe" "${WINCNI_BIN_DIR}/sdnoverlay.exe"

          # Clean the tmp GOPATH dir.
          rm -rf "${GOPATH}"

      - name: Configure cni on Windows
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          set -o errexit
          set -o nounset
          set -o pipefail

          CNI_CONFIG_DIR="${CNI_CONFIG_DIR:-"C:\\Program Files\\containerd\\cni\\conf"}"
          mkdir -p "${CNI_CONFIG_DIR}"

          # split_ip splits ip into a 4-element array.
          split_ip() {
            local -r varname="$1"
            local -r ip="$2"
            for i in {0..3}; do
              eval "$varname"[$i]=$( echo "$ip" | cut -d '.' -f $((i + 1)) )
            done
          }

          # subnet gets subnet for a gateway, e.g. 192.168.100.0/24.
          calculate_subnet() {
            local -r gateway="$1"
            local -r prefix_len="$2"
            split_ip gateway_array "$gateway"
            local len=$prefix_len
            for i in {0..3}; do
              if (( len >= 8 )); then
                mask=255
              elif (( len > 0 )); then
                mask=$(( 256 - 2 ** ( 8 - len ) ))
              else
                mask=0
              fi
              (( len -= 8 ))
              result_array[i]=$(( gateway_array[i] & mask ))
            done
            result="$(printf ".%s" "${result_array[@]}")"
            result="${result:1}"
            echo "$result/$((32 - prefix_len))"
          }

          # nat already exists on the Windows VM, the subnet and gateway
          # we specify should match that.
          gateway="$(powershell -c "(Get-NetIPAddress -InterfaceAlias 'vEthernet (nat)' -AddressFamily IPv4).IPAddress")"
          prefix_len="$(powershell -c "(Get-NetIPAddress -InterfaceAlias 'vEthernet (nat)' -AddressFamily IPv4).PrefixLength")"

          subnet="$(calculate_subnet "$gateway" "$prefix_len")"

          # The "name" field in the config is used as the underlying
          # network type right now (see
          # https://github.com/microsoft/windows-container-networking/pull/45),
          # so it must match a network type in:
          # https://docs.microsoft.com/en-us/windows-server/networking/technologies/hcn/hcn-json-document-schemas
          bash -c 'cat >"'"${CNI_CONFIG_DIR}"'"/0-containerd-nat.conf <<EOF
          {
              "cniVersion": "0.2.0",
              "name": "nat",
              "type": "nat",
              "master": "Ethernet",
              "ipam": {
                  "subnet": "'$subnet'",
                  "routes": [
                      {
                          "GW": "'$gateway'"
                      }
                  ]
              },
              "capabilities": {
                  "portMappings": true,
                  "dns": true
              }
          }
          EOF'

      - name: Install ${{matrix.runc}} runtime engine on Linux
        if: startsWith(matrix.os, 'ubuntu')
        env:
          RUNC_FLAVOR: ${{matrix.runc}}
        run: |
          sudo -E PATH=$PATH script/setup/install-runc
        working-directory: src/github.com/containerd/containerd

      - name: Build cri-tools on Linux
        if: startsWith(matrix.os, 'ubuntu')
        run: |
          make
          sudo -E PATH=$PATH make install
        working-directory: ${{ github.workspace }}/src/github.com/kubernetes-sigs/cri-tools

      - name: Build cri-tools on Windows
        if: startsWith(matrix.os, 'windows')
        run: |
          make
          make install
        working-directory: ${{ github.workspace }}/src/github.com/kubernetes-sigs/cri-tools

      - name: Install ginkgo on Linux
        if: startsWith(matrix.os, 'ubuntu')
        shell: bash
        run: |
          sudo chown -R $(id -u):$(id -g) $(go env GOPATH)
          go install github.com/onsi/ginkgo/v2/ginkgo@latest
          ginkgo version
          sudo cp $(command -v ginkgo) /usr/local/bin

      - name: Install ginkgo on Windows
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          go install github.com/onsi/ginkgo/v2/ginkgo@latest
          ginkgo version
          cp $(command -v ginkgo) /usr/local/bin

      - name: Run critest on Linux
        if: startsWith(matrix.os, 'ubuntu')
        shell: bash
        run: |
          set -o errexit
          set -o nounset
          set -o pipefail
          set -x

          BDIR="/var/lib/containerd-critest"
          echo "containerd temp dir: ${BDIR}"
          sudo mkdir -p ${BDIR}/{root,state}

          sudo bash -c 'cat > ${BDIR}/config.toml <<EOF
          version = 2
          [plugins]
            [plugins.cri.containerd.default_runtime]
              runtime_type = \"${{matrix.runtime}}\"
          EOF'

          # Remove possibly existing containerd configuration
          sudo rm -rf /etc/containerd
          sudo PATH=$PATH bash -c "/usr/local/bin/containerd -a ${BDIR}/c.sock -root ${BDIR}/root -state ${BDIR}/state -log-level debug &> ${BDIR}/containerd-cri.log &"

          while ! grep -q 'containerd successfully booted' ${BDIR}/containerd-cri.log; do
              sleep 1
          done

          sudo /usr/local/bin/ctr -a ${BDIR}/c.sock version
          sudo /usr/local/sbin/runc --version
          sudo mount

          TEST_RC=0
          sudo -E PATH=$PATH critest --ginkgo.vv --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8 || TEST_RC=$?
          test $TEST_RC -ne 0 && cat ${BDIR}/containerd-cri.log
          sudo pkill containerd
          echo "CONTD_CRI_DIR=$BDIR" >> $GITHUB_ENV
          test $TEST_RC -eq 0 || /bin/false

      - name: Run critest on Windows
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          set -o errexit
          set -o nounset
          set -o pipefail

          export PATH="/usr/local/bin:$PATH"
          FOCUS="${FOCUS:-"Conformance"}"
          REPORT_DIR="${REPORT_DIR:-"c/_artifacts"}"
          mkdir -p "${REPORT_DIR}"

          containerd -log-level debug &> "${REPORT_DIR}/containerd-cri.log" &
          pid=$!

          while ! grep -q 'containerd successfully booted' ${REPORT_DIR}/containerd-cri.log; do
              sleep 1
          done
          ctr version

          set +o errexit
          critest --runtime-endpoint=npipe:////./pipe/containerd-containerd --ginkgo.focus="${FOCUS}" --report-dir="${REPORT_DIR}" --report-prefix="windows"
          TEST_RC=$?
          test $TEST_RC -ne 0 && cat ${REPORT_DIR}/containerd.log
          set -o errexit
          kill -9 $pid
          echo "CONTD_CRI_DIR=$REPORT_DIR" >> $GITHUB_ENV
          exit ${TEST_RC}

      - name: Upload containerd ${{ format(matrix.version, '/') }} logs
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: containerd-${{ format(matrix.version, '_') }}-${{ matrix.os }}-${{matrix.runtime}}-${{matrix.runc}}-${{github.sha}}.log
          path: ${{env.CONTD_CRI_DIR}}/containerd-cri.log

      - name: Cleanup temp directory on Linux
        if: startsWith(matrix.os, 'ubuntu')
        shell: bash
        run: |
          echo "Cleanup temp directory ${{env.CONTD_CRI_DIR}} created for cri tests"
          sudo rm -rf ${{env.CONTD_CRI_DIR}}

      - name: Cleanup temp directory on Windows
        if: startsWith(matrix.os, 'windows')
        shell: bash
        run: |
          echo "Cleanup temp directory ${{env.CONTD_CRI_DIR}} created for cri tests"
          rm -rf ${{env.CONTD_CRI_DIR}}
