Skip to content

Commit 9388f88

Browse files
committed
codebase
1 parent 4c903c4 commit 9388f88

6 files changed

Lines changed: 255 additions & 0 deletions

File tree

index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { getGithubUsersFromGoogle } from './src/google'
2+
import {
3+
getGithubUsersFromGithub,
4+
addUsersToGitHubOrg,
5+
removeUsersToGitHubOrg
6+
} from './src/github'
7+
8+
export async function run () {
9+
const googleUsers = await getGithubUsersFromGoogle()
10+
console.log(`Users from google: ${Array.from(googleUsers).join(', ')}`)
11+
12+
const gitHubUsers = await getGithubUsersFromGithub()
13+
console.log(`Users from github: ${Array.from(gitHubUsers).join(', ')}`)
14+
15+
const usersNotInGithub = new Set(
16+
Array.from(googleUsers).filter(x => !gitHubUsers.has(x))
17+
)
18+
19+
const usersNotInGoogle = new Set(
20+
Array.from(gitHubUsers).filter(x => !googleUsers.has(x))
21+
)
22+
if (usersNotInGithub.size > 0) {
23+
console.log(
24+
`Users not in github: ${Array.from(usersNotInGithub).join(', ')}`
25+
)
26+
if (process.env.ADD_USERS.toLowerCase() === 'true')
27+
await addUsersToGitHubOrg(usersNotInGithub)
28+
}
29+
30+
if (usersNotInGoogle.size > 0) {
31+
console.log(
32+
`Users not in google: ${Array.from(usersNotInGoogle).join(', ')}`
33+
)
34+
if (process.env.REMOVE_USERS.toLowerCase() === 'true')
35+
await removeUsersToGitHubOrg(usersNotInGoogle)
36+
}
37+
38+
if (usersNotInGoogle.size > 0 || usersNotInGithub.size > 0)
39+
process.exit(parseInt(process.env.EXIT_CODE_ON_MISMATCH) ?? 0)
40+
}
41+
42+
if (require.main === module) run()

src/github.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { createAppAuth } from '@octokit/auth-app'
2+
import { Octokit } from '@octokit/rest'
3+
const ignoredUsers = process.env.IGNORED_USERS.toLowerCase().split(',')
4+
5+
const octokit = getAuthenticatedOctokit()
6+
7+
export function getAuthenticatedOctokit () {
8+
return new Octokit({
9+
authStrategy: createAppAuth,
10+
auth: {
11+
appId: process.env.GITHUB_APP_ID,
12+
privateKey: Buffer.from(
13+
process.env.GITHUB_PRIVATE_KEY,
14+
'base64'
15+
).toString('utf-8'),
16+
installationId: process.env.GITHUB_INSTALLATION_ID
17+
}
18+
})
19+
}
20+
21+
export async function getGithubUsersFromGithub () {
22+
const members = await octokit.paginate(octokit.orgs.listMembers, {
23+
org: process.env.GITHUB_ORG
24+
})
25+
26+
const pendingInvites = await octokit.paginate(
27+
octokit.orgs.listPendingInvitations,
28+
{
29+
org: process.env.GITHUB_ORG
30+
}
31+
)
32+
const pendingGithubAccounts = formatUserList(pendingInvites)
33+
34+
const githubAccounts = formatUserList(members)
35+
36+
if (pendingGithubAccounts.size > 0)
37+
console.log(
38+
`Outstanding GitHub invites for ${Array.from(pendingGithubAccounts).join(
39+
', '
40+
)}`
41+
)
42+
43+
return new Set([...githubAccounts, ...pendingGithubAccounts])
44+
}
45+
46+
export function formatUserList (users): Set<string> {
47+
return new Set(users.map(user => user.login.toLowerCase()))
48+
}
49+
50+
export async function getUserIdFromUsername (username: string) {
51+
console.log(`Looking up user ${username}`)
52+
let user
53+
try {
54+
user = await octokit.users.getByUsername({ username })
55+
} catch (error) {
56+
throw `Unable to find user id for ${username}`
57+
}
58+
console.log(`User ${username} userid: ${user.data.id}`)
59+
return user.data.id
60+
}
61+
62+
export async function addUsersToGitHubOrg (users: Set<string>) {
63+
for (const user of users) {
64+
await addUserToGitHubOrg(user)
65+
}
66+
}
67+
68+
export async function addUserToGitHubOrg (user: string) {
69+
if (ignoredUsers.includes(user.toLowerCase())) {
70+
console.log(`Ignoring add for ${user}`)
71+
return false
72+
}
73+
const userId = await getUserIdFromUsername(user)
74+
console.log(`Inviting ${user} (${userId} to ${process.env.GITHUB_ORG})`)
75+
await octokit.orgs.createInvitation({
76+
org: process.env.GITHUB_ORG,
77+
invitee_id: userId
78+
})
79+
console.log(
80+
`Invitation sent to ${user} (${userId} to ${process.env.GITHUB_ORG})`
81+
)
82+
}
83+
84+
export async function removeUsersToGitHubOrg (users: Set<string>) {
85+
for (const user of users) {
86+
await removeUserToGitHubOrg(user)
87+
}
88+
}
89+
90+
export async function removeUserToGitHubOrg (user: string) {
91+
if (ignoredUsers.includes(user.toLowerCase())) {
92+
console.log(`Ignoring remove for ${user}`)
93+
return false
94+
}
95+
console.log(`Removing user/invitation ${user} from ${process.env.GITHUB_ORG}`)
96+
return octokit.orgs.removeMembershipForUser({
97+
org: process.env.GITHUB_ORG,
98+
username: user
99+
})
100+
}

src/google.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const { google } = require('googleapis')
2+
3+
const privatekey = JSON.parse(
4+
Buffer.from(process.env.GOOGLE_CREDENTIALS, 'base64').toString('utf-8')
5+
)
6+
7+
export const googleAuth = async () => {
8+
const jwtClient = new google.auth.JWT(
9+
privatekey.client_email,
10+
null,
11+
privatekey.private_key,
12+
['https://www.googleapis.com/auth/admin.directory.user.readonly'],
13+
process.env.GOOGLE_EMAIL_ADDRESS
14+
)
15+
await jwtClient.authorize()
16+
return jwtClient
17+
}
18+
19+
export async function getAdminService () {
20+
return google.admin({
21+
version: 'directory_v1',
22+
auth: await googleAuth()
23+
})
24+
}
25+
26+
export async function getGithubUsersFromGoogle () {
27+
const service = await getAdminService()
28+
29+
const userList: {
30+
data: { users: Array<userResponseEntry> }
31+
} = await service.users.list({
32+
customer: 'my_customer',
33+
maxResults: 250,
34+
projection: 'custom',
35+
fields: 'users(customSchemas/Accounts/github(value))',
36+
customFieldMask: 'Accounts'
37+
})
38+
39+
const githubAccounts = formatUserList(userList.data.users)
40+
return githubAccounts
41+
}
42+
43+
interface userResponseEntry {
44+
customSchemas: { Accounts: { github: Array<{ value: string }> } }
45+
}
46+
47+
export function formatUserList (users: Array<userResponseEntry>): Set<string> {
48+
return new Set(
49+
users
50+
.map(user =>
51+
user?.customSchemas?.Accounts?.github.map(account =>
52+
account.value.toLowerCase()
53+
)
54+
)
55+
.flat()
56+
.filter(Boolean)
57+
)
58+
}

tests/github.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as mod from '../src/github'
2+
describe('github integration', () => {
3+
it.todo('getAuthenticatedOctokit')
4+
it.todo('getGithubUsersFromGithub')
5+
it.todo('getUserIdFromUsername')
6+
it.todo('addUsersToGitHubOrg')
7+
it.todo('addUserToGitHubOrg')
8+
it.todo('removeUsersToGitHubOrg')
9+
it.todo('removeUserToGitHubOrg')
10+
11+
it('formatUserList', () => {
12+
const response = [
13+
{ login: 'chrisns' },
14+
{ login: 'chrisns' },
15+
{ login: 'foo' }
16+
]
17+
18+
return expect(mod.formatUserList(response)).toEqual(
19+
new Set(['chrisns', 'foo'])
20+
)
21+
})
22+
})

tests/google.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as mod from '../src/google'
2+
3+
describe('google integration', () => {
4+
it.todo('googleAuth')
5+
it.skip('getAdminService', () => {
6+
const result = mod.getAdminService()
7+
return expect(result).resolves.toBe({})
8+
})
9+
10+
it.todo('getGithubUsersFromGoogle')
11+
12+
it('formatUserList', () => {
13+
const response = [
14+
{ customSchemas: { Accounts: { github: [{ value: 'chrisns' }] } } },
15+
{
16+
customSchemas: {
17+
Accounts: { github: [{ value: 'Foo' }, , { value: 'tar' }] }
18+
}
19+
},
20+
{
21+
customSchemas: {
22+
Accounts: { github: [{ value: 'foo' }, { value: 'bar' }] }
23+
}
24+
}
25+
]
26+
return expect(mod.formatUserList(response)).toEqual(
27+
new Set(['chrisns', 'foo', 'bar', 'tar'])
28+
)
29+
})
30+
})

tests/index.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
describe('index', () => {
2+
it.todo('run')
3+
})

0 commit comments

Comments
 (0)