Unverified Commit 2c2e4bc6 authored by Silvan Mosberger's avatar Silvan Mosberger Committed by GitHub
Browse files

lib.teams: Sync from GitHub (members/shortName/scope) (#450864)

parents d0b2c6c6 76363898
Loading
Loading
Loading
Loading
+81 −0
Original line number Diff line number Diff line
name: Teams

on:
  schedule:
    # Every Tuesday at 19:42 (randomly chosen)
    - cron: '42 19 * * 1'

  # Allows manual trigger
  workflow_dispatch:

permissions: {}

defaults:
  run:
    shell: bash

jobs:
  sync:
    runs-on: ubuntu-24.04-arm
    steps:
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        id: team-token
        with:
          app-id: ${{ vars.OWNER_APP_ID }}
          private-key: ${{ secrets.OWNER_APP_PRIVATE_KEY }}
          permission-administration: read
          permission-members: read
      - name: Fetch source
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false
          sparse-checkout: |
            ci/github-script
            maintainers/github-teams.json
      - name: Install dependencies
        run: npm install bottleneck
      - name: Synchronise teams
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          github-token: ${{ steps.team-token.outputs.token }}
          script: |
            require('./ci/github-script/get-teams.js')({
              github,
              context,
              core,
              outFile: "maintainers/github-teams.json"
            })

      # Use a GitHub App to create the PR so that CI gets triggered
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        id: sync-token
        with:
          app-id: ${{ vars.NIXPKGS_CI_APP_ID }}
          private-key: ${{ secrets.NIXPKGS_CI_APP_PRIVATE_KEY }}
          permission-contents: write
          permission-pull-requests: write
      - name: Get GitHub App User Git String
        id: user
        env:
          GH_TOKEN: ${{ steps.sync-token.outputs.token }}
          APP_SLUG: ${{ steps.sync-token.outputs.app-slug }}
        run: |
          name="${APP_SLUG}[bot]"
          userId=$(gh api "/users/$name" --jq .id)
          email="$userId+$name@users.noreply.github.com"
          echo "git-string=$name <$email>" >> "$GITHUB_OUTPUT"
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
        with:
          token: ${{ steps.sync-token.outputs.token }}
          add-paths: maintainers/github-teams.json
          author: ${{ steps.user.outputs.git-string }}
          committer: ${{ steps.user.outputs.git-string }}
          commit-message: "maintainers/github-teams.json: Automated sync"
          branch: github-team-sync
          title: "maintainers/github-teams.json: Automated sync"
          body: |
            This is an automated PR to sync the GitHub teams with access to this repository to the `lib.teams` list.

            This PR can be merged without taking any further action.
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@
/lib/asserts.nix            @infinisil @hsjobeki @Profpatsch
/lib/path/*                 @infinisil @hsjobeki
/lib/fileset                @infinisil @hsjobeki
/maintainers/github-teams.json @infinisil
/maintainers/computed-team-list.nix @infinisil
## Standard environment–related libraries
/lib/customisation.nix      @alyssais @NixOS/stdenv
/lib/derivations.nix        @alyssais @NixOS/stdenv
+82 −0
Original line number Diff line number Diff line
const excludeTeams = [
  /^voters.*$/,
  /^nixpkgs-maintainers$/,
  /^nixpkgs-committers$/,
]

module.exports = async ({ github, context, core, outFile }) => {
  const withRateLimit = require('./withRateLimit.js')
  const { writeFileSync } = require('node:fs')
  const result = {}
  await withRateLimit({ github, core }, async (_stats) => {
    /// Turn an Array of users into an Object, mapping user.login -> user.id
    function makeUserSet(users) {
      // Sort in-place and build result by mutation
      users.sort((a, b) => (a.login > b.login ? 1 : -1))

      return users.reduce((acc, user) => {
        acc[user.login] = user.id
        return acc
      }, {})
    }

    /// Process a list of teams and append to the result variable
    async function processTeams(teams) {
      for (const team of teams) {
        core.notice(`Processing team ${team.slug}`)
        if (!excludeTeams.some((regex) => team.slug.match(regex))) {
          const members = makeUserSet(
            await github.paginate(github.rest.teams.listMembersInOrg, {
              org: context.repo.owner,
              team_slug: team.slug,
              role: 'member',
            }),
          )
          const maintainers = makeUserSet(
            await github.paginate(github.rest.teams.listMembersInOrg, {
              org: context.repo.owner,
              team_slug: team.slug,
              role: 'maintainer',
            }),
          )
          result[team.slug] = {
            description: team.description,
            id: team.id,
            maintainers,
            members,
            name: team.name,
          }
        }
        await processTeams(
          await github.paginate(github.rest.teams.listChildInOrg, {
            org: context.repo.owner,
            team_slug: team.slug,
          }),
        )
      }
    }

    const teams = await github.paginate(github.rest.repos.listTeams, {
      owner: context.repo.owner,
      repo: context.repo.repo,
    })

    await processTeams(teams)
  })

  // Sort the teams by team name
  const sorted = Object.keys(result)
    .sort()
    .reduce((acc, key) => {
      acc[key] = result[key]
      return acc
    }, {})

  const json = `${JSON.stringify(sorted, null, 2)}\n`

  if (outFile) {
    writeFileSync(outFile, json)
  } else {
    console.log(json)
  }
}
+12 −0
Original line number Diff line number Diff line
@@ -83,4 +83,16 @@ program
    }
  })

program
  .command('get-teams')
  .description('Fetch the list of teams with GitHub and output it to a file')
  .argument('<owner>', 'Owner of the GitHub repository to label (Example: NixOS)')
  .argument('<repo>', 'Name of the GitHub repository to label (Example: nixpkgs)')
  .argument('[outFile]', 'Path to the output file (Example: github-teams.json). If not set, prints to stdout')
  .action(async (owner, repo, outFile, options) => {
    const getTeams = (await import('./get-teams.js')).default
    // TODO: Refactor this file so we don't need to pass a PR
    await run(getTeams, owner, repo, undefined, { ...options, outFile })
  })

await program.parse()
+1 −1
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ let
      customisation = callLibs ./customisation.nix;
      derivations = callLibs ./derivations.nix;
      maintainers = import ../maintainers/maintainer-list.nix;
      teams = callLibs ../maintainers/team-list.nix;
      teams = callLibs ../maintainers/computed-team-list.nix;
      meta = callLibs ./meta.nix;
      versions = callLibs ./versions.nix;

Loading