Skip to content

Instantly share code, notes, and snippets.

@davidrleonard
Last active May 8, 2024 15:11
Show Gist options
  • Save davidrleonard/bbdfa12f984af8d3ead0841946bb4670 to your computer and use it in GitHub Desktop.
Save davidrleonard/bbdfa12f984af8d3ead0841946bb4670 to your computer and use it in GitHub Desktop.
Add a new team to all Github repos in an organization
/*
* Adds a team to all the repos in a Github organization. This is a tedious
* process in the UI. You'll need a newer version of node to run this (e.g 9+)
* because it uses async/await.
*
* Instructions:
*
* 1. Copy this file somewhere on your computer, e.g. ~/addteamrepos.js
* 2. Fill in the uppercase variables below with the right values
* 3. Run this file: `$ node ~/addteamrepos.js`
*/
const GITHUB_ORG = 'xxxxx'; /* Name of the github organization the team is under and the repos are in */
const GITHUB_ACCESS_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxx'; /* Create an access token here: https://github.com/settings/tokens */
const TEAM_ID = '11111111'; /* Github team ID, not the same as the name, get it from the API */
const TEAM_PERMISSION = 'push'; /* 'pull' or 'push' or 'admin' */
const { exec } = require('child_process');
function execPromise(command) {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (err) {
return reject(err);
}
resolve([stdout, stderr]);
});
});
}
async function fetchReposPage(org, page) {
const [response] = await execPromise(
`curl -i -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/${org}/repos?page=${page}`
);
const nextPageRe = /Link\: \<.+page\=([0-9])\>\; rel\=\"next\"/g;
const nextPageMatch = nextPageRe.exec(response);
const nextPage = nextPageMatch ? nextPageMatch[1] : null;
const repos = JSON.parse(response.slice(response.indexOf('[')));
return [repos, nextPage];
}
async function fetchRepos(org) {
let repos = [];
let page = 1;
while (page) {
let [currentRepos, nextPage] = await fetchReposPage(org, page);
repos = [...repos, ...currentRepos];
page = nextPage;
}
return repos;
}
async function addTeamToRepo(teamId, org, repo, permission) {
const [out,err] = await execPromise(
`curl -X PUT -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" -d '{"permission":"${permission}"}' https://api.github.com/teams/${teamId}/repos/${org}/${repo}`
);
console.log(`... Added team "${teamId}" to repo "${org}/${repo}" with permission "${permission}"`);
}
(async () => {
/* Fetch all repos names for org */
console.log(`Fetching repos from organization "${GITHUB_ORG}"`);
const repos = await fetchRepos(GITHUB_ORG);
const repoNames = repos.map(r => r.name);
console.log(`... Found ${repoNames.length} repos`)
/* Add team to each repo */
console.log(`Adding team "${TEAM_ID}" to ${repoNames.length} repos with permission "${TEAM_PERMISSION}"`);
for (let repo of repoNames) {
await addTeamToRepo(TEAM_ID, GITHUB_ORG, repo, TEAM_PERMISSION);
}
})();
@sethlinnpubliq
Copy link

Hello, I am having a little problem, when I run this script line 55 returns
{
"message": "Problems parsing JSON",
"documentation_url": "https://docs.github.com/rest/reference/teams#add-or-update-team-repository-permissions"
}
I'm I doing something wrong?
Regards,
Seth

@adailey14
Copy link

To get this to work for multiple pages, I had to change the regex on line 35 to have a lowercase "L" in link. I'm guessing that changed since this was written. Otherwise works great thanks!

@rmateu-pricesmart
Copy link

Thanks @adailey14 !!!

@steve-hart-lion
Copy link

This script was only fetching up to page 9 for me, so I added an asterisk to the nextPageRe regex so it looks like this now on mine and it will fetch all my repos even after page 9 now!

const nextPageRe = /link\: \<.+page\=([0-9]*)\>\; rel\=\"next\"/g;

@maciejkonka
Copy link

@adailey14 capital "L" is working for me :)

@sethlinnpubliq

-d "{\""permission\"":\""${permission}\""}" should solve it

@ets
Copy link

ets commented Mar 14, 2022

Thanks for this massive timesaver.

@david-luu-aera
Copy link

david-luu-aera commented May 20, 2022

@adailey14 Lowercase "L" fixed multiple pages for me.

@steve-hart-lion The asterisk fixed going past page 9 for me.

This script is a lifesaver. Thank you!

@ebesic-bluefield
Copy link

ebesic-bluefield commented Aug 18, 2022

Had to edit a few things to get it to work on my windows machine,

Following the API I've changed the following:

  1. Uppercase L is fine (again)
  2. Asterisk seems to work so i added that in
  3. Repo owner is now apparently a required field -> using the same as our organisation name worked, but in the case that certain repos have different owners you might want to check that; you can probably find it in repos.owner.login
  4. team id is now the team slug
  5. added logging for replies
  6. fixed the curl parsing issue when working from cmd

So here's the script with these changes that worked for me in windows using cmd:

/*
 * Adds a team to all the repos in a Github organization. This is a tedious
 * process in the UI. You'll need a newer version of node to run this (e.g 9+)
 * because it uses async/await.
 *
 * Instructions:
 *
 * 1. Copy this file somewhere on your computer, e.g. ~/addteamrepos.js
 * 2. Fill in the uppercase variables below with the right values
 * 3. Run this file: `$ node ~/addteamrepos.js`
 */

const GITHUB_ORG = 'your-organisation'; /* Name of the github organization the team is under and the repos are in */
const GITHUB_ACCESS_TOKEN = 'ghp_yourtoken'; /* Create an access token here: https://github.com/settings/tokens */
const TEAM_SLUG = 'your-team-slug'; /* GitHub team slug, similar to the name, you can get it from the API (the url when checking team) */
const TEAM_PERMISSION = 'push'; /* 'pull' or 'push' or 'admin' */
const REPO_OWNER = 'name-of-owner' /* Possibly the same as your organisation */

const { exec } = require('child_process');

function execPromise(command) {
  return new Promise((resolve, reject) => {
    exec(command, (err, stdout, stderr) => {
      if (err) {
        return reject(err);
      }
      resolve([stdout, stderr]);
    });
  });
}

async function fetchReposPage(org, page) {
  const [response] = await execPromise(
    `curl -i -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/${org}/repos?page=${page}`
  );
  const nextPageRe = /Link\: \<.+page\=([0-9]*)\>\; rel\=\"next\"/g;
  const nextPageMatch = nextPageRe.exec(response);
  const nextPage = nextPageMatch ? nextPageMatch[1] : null;
  const repos = JSON.parse(response.slice(response.indexOf('[')));
  return [repos, nextPage];
}

async function fetchRepos(org) {
  let repos = [];
  let page = 1;
  while (page) {
    let [currentRepos, nextPage] =  await fetchReposPage(org, page);
    repos = [...repos, ...currentRepos];
    page = nextPage;
  }
  return repos;
}

async function addTeamToRepo(teamSlug, org, repo, permission, owner) {
  const [out,err] = await execPromise(
    `curl -X PUT -H "Authorization: token ${GITHUB_ACCESS_TOKEN}"  https://api.github.com/orgs/${org}/teams/${teamSlug}/repos/${owner}/${repo} -d \"{\\"permission\\":\\"${permission}\\"}\"`
  );
  console.log(out);
  console.log(`...  Added team "${teamSlug}" to repo "${org}/${repo}" with permission "${permission}"`);
}

(async () => {
  /* Fetch all repos names for org */
  console.log(`Fetching repos from organization "${GITHUB_ORG}"`);
  const repos = await fetchRepos(GITHUB_ORG);
  const repoNames = repos.map(r => r.name);
  console.log(`... Found ${repoNames.length} repos`)

  /* Add team to each repo */
  console.log(`Adding team "${TEAM_SLUG}" to ${repoNames.length} repos with permission "${TEAM_PERMISSION}"`);
  for (let repo of repoNames) {
    await addTeamToRepo(TEAM_SLUG, GITHUB_ORG, repo, TEAM_PERMISSION, REPO_OWNER);
  }
})();

If you're not running in CMD you might want to change the
\"{\\"permission\\":\\"${permission}\\"}\"
back to
'{"permission":"${permission}"}'

@rakatar
Copy link

rakatar commented Sep 23, 2022

With this script I am add only public repos and not the private repos.

@garfieldgrant-ptx
Copy link

You need to setup your token to access ALL repos

@sergeisourcepoint
Copy link

thank you!

@matt-horwood-mayden
Copy link

matt-horwood-mayden commented Oct 23, 2023

Morning 👋

Could you add a note to the top on how you get the team ID?
something like run curl -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/${GITHUB_ORG}/teams#list-teams

@hmhka
Copy link

hmhka commented Jan 21, 2024

In case this is useful to anyone looking at easy ways to obtain a token

# for those using GitHub CLI
# there is an easy way to obtain a token from command line
# post initial authenticating  
# if I'm not wrong github access token prefix could differ
# -> classic token vs fine grained token 
#
# below produced with
# gh version 2.4.0+dfsg1 (2022-03-23 Ubuntu 2.4.0+dfsg1-2)
# https://github.com/cli/cli/releases/latest
token_prefix="gho." 
token=$(gh auth status --show-token 2>&1) 
GITHUB_ACCESS_TOKEN=$(echo $token | grep -o -P "$token_prefix{37}") 
# confirm token value is correct - before adding to scripts
echo $GITHUB_ACCESS_TOKEN

# with a token which relevant access to GitHub org
# curl
curl -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/infraxis/teams
# curl + jq
curl -s -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/infraxis/teams | jq -r '.[]'
# curl + jq - show id and name
curl -s -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" https://api.github.com/orgs/infraxis/teams | jq -r '.[] | "\(.id) \t \(.name)"'
# curl + jq + team name grep
curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" https://api.github.com/orgs/infraxis/teams | jq -r '.[] | "\(.id) \t \(.name)"' | grep -i foo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment