name: Presubmit 

on:
  pull_request:
  push:
    branches-ignore:
      - main  # push events to main branch occur after PRs are merged, when the same checks were run


concurrency:
  # limits the workflow to a single run per branch/PR
  group: ${{ github.workflow }}-${{ github.ref }}
  # previous runs are cancelled when a new run is started
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  buildozer:
    runs-on: ubuntu-22.04
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Install go
      uses: actions/setup-go@v6
      with:
        go-version: '1.26.6'
    - name: Install buildozer
      run: go install github.com/bazelbuild/buildtools/buildozer@latest
    - name: Validate formatting
      working-directory: base/cvd
      if: '!cancelled()'
      run: |
        if [[ $(buildozer '//cuttlefish/...:__pkg__' format 2>&1) ]]; then
          echo "Please format BUILD.bazel files with \"buildozer '//cuttlefish/...:__pkg__' format\"";
          exit 1;
        fi
    - name: Validate no cc_binary targets under //cuttlefish
      if: '!cancelled()'
      working-directory: base/cvd
      run: |
        if [[ $(buildozer print '//cuttlefish/...:%cc_binary') ]]; then
          buildozer print '//cuttlefish/...:%cc_binary'
          echo "Please use cf_cc_binary rather than cc_binary";
          exit 1;
        fi
    - name: Validate no cc_library targets under //cuttlefish
      if: '!cancelled()'
      working-directory: base/cvd
      run: |
        if [[ $(buildozer print '//cuttlefish/...:%cc_library') ]]; then
          buildozer print '//cuttlefish/...:%cc_library'
          echo "Please use cf_cc_library rather than cc_library";
          exit 1;
        fi
    - name: Validate no cc_test targets under //cuttlefish
      if: '!cancelled()'
      working-directory: base/cvd
      run: |
        if [[ $(buildozer print '//cuttlefish/...:%cc_test') ]]; then
          buildozer print '//cuttlefish/...:%cc_test'
          echo "Please use cf_cc_test rather than cc_test";
          exit 1;
        fi;
    - name: Validate no unused loads
      if: '!cancelled()'
      working-directory: base/cvd
      run: |
        if [[ $(buildozer -stdout=true '//cuttlefish/...:__pkg__' 'fix unusedLoads') ]]; then
          buildozer '//...:__pkg__' 'fix unusedLoads'
          echo "Please remove unused 'load' statements with \"buildozer '//cuttlefish/...:__pkg__' 'fix unusedLoads'\"";
          exit 1;
        fi
  staticcheck:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        dir:
          - "e2etests"
          - "container/src/podcvd"
          - "frontend/src/host_orchestrator"
          - "frontend/src/libhoclient"
          - "frontend/src/liboperator"
          - "frontend/src/operator"
          - "tools/baseimage"
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Install dependencies
      uses: actions/setup-go@v6
      with:
        go-version: '1.27.0'
    - run: go version
    - name: Staticcheck
      uses: dominikh/staticcheck-action@v1.3.1
      with:
        version: "latest"
        install-go: false
        working-directory: ${{ matrix.dir }}
  run-frontend-unit-tests:
    runs-on: ubuntu-22.04
    container:
      image: debian@sha256:9258a75a7e4323c9e5562b361effc84ee747920116d8adfc98a465a5cdc9150e # debian:bookworm-20250407 (amd64)
    env:
      GOPROJECTS: ('host_orchestrator' 'libhoclient' 'liboperator' 'operator')
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Setup apt
      run: apt update -y && apt upgrade -y
    - name: Install dependencies
      run: apt install -y git golang
    - name: Go version
      run: go version
    - name: Run gofmt check
      shell: bash
      run: |
        projects=${{ env.GOPROJECTS }}
        for item in "${projects[@]}"; do
          pushd "frontend/src/${item}"
          gofmt -d -e . && test -z "$(gofmt -l .)"
          popd
        done
    - name: Run go tests
      shell: bash
      run: |
        projects=${{ env.GOPROJECTS }}
        for item in "${projects[@]}"; do
          pushd "frontend/src/${item}"
          go test ./...
          popd
        done
  run-frontend-api-documentation-check:
    runs-on: ubuntu-22.04
    container:
      image: debian@sha256:9258a75a7e4323c9e5562b361effc84ee747920116d8adfc98a465a5cdc9150e # debian:bookworm-20250407 (amd64)
    env:
      GOPROJECTS: ('host_orchestrator')
    steps:
    - name: Setup apt
      run: apt update -y && apt upgrade -y
    - name: Install dependencies
      run: apt install -y git golang
    - name: Setup git
      run: |
        git --version
        # Fixes fatal: detected dubious ownership in repository at '/__w/android-cuttlefish/android-cuttlefish'
        git config --global --add safe.directory /__w/android-cuttlefish/android-cuttlefish
    - name: Go version
      run: go version
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Install swag
      run: go install github.com/swaggo/swag/cmd/swag@v1.16.5
    - name: Run swag check
      shell: bash
      run: |
        $(go env GOPATH)/bin/swag --version
        projects=${{ env.GOPROJECTS }}
        for item in "${projects[@]}"; do
          pushd "frontend/src/${item}"
          $(go env GOPATH)/bin/swag fmt
          git diff --exit-code || ( echo "format error: see frontend/src/host_orchestrator/README.md" && false)
          $(go env GOPATH)/bin/swag init
          git diff --exit-code || ( echo "This change requires REST API documentation update: see frontend/src/host_orchestrator/README.md" && false)
          popd
        done
  run-cvd-unit-tests:
    runs-on: ubuntu-24.04-4core
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@v1.3.1
      with:
        large-packages: false
        swap-storage: false
        tool-cache: true
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Mount Bazel cache
      uses: ./.github/actions/mount-bazel-cache
      with:
        action-name: "run-cvd-unit-tests"
    - name: Run cvd unit tests
      uses: ./.github/actions/run-cvd-unit-tests
    - name: Upload test logs
      if: always()
      uses: actions/upload-artifact@v6
      with:
        name: cvd-unit-tests-logs
        path: base/cvd/bazel-out/k8-fastbuild/testlogs
  bazel-9-readiness:
    runs-on: ubuntu-24.04
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@v1.3.1
      with:
        large-packages: false
        swap-storage: false
        tool-cache: true
    - name: Checkout repository
      uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # aka v2
    - name: Check bazel 9 readiness
      uses: ./.github/actions/bazel-9-readiness
  build-debian-package-amd64:
    runs-on: ubuntu-22.04
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # aka v1.3.1
      with:
        docker-images: false
        swap-storage: false
        tool-cache: true
    - name: checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Mount Bazel cache
      uses: ./.github/actions/mount-bazel-cache
      with:
        action-name: "build-debian-packages"
    - name: Build CF host debian packages
      uses: ./.github/actions/build-debian-packages
    - name: Build debs_amd64.tar
      run: find . -name 'cuttlefish-*.deb' -print0 | tar -cvf debs_amd64.tar --null --files-from -
    - name: Publish debs_amd64.tar
      uses: actions/upload-artifact@v6
      with:
        name: debs_amd64
        path: debs_amd64.tar
  build-debian-package-arm64:
    runs-on: ubuntu-22.04-arm
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # aka v1.3.1
      with:
        docker-images: false
        swap-storage: false
    - name: checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Mount Bazel cache
      uses: ./.github/actions/mount-bazel-cache
      with:
        action-name: "build-debian-packages"
    - name: Build CF host debian packages
      uses: ./.github/actions/build-debian-packages
    - name: Build debs_arm64.tar
      run: find . -name 'cuttlefish-*.deb' -print0 | tar -cvf debs_arm64.tar --null --files-from -
    - name: Publish debs_arm64.tar
      uses: actions/upload-artifact@v6
      with:
        name: debs_arm64
        path: debs_arm64.tar
  build-docker-image-amd64:
    needs: [build-debian-package-amd64]
    runs-on: ubuntu-22.04
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download cuttlefish debs
      uses: actions/download-artifact@v7
      with:
        name: debs_amd64
    - name: Build docker image
      shell: bash
      run: |
        tar -xvf debs_amd64.tar
        container/image/image-builder.sh -m dev
    - name: Save docker image
      run: docker save --output cuttlefish-orchestration.tar cuttlefish-orchestration:latest
    - name: Publish docker image
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f  # v6.0.0
      with:
        name: cuttlefish-orchestration-amd64
        path: cuttlefish-orchestration.tar
  build-docker-image-arm64:
    needs: [build-debian-package-arm64]
    runs-on: ubuntu-22.04-arm
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download cuttlefish debs
      uses: actions/download-artifact@v7
      with:
        name: debs_arm64
    - name: Build docker image
      shell: bash
      run: |
        tar -xvf debs_arm64.tar
        container/image/image-builder.sh -m dev
    - name: Save docker image
      run: docker save --output cuttlefish-orchestration.tar cuttlefish-orchestration:latest
    - name: Publish docker image
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f  # v6.0.0
      with:
        name: cuttlefish-orchestration-arm64
        path: cuttlefish-orchestration.tar
  e2e-tests-orchestration-build-image:
    runs-on: ubuntu-24.04
    needs: [build-debian-package-amd64]
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download cuttlefish debs
      uses: actions/download-artifact@v7
      with:
        name: debs_amd64
        github-token: ${{ github.token }}
    - name: Build image
      run: |
        tar -xvf debs_amd64.tar
        sudo podman info
        sudo podman build -f "tools/testutils/cw/Containerfile" --tag "android-cuttlefish-e2etest:latest" .
        sudo podman save --quiet -o android-cuttlefish-e2etest.tar localhost/android-cuttlefish-e2etest
    - name: Upload image
      uses: actions/upload-artifact@v6
      with:
        name: android-cuttlefish-e2etest-image-tar
        path: android-cuttlefish-e2etest.tar
  e2e-tests-orchestration-runner-1:
    runs-on: ubuntu-24.04
    needs: [e2e-tests-orchestration-build-image]
    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
      - name: Run tests
        uses: ./.github/actions/run-cw-sharded-e2e-test
        with:
          runner-index: 1
          runners-total: 3
  e2e-tests-orchestration-runner-2:
    runs-on: ubuntu-24.04
    needs: [e2e-tests-orchestration-build-image]
    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
      - name: Run tests
        uses: ./.github/actions/run-cw-sharded-e2e-test
        with:
          runner-index: 2
          runners-total: 3
  e2e-tests-orchestration-runner-3:
    runs-on: ubuntu-24.04
    needs: [e2e-tests-orchestration-build-image]
    steps:
      - name: Checkout repository
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
      - name: Run tests
        uses: ./.github/actions/run-cw-sharded-e2e-test
        with:
          runner-index: 3
          runners-total: 3
  e2e-tests-orchestration-runner-special:
    runs-on: ubuntu-24.04
    needs: [e2e-tests-orchestration-build-image]
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # aka v1.3.1
      with:
        tool-cache: true
    - name: checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: download image
      uses: actions/download-artifact@v7
      with:
        name: android-cuttlefish-e2etest-image-tar
        github-token: ${{ github.token }}
    - name: Run tests
      run: |
        sudo podman info
        sudo podman load --quiet -i android-cuttlefish-e2etest.tar && rm android-cuttlefish-e2etest.tar
        mkdir -p -m 777 /tmp/cw_bazel
        # Run flaky tests.
        #
        # Flaky tests would be executed in clean container everytime, rather than using `cvd reset`
        # in a tainted container.
        sudo podman run --name tester -d  --privileged --pids-limit=8192 -v /tmp/cw_bazel:/tmp/cw_bazel -v .:/src/workspace -w /src/workspace/e2etests android-cuttlefish-e2etest:latest
        targets=$(sudo podman exec --user=testrunner -it tester sh -c "stty -onlcr && bazel --output_user_root=/tmp/cw_bazel/output query --noshow_progress 'attr(flaky, 1, orchestration/...)' 2>/dev/null" | sort)
        echo "FLAKY TARGETS:"                                                                                                                                                                                              
        echo "${targets}"                                                                                                                                                                                                  
        echo ""
        readonly ATTEMPTS=3
        for t in ${targets}; do
          echo "Executing target: ${t}"
          attempt=1
          while [[ ${attempt} -le ${ATTEMPTS} ]]; do                                                                                                                                                                       
            echo "Attempt: ${attempt} of ${ATTEMPTS}"
            sudo podman rm -f tester
            sudo podman run --name tester -d  --privileged --pids-limit=8192 -v /tmp/cw_bazel:/tmp/cw_bazel -v .:/src/workspace -w /src/workspace/e2etests android-cuttlefish-e2etest:latest
            sudo podman exec --user=testrunner -it tester bazel --output_user_root=/tmp/cw_bazel/output test --test_timeout 600 --sandbox_writable_path=/home/testrunner --flaky_test_attempts=1 ${t} && break || true 
            attempt=$((attempt+1))                                                                                                                                                                                         
          done
          if [[ ${attempt} -gt ${ATTEMPTS}  ]]; then
            # Every previous attempt failed.
            exit 1
          fi
        done
        sudo podman rm -f tester
        # Run verify_access_token_test
        sudo podman run --name tester -d  --privileged --pids-limit=8192 -v /tmp/cw_bazel:/tmp/cw_bazel -v .:/src/workspace -w /src/workspace/e2etests android-cuttlefish-e2etest:latest
        sleep 30s # Add delay before restarting cuttlefish-host_orchestrator service.
        sudo podman exec -it tester sh -c 'echo "orchestrator_android_build_url=http://localhost:8090" >> /etc/default/cuttlefish-host_orchestrator && service cuttlefish-host_orchestrator restart'
        sudo podman exec --user=testrunner -it tester bazel --output_user_root=/tmp/cw_bazel/output test //orchestration/verify_access_token_test:verify_access_token_test_test
        sudo podman rm -f tester
        # Run create_with_gce_metadata_credentials_test
        sudo podman run --name tester -d  --privileged --pids-limit=8192 -v /tmp/cw_bazel:/tmp/cw_bazel -v .:/src/workspace -w /src/workspace/e2etests android-cuttlefish-e2etest:latest
        sleep 30s # Add delay before restarting cuttlefish-host_orchestrator service.
        sudo podman exec -it tester sh -c 'echo "build_api_credentials_use_gce_metadata=true" >> /etc/default/cuttlefish-host_orchestrator && service cuttlefish-host_orchestrator restart'
        sudo podman exec --user=testrunner -it tester bazel --output_user_root=/tmp/cw_bazel/output test //orchestration/create_with_gce_metadata_credentials_test:create_with_gce_metadata_credentials_test_test
        sudo podman rm -f tester
    - name: Upload test logs
      if: always()
      uses: actions/upload-artifact@v6
      with:
        name: e2e-tests-orchestration-runner-special-testlogs
        path: /tmp/cw_bazel/output/5d2d32753412f49aca3a92f1e1e5e35e/execroot/_main/bazel-out/k8-fastbuild/testlogs
    - name: Used disk space
      run: |
        df -h
  docker-image-check:
    runs-on: ubuntu-22.04
    needs: [build-docker-image-amd64]
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download docker image
      uses: actions/download-artifact@v7
      with:
        name: cuttlefish-orchestration-amd64
    - name: Load docker image
      run: docker load --input cuttlefish-orchestration.tar
    - name: Run check
      shell: bash
      run: |
        sudo docker run \
          -d \
          --device /dev/kvm \
          --device /dev/net/tun \
          --device /dev/vhost-net \
          --device /dev/vhost-vsock \
          --cap-add NET_ADMIN \
          --security-opt seccomp=unconfined \
          -p 2080:2080 \
          cuttlefish-orchestration:latest
        # Wait for HO service to start.
        sleep 10s
        res=$( curl -v http://localhost:2080/cvds )
        echo "response: ${res}"
        test "${res}" = '{"cvds":[]}'
        # Run create_from_images_zip_test e2e tests
        cd e2etests
        bazel test orchestration/create_from_images_zip_test:create_from_images_zip_test_test
  podman-image-check:
    runs-on: ubuntu-22.04
    needs: [build-docker-image-amd64]
    steps:
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download docker image
      uses: actions/download-artifact@v7
      with:
        name: cuttlefish-orchestration-amd64
    - name: Load docker image as podman image
      run: |
        sudo apt install -y skopeo
        sudo skopeo copy \
          docker-archive:cuttlefish-orchestration.tar \
          containers-storage:localhost/cuttlefish-orchestration:latest
    - name: Run check
      shell: bash
      run: |
        sudo podman run \
          -d \
          --device /dev/kvm \
          --device /dev/net/tun \
          --device /dev/vhost-net \
          --device /dev/vhost-vsock \
          --cap-add NET_ADMIN \
          -p 2080:2080 \
          localhost/cuttlefish-orchestration:latest
        # Wait for HO service to start.
        sleep 10s
        res=$( curl -v http://localhost:2080/cvds )
        echo "response: ${res}"
        test "${res}" = '{"cvds":[]}'
        # Run create_from_images_zip_test e2e tests
        cd e2etests
        bazel test orchestration/create_from_images_zip_test:create_from_images_zip_test_test
  e2etests-podcvd:
    runs-on: ubuntu-24.04
    needs: [build-debian-package-amd64, build-docker-image-amd64]
    steps:
    - name: Free disk space
      uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # aka v1.3.1
      with:
        docker-images: false
        swap-storage: false
        tool-cache: true
    - name: Checkout repository
      uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8  # v6.0.1
    - name: Download cuttlefish debs
      uses: actions/download-artifact@v7
      with:
        name: debs_amd64
    - name: Install cuttlefish-podcvd
      run: |
        tar -xvf debs_amd64.tar
        sudo apt install -y ./cuttlefish-podcvd_*.deb
        /usr/lib/cuttlefish-podcvd/bin/podcvd-setup
    - name: Download docker image
      uses: actions/download-artifact@v7
      with:
        name: cuttlefish-orchestration-amd64
    - name: Load docker image as podman image
      run: |
        sudo sysctl -w kernel.unprivileged_userns_clone=1
        sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
        sudo apt install -y skopeo

        # On E2E test, it uses podman local image unlike production.
        # Setting graphroot for this case is necessary to find local image by podman CLI.
        DRIVER=$(podman info --format '{{ .Store.GraphDriverName }}')
        GRAPHROOT=$(podman info --format '{{ .Store.GraphRoot }}')
        mkdir -p /tmp/podcvd-podman-config
        cat <<EOF > /tmp/podcvd-podman-config/storage.conf
        [storage]
        driver = "$DRIVER"
        graphroot = "$GRAPHROOT"
        rootless_storage_path = "$GRAPHROOT"
        EOF

        CONTAINERS_STORAGE_CONF=/tmp/podcvd-podman-config/storage.conf skopeo copy \
          docker-archive:cuttlefish-orchestration.tar \
          containers-storage:localhost/cuttlefish-orchestration:latest
    - name: Run cvd e2e test for podcvd
      run: |
        # TODO(b/520405665): Resolve flakiness when not doing chmod.
        sudo chmod 666 /dev/kvm

        cd e2etests
        bazel test \
          //cvd/bugreport_tests \
          //cvd/cvd_powerwash_tests \
          //cvd/display_tests \
          //cvd/env_tests \
          --test_env=HOME=$HOME \
          --test_env=USE_PODCVD=true \
          --test_env=XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \
          --test_env=CONTAINERS_STORAGE_CONF=/tmp/podcvd-podman-config/storage.conf \
          --test_output=errors
