Unverified Commit 9728ef4d authored by Wolfgang Walther's avatar Wolfgang Walther
Browse files

Merge branch 'master' into staging-next

parents 9b58e9be de2bd218
Loading
Loading
Loading
Loading
+0 −12
Original line number Diff line number Diff line
@@ -112,18 +112,6 @@ jobs:
    with:
      headBranch: ${{ needs.prepare.outputs.headBranch }}

  reviewers:
    name: Reviewers
    needs: [prepare, eval]
    if: |
      needs.prepare.outputs.targetSha &&
      !contains(fromJSON(needs.prepare.outputs.headBranch).type, 'development')
    uses: ./.github/workflows/reviewers.yml
    secrets:
      OWNER_APP_PRIVATE_KEY: ${{ secrets.OWNER_APP_PRIVATE_KEY }}
    with:
      artifact-prefix: ${{ inputs.artifact-prefix }}

  build:
    name: Build
    needs: [prepare]

.github/workflows/reviewers.yml

deleted100644 → 0
+0 −157
Original line number Diff line number Diff line
# This workflow will request reviews from the maintainers of each package
# listed in the PR's most recent eval comparison artifact.

name: Reviewers

on:
  pull_request_target:
    types: [ready_for_review]
  workflow_call:
    inputs:
      artifact-prefix:
        required: true
        type: string
    secrets:
      OWNER_APP_PRIVATE_KEY:
        required: true

concurrency:
  group: reviewers-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
  cancel-in-progress: true

permissions: {}

defaults:
  run:
    shell: bash

jobs:
  request:
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 20
    steps:
      - name: Check out the PR at the base commit
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false
          path: trusted
          sparse-checkout: ci

      - name: Install Nix
        uses: cachix/install-nix-action@456688f15bc354bef6d396e4a35f4f89d40bf2b7 # v31

      - name: Build the requestReviews derivation
        run: nix-build trusted/ci -A requestReviews

      # For requesting reviewers, this job depends on a GitHub App with the following permissions:
      # - Permissions:
      #   - Repository > Administration: read-only
      #   - Organization > Members: read-only
      #   - Repository > Pull Requests: read-write
      # - Install App on this repository, setting these variables:
      #   - OWNER_APP_ID (variable)
      #   - OWNER_APP_PRIVATE_KEY (secret)
      #
      # Can't use the token received from permissions above, because it can't get enough permissions.
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        if: github.event_name == 'pull_request_target' && vars.OWNER_APP_ID
        id: app-token
        with:
          app-id: ${{ vars.OWNER_APP_ID }}
          private-key: ${{ secrets.OWNER_APP_PRIVATE_KEY }}
          permission-administration: read
          permission-members: read
          permission-pull-requests: write

      - name: Log current API rate limits (github.token)
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh api /rate_limit | jq

      # In the regular case, this workflow is called via workflow_call from the eval workflow directly.
      # In the more special case, when a PR is undrafted an eval run will have started already.
      - name: Wait for comparison to be done
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        id: eval
        env:
          ARTIFACT: ${{ inputs.artifact-prefix }}comparison
        with:
          script: |
            const run_id = (await github.rest.actions.listWorkflowRuns({
              owner: context.repo.owner,
              repo: context.repo.repo,
              workflow_id: context.eventName === 'pull_request' ? 'test.yml' : 'pull-request-target.yml',
              event: context.eventName,
              head_sha: context.payload.pull_request.head.sha
            })).data.workflow_runs[0].id

            core.setOutput('run-id', run_id)

            // Waiting 120 * 5 sec = 10 min. max.
            // The extreme case is an Eval run that just started when the PR is undrafted.
            // Eval takes max 5-6 minutes, normally.
            for (let i = 0; i < 120; i++) {
              const result = await github.rest.actions.listWorkflowRunArtifacts({
                owner: context.repo.owner,
                repo: context.repo.repo,
                run_id,
                name: process.env.ARTIFACT,
              })
              if (result.data.total_count > 0) return
              await new Promise(resolve => setTimeout(resolve, 5000))
            }
            throw new Error("No comparison artifact found.")

      - name: Log current API rate limits (github.token)
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh api /rate_limit | jq

      - name: Download the comparison results
        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
        with:
          run-id: ${{ steps.eval.outputs.run-id }}
          github-token: ${{ github.token }}
          pattern: ${{ inputs.artifact-prefix }}comparison
          path: comparison
          merge-multiple: true

      - name: Log current API rate limits (app-token)
        if: ${{ steps.app-token.outputs.token }}
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: gh api /rate_limit | jq

      - name: Log current API rate limits (github.token)
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh api /rate_limit | jq

      - name: Requesting reviews
        if: ${{ steps.app-token.outputs.token }}
        env:
          GH_TOKEN: ${{ github.token }}
          APP_GH_TOKEN: ${{ steps.app-token.outputs.token }}
          REPOSITORY: ${{ github.repository }}
          NUMBER: ${{ github.event.number }}
          AUTHOR: ${{ github.event.pull_request.user.login }}
          # Don't request reviewers on draft PRs
          DRY_MODE: ${{ github.event.pull_request.draft && '1' || '' }}
        run: |
          # maintainers.json contains GitHub IDs. Look up handles to request reviews from.
          # There appears to be no API to request reviews based on GitHub IDs
          jq -r 'keys[]' comparison/maintainers.json \
            | while read -r id; do gh api /user/"$id" --jq .login; done \
            | cat comparison/owners.txt - \
            | GH_TOKEN="$APP_GH_TOKEN" result/bin/request-reviewers.sh "$REPOSITORY" "$NUMBER" "$AUTHOR"

      - name: Log current API rate limits (app-token)
        if: ${{ steps.app-token.outputs.token }}
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: gh api /rate_limit | jq

      - name: Log current API rate limits (github.token)
        env:
          GH_TOKEN: ${{ github.token }}
        run: gh api /rate_limit | jq
+0 −1
Original line number Diff line number Diff line
@@ -69,7 +69,6 @@ jobs:
              '.github/workflows/eval.yml',
              '.github/workflows/lint.yml',
              '.github/workflows/pull-request-target.yml',
              '.github/workflows/reviewers.yml',
              '.github/workflows/test.yml',
              'ci/github-script/bot.js',
              'ci/github-script/merge.js',
+0 −1
Original line number Diff line number Diff line
@@ -156,7 +156,6 @@ let
in
rec {
  inherit pkgs fmt;
  requestReviews = pkgs.callPackage ./request-reviews { };
  codeownersValidator = pkgs.callPackage ./codeowners-validator { };

  # FIXME(lf-): it might be useful to test other Nix implementations
+48 −13
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ module.exports = async ({ github, context, core, dry }) => {
  const withRateLimit = require('./withRateLimit.js')
  const { classify } = require('../supportedBranches.js')
  const { handleMerge } = require('./merge.js')
  const { handleReviewers } = require('./reviewers.js')

  const artifactClient = new DefaultArtifactClient()

@@ -209,10 +210,16 @@ module.exports = async ({ github, context, core, dry }) => {
      }
    }

    const reviews = await github.paginate(github.rest.pulls.listReviews, {
    // Check for any human reviews other than GitHub actions and other GitHub apps.
    // Accounts could be deleted as well, so don't count them.
    const reviews = (
      await github.paginate(github.rest.pulls.listReviews, {
        ...context.repo,
        pull_number,
      })
    ).filter(
      (r) => r.user && !r.user.login.endsWith('[bot]') && r.user.type !== 'Bot',
    )

    const approvals = new Set(
      reviews
@@ -282,13 +289,6 @@ module.exports = async ({ github, context, core, dry }) => {
    log('Last eval run', run_id ?? '<n/a>')

    if (conclusion === 'success') {
      // Check for any human reviews other than GitHub actions and other GitHub apps.
      // Accounts could be deleted as well, so don't count them.
      const humanReviews = reviews.filter(
        (r) =>
          r.user && !r.user.login.endsWith('[bot]') && r.user.type !== 'Bot',
      )

      Object.assign(prLabels, {
        // We only set this label if the latest eval run was successful, because if it was not, it
        // *could* have requested reviewers. We will let the PR author fix CI first, before "escalating"
@@ -301,7 +301,7 @@ module.exports = async ({ github, context, core, dry }) => {
        '9.needs: reviewer':
          !pull_request.draft &&
          pull_request.requested_reviewers.length === 0 &&
          humanReviews.length === 0,
          reviews.length === 0,
      })
    }

@@ -373,6 +373,41 @@ module.exports = async ({ github, context, core, dry }) => {
          maintainers[pkg]?.some((m) => approvals.has(m)),
        ),
      })

      if (!pull_request.draft) {
        let owners = []
        try {
          // TODO: Create owner map similar to maintainer map.
          owners = (await readFile(`${pull_number}/owners.txt`, 'utf-8')).split(
            '\n',
          )
        } catch (e) {
          // Older artifacts don't have the owners.txt, yet.
          if (e.code !== 'ENOENT') throw e
        }

        // We set this label earlier already, but the current PR state can be very different
        // after handleReviewers has requested reviews, so update it in this case to prevent
        // this label from flip-flopping.
        prLabels['9.needs: reviewer'] = await handleReviewers({
          github,
          context,
          core,
          log,
          dry,
          pull_request,
          reviews,
          // TODO: Use maintainer map instead of the artifact.
          maintainers: Object.keys(
            JSON.parse(
              await readFile(`${pull_number}/maintainers.json`, 'utf-8'),
            ),
          ).map((id) => parseInt(id)),
          owners,
          getTeamMembers,
          getUser,
        })
      }
    }

    return prLabels
@@ -521,7 +556,7 @@ module.exports = async ({ github, context, core, dry }) => {
      const hasChanges = Object.keys(after).some(
        (name) => (before[name] ?? false) !== after[name],
      )
      if (log('Has changes', hasChanges, !hasChanges)) return
      if (log('Has label changes', hasChanges, !hasChanges)) return

      // Skipping labeling on a pull_request event, because we have no privileges.
      const labels = Object.entries(after)
Loading