-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created list-team-members GitHub action (#1)
Created GitHub action to list all the members of a team. I created this action with the intention of combine it with some of ours actions. Some action don't require to have access to the organization only for the sake of having the team members, this action will allow us to fine tune the steps. I created a basic template in this repository before populating it.
- Loading branch information
Showing
4 changed files
with
311 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
name: Publish package to GitHub Packages | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
env: | ||
IMAGE_NAME: action | ||
|
||
jobs: | ||
test-image: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/[email protected] | ||
- name: Check that the image builds | ||
run: docker build . --file Dockerfile | ||
|
||
test-versions: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/[email protected] | ||
- name: Extract package.json version | ||
id: package_version | ||
run: echo "VERSION=$(jq '.version' -r package.json)" >> $GITHUB_OUTPUT | ||
- name: Extract action.yml version | ||
uses: mikefarah/yq@master | ||
id: action_image | ||
with: | ||
cmd: yq '.runs.image' 'action.yml' | ||
- name: Parse action.yml version | ||
id: action_version | ||
run: | | ||
echo "IMAGE_VERSION=$(echo $IMAGE_URL | cut -d: -f3)" >> $GITHUB_OUTPUT | ||
env: | ||
IMAGE_URL: ${{ steps.action_image.outputs.result }} | ||
- name: Compare versions | ||
run: | | ||
echo "Verifying that $IMAGE_VERSION from action.yml is the same as $PACKAGE_VERSION from package.json" | ||
[[ $IMAGE_VERSION == $PACKAGE_VERSION ]] | ||
env: | ||
IMAGE_VERSION: ${{ steps.action_version.outputs.IMAGE_VERSION }} | ||
PACKAGE_VERSION: ${{ steps.package_version.outputs.VERSION }} | ||
|
||
tag: | ||
if: github.event_name == 'push' | ||
needs: [test-image, test-versions] | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: write | ||
outputs: | ||
tagcreated: ${{ steps.autotag.outputs.tagcreated }} | ||
tagname: ${{ steps.autotag.outputs.tagname }} | ||
steps: | ||
- uses: actions/[email protected] | ||
with: | ||
fetch-depth: 0 | ||
- uses: butlerlogic/action-autotag@stable | ||
id: autotag | ||
with: | ||
head_branch: master | ||
tag_prefix: "v" | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Changelog | ||
uses: Bullrich/[email protected] | ||
id: Changelog | ||
env: | ||
REPO: ${{ github.repository }} | ||
- name: Create Release | ||
if: steps.autotag.outputs.tagname != '' | ||
uses: actions/create-release@latest | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
with: | ||
tag_name: ${{ steps.autotag.outputs.tagname }} | ||
release_name: Release ${{ steps.autotag.outputs.tagname }} | ||
body: | | ||
${{ steps.Changelog.outputs.changelog }} | ||
publish: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
packages: write | ||
needs: [tag] | ||
if: needs.tag.outputs.tagname != '' | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Build image | ||
run: docker build . --file Dockerfile --tag $IMAGE_NAME | ||
- name: Log into registry | ||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin | ||
- name: Push image | ||
run: | | ||
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME | ||
# Change all uppercase to lowercase | ||
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') | ||
# Strip git ref prefix from version | ||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') | ||
# Strip "v" prefix from tag name | ||
[[ ! -z $TAG ]] && VERSION=$(echo $TAG | sed -e 's/^v//') | ||
# Use Docker `latest` tag convention | ||
[ "$VERSION" == "main" ] && VERSION=latest | ||
echo IMAGE_ID=$IMAGE_ID | ||
echo VERSION=$VERSION | ||
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION | ||
docker push $IMAGE_ID:$VERSION | ||
env: | ||
TAG: ${{ needs.tag.outputs.tagname }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,133 @@ | ||
# list-team-members | ||
Lists all the members of a GitHub Organization's team | ||
|
||
# List team members | ||
GitHub action to lists all the members of an Organization's team. | ||
|
||
|
||
[![Publish](https://github.com/paritytech/list-team-members/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/paritytech/list-team-members/actions/workflows/publish.yml) | ||
|
||
## Why? | ||
|
||
This action is intended to have its output used by other action. It provides all the users belonging to a team in an organization. | ||
|
||
By being agnostic on the result, users can use the output to generate a custom message on their favorite system. | ||
|
||
Needed for some GitHub actions, for example [paritytech/stale-issues-finder](https://github.com/paritytech/stale-issues-finder) | ||
|
||
## Example usage | ||
|
||
You need to create a file in `.github/workflows` and add the following: | ||
|
||
```yml | ||
name: Find team members | ||
|
||
on: | ||
workflow_dispatch: | ||
|
||
jobs: | ||
get-team: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Fetch team data | ||
# We add the id to access to this step outputs | ||
id: teams | ||
uses: paritytech/list-team-members@main | ||
with: | ||
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
team: developers | ||
# optional, in case that it searches on a different organization | ||
organization: paritytech | ||
# example showing how to use the content | ||
- name: Show data | ||
run: | | ||
echo "The users are $USERNAMES" | ||
echo "Data: $DATA" | ||
env: | ||
USERNAMES: ${{ steps.teams.outputs.usernames }}" | ||
# a json object | ||
DATA: ${{ steps.teams.outputs.team-data }}" | ||
``` | ||
This will produce the following message: | ||
> The users are Username1,Username2,Username3 | ||
> | ||
> Data : [{"username" : "Username1","url" : "https : //github.com/Username1","avatar" : "https : //avatars.githubusercontent.com/u/etcasd?v=4"},{"username" : "Username2","url" : "https : //github.com/Username2","avatar" : "https : //avatars.githubusercontent.com/u/fwedfads?v=4"},{"username" : "Username3","url" : "https : //github.com/Username3","avatar" : "https : //avatars.githubusercontent.com/u/sdffsfdsf?v=4"}] | ||
### Inputs | ||
You can find all the inputs in [the action file](./action.yml) but let's walk through each one of them: | ||
- `ACCESS_TOKEN`: Personal Access Token to access the organization teams. | ||
- **required** | ||
- Requires the following scope | ||
- [x] Repo (_Full control of private repositories_) | ||
- If using a GitHub app, read the [Using a GitHub app instead of a PAT](#using-a-github-app-instead-of-a-pat) section | ||
- `organization`: name of the organization/user where the team is. Example: `https://github.com/OWNER-NAME/list-team-members` | ||
- **defaults** to the organization where this action is ran. | ||
- Make sure that the `ACCESS_TOKEN` has access to that organization. | ||
- `team`: Name of the team. | ||
- **required** | ||
- Be sure to get the _team slug_. You can find the teams in https://github.com/orgs/ORG-NAME/teams and copy the name in the URL. | ||
- For example, if the team name is _CI & CD_ but the url is https://github.com/orgs/ORG-NAME/teams/ci-cd, then the _team slug_ is `ci-cd`. | ||
|
||
### Outputs | ||
Outputs are needed for your chained actions. If you want to use this information, remember to set an `id` field in the step so you can access it. | ||
You can find all the outputs in [the action file](./action.yml) but let's walk through each one of them: | ||
- `usernames`: all of the usernames combined by a comma. | ||
- Intended to be used by [`usernames.split(",");`](https://www.w3schools.com/jsref/jsref_split.asp) | ||
- `data`: A json array with the curated data of the team members. | ||
|
||
#### JSON Data example | ||
```json | ||
[ | ||
{ | ||
"username": "Username1", | ||
"url": "https : //github.com/Username1", | ||
"avatar": "https : //avatars.githubusercontent.com/u/etcasd?v=4" | ||
}, | ||
{ | ||
"username": "Username2", | ||
"url": "https : //github.com/Username2", | ||
"avatar": "https : //avatars.githubusercontent.com/u/fwedfads?v=4" | ||
}, | ||
{ | ||
"username": "Username3", | ||
"url": "https : //github.com/Username3", | ||
"avatar": "https : //avatars.githubusercontent.com/u/sdffsfdsf?v=4" | ||
} | ||
] | ||
``` | ||
|
||
### Using a GitHub app instead of a PAT | ||
In some cases, specially in big organizations, it is more organized to use a GitHub app to authenticate, as it allows us to give it permissions per repository and we can fine-grain them even better. If you wish to do that, you need to create a GitHub app with the following permissions: | ||
- Organization permissions: | ||
- Members | ||
- [x] Read | ||
|
||
Because this project is intended to be used with a token we need to do an extra step to generate one from the GitHub app: | ||
- After you create the app, copy the *App ID* and the *private key* and set them as secrets. | ||
- Then you need to modify the workflow file to have an extra step: | ||
```yml | ||
steps: | ||
- name: Generate token | ||
id: generate_token | ||
uses: tibdex/github-app-token@v1 | ||
with: | ||
app_id: ${{ secrets.APP_ID }} | ||
private_key: ${{ secrets.PRIVATE_KEY }} | ||
- name: Fetch team members | ||
id: stale | ||
uses: paritytech/list-team-members@main | ||
with: | ||
team: developers | ||
# The previous step generates a token which is used as the input for this action | ||
ACCESS_TOKEN: ${{ steps.generate_token.outputs.token }} | ||
``` | ||
|
||
## Development | ||
To work on this app, you require | ||
- `Node 18.x` | ||
- `yarn` | ||
|
||
Use `yarn install` to set up the project. | ||
|
||
`yarn build` compiles the TypeScript code to JavaScript. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: "List Team Members" | ||
description: "Lists all the members of an Organization's team" | ||
author: paritytech | ||
branding: | ||
icon: users | ||
color: organge | ||
inputs: | ||
ACCESS_TOKEN: | ||
required: true | ||
description: The token to access the repo | ||
organization: | ||
required: false | ||
description: The repository to fetch the issues from | ||
team: | ||
required: false | ||
description: The name of the org/user that owns the repository | ||
outputs: | ||
usernames: | ||
description: 'All of the usernames combined by a comma' | ||
data: | ||
description: 'A JSON object with the users data' | ||
|
||
runs: | ||
using: 'docker' | ||
image: 'docker://ghcr.io/paritytech/list-team-members/action:0.0.1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,47 @@ | ||
function greet(name: string) { | ||
console.log(`Hello ${name}!`); | ||
import { getInput, info, setFailed, setOutput } from "@actions/core"; | ||
import { context, getOctokit } from "@actions/github"; | ||
import { Context } from "@actions/github/lib/context"; | ||
import { GitHub } from "@actions/github/lib/utils"; | ||
|
||
type UserData = { | ||
username: string; | ||
url: string; | ||
avatar: string; | ||
} | ||
|
||
async function fetchTeam(octokit: InstanceType<typeof GitHub>, org: string, team: string): Promise<UserData[]> { | ||
const teamData = await octokit.rest.teams.listMembersInOrg({ | ||
org, | ||
team_slug: team, | ||
}); | ||
|
||
return teamData.data.map(user => { | ||
return { | ||
username: user.login, | ||
url: user.html_url, | ||
avatar: user.avatar_url | ||
} | ||
}); | ||
} | ||
|
||
async function runAction(ctx: Context) { | ||
const token = getInput("ACCESS_TOKEN", { required: true }); | ||
let organization = getInput("organization", { required: false }); | ||
if (!organization) { | ||
organization = ctx.repo.owner; | ||
} | ||
|
||
const team = getInput("team", { required: true }); | ||
|
||
const octokit = getOctokit(token); | ||
const teamData = await fetchTeam(octokit, organization, team); | ||
if (teamData.length > 0) { | ||
info(`Obtained data from ${teamData.length} users`); | ||
setOutput("usernames", teamData.map(({ username }) => username).join(",")); | ||
setOutput("team-data", JSON.stringify(teamData)); | ||
} else { | ||
setFailed(`No users were found when searching for the team ${team}`); | ||
} | ||
} | ||
|
||
greet("Parity"); | ||
runAction(context); |