Skip to content

Commit

Permalink
Organization support (#5)
Browse files Browse the repository at this point in the history
* Adding support for getting a token if the application is installed on an organization
* Adding more workflow tests for organization applications
* Adding better tracking on execution when retrieving the token due to two major execution paths
* Updating dependencies
  • Loading branch information
peter-murray authored Aug 11, 2021
1 parent 4a38172 commit 33bc2a1
Show file tree
Hide file tree
Showing 12 changed files with 1,662 additions and 147 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/test_failure_organization_not_installed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Tests this action failure by the application not being installed on the organization

name: Test Failure - organization - not installed

on:
push:
workflow_dispatch:

jobs:
test_failure:
runs-on: ubuntu-latest
continue-on-error: true

outputs:
action_step_outcome: ${{ steps.use_action.outcome }}
action_step_conclusion: ${{ steps.use_action.conclusion }}

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Use action
id: use_action
uses: ./
with:
application_id: ${{ secrets.APPLICATION_ID_NOT_INSTALLED }}
application_private_key: ${{ secrets.APPLICATION_PRIVATE_KEY_NOT_INSTALLED }}
organization: devopsventures

validate_results:
needs: test_failure

runs-on: ubuntu-latest

steps:
- name: Validate failure
if: needs.test_failure.outputs.action_step_outcome != 'failure'
run: |
echo "Outcome: ${{ needs.test_failure.outputs.action_step_outcome }}"
echo "Conclusion: ${{ needs.test_failure.outputs.action_step_conclusion }}"
echo "Outcome should have been failure for an application that is not installed."
exit 1
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Tests this action failure by the application not being installed on the repository

name: Test Failure - not installed
name: Test Failure - repository - not installed

on:
push:
workflow_dispatch:

jobs:
test_failure:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Tests this action success by the application already being installed on the repository

name: Test Success - installed action
name: Test Success - repository - installed

on:
push:
workflow_dispatch:

jobs:
test:
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/test_repository_installed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Tests this action success by the application already being installed on an organization

name: Test Success - organization - installed

on:
push:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Use action
id: use_action
uses: ./
with:
application_id: ${{ secrets.APPLICATION_ID }}
application_private_key: ${{ secrets.APPLICATION_PRIVATE_KEY }}
organization: octodemo

- name: Use token to read details
uses: actions/github-script@v2
with:
github-token: ${{ steps.use_action.outputs.token }}
script: |
const repo = await github.repos.get({owner: 'octodemo', repo: 'demo-bootstrap'});
console.log(JSON.stringify(repo, null, 2));
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ inputs:
description: GitHub Application ID value.
required: true

organization:
description: The GitHub Organization to get the application installation for, if not specified will use the current repository instead
required: false

outputs:
token:
description: A valid token representing the Application that can be used to access what the Application has been scoped to access.
Expand Down
6 changes: 3 additions & 3 deletions dist/index.js

Large diffs are not rendered by default.

40 changes: 33 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,48 @@ async function run() {
}

if (app) {
core.info(`Found GitHub Application: ${app.name}`);

try {
const repository = process.env['GITHUB_REPOSITORY']
const userSpecifiedOrganization = core.getInput('organization')
, repository = process.env['GITHUB_REPOSITORY']
, repoParts = repository.split('/')
;

const installation = await app.getRepositoryInstallation(repoParts[0], repoParts[1]);
if (installation && installation.id) {
const accessToken = await app.getInstallationAccessToken(installation.id);


let installationId;

if (userSpecifiedOrganization) {
core.info(`Obtaining application installation for organization: ${userSpecifiedOrganization}`);

// use the organization specified to get the installation
const installation = await app.getOrganizationInstallation(userSpecifiedOrganization);
if (installation && installation.id) {
installationId = installation.id;
} else {
fail(null, `GitHub Application is not installed on the specified organization: ${userSpecifiedOrganization}`);
}
} else {
core.info(`Obtaining application installation for repository: ${repository}`);

// fallback to getting a repository installation
const installation = await app.getRepositoryInstallation(repoParts[0], repoParts[1]);
if (installation && installation.id) {
installationId = installation.id;
} else {
fail(null, `GitHub Application is not installed on repository: ${repository}`);
}
}

if (installationId) {
const accessToken = await app.getInstallationAccessToken(installationId);

// Register the secret to mask it in the output
core.setSecret(accessToken.token);
core.setOutput('token', accessToken.token);
core.info(JSON.stringify(accessToken));
core.info(`Successfully generated an access token for application.`)
} else {
fail(null, `GitHub Application is not installed on repository: ${repository}`);
fail('No installation of the specified GitHub application was abel to be retrieved');
}
} catch (err) {
fail(err);
Expand Down
21 changes: 19 additions & 2 deletions lib/github-application.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class GitHubApplication {
get client() {
const client = this._client;
if (client === null) {
throw new Error('Application has not been initialized correctly, call connect() to connect to GitHub.com first.');
throw new Error('Application has not been initialized correctly, call connect() to connect to GitHub first.');
}
return client;
}
Expand All @@ -76,6 +76,10 @@ class GitHubApplication {
return this._config.id;
}

get name() {
return this._metadata.name;
}

getApplicationInstallations() {
return this.client.request('GET /app/installations', {
mediaType: {
Expand All @@ -94,7 +98,7 @@ class GitHubApplication {
//TODO can get other types of app installations too at org and enterprise

getRepositoryInstallation(owner, repo) {
return this.client.apps.getRepoInstallation({
return this.client.rest.apps.getRepoInstallation({
owner: owner,
repo: repo
}).catch(err => {
Expand All @@ -107,6 +111,19 @@ class GitHubApplication {
});
}

getOrganizationInstallation(org) {
return this.client.rest.apps.getOrgInstallation({
org: org
}).catch(err => {
throw new Error(`Failed to resolve installation of application on organization ${org}; ${err.message}`);
}).then(resp => {
if (resp.status === 200) {
return resp.data;
}
throw new Error(`Unexpected status code ${resp.status}; ${resp.data}`);
});
}

getInstallationAccessToken(installationId) {
if (!installationId) {
throw new Error('GitHub Application installation id must be provided');
Expand Down
12 changes: 11 additions & 1 deletion lib/github-application.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ describe('GitHubApplication', () => {
expect(data).to.have.property('permissions');
});

it('should be able to get installation for an organization', async () => {
const data = await app.getOrganizationInstallation(
testValues.getTestOrganization(APPLICATION_NAME)
);

expect(data).to.have.property('id');
expect(data).to.have.property('permissions');
});


it('should be able to get access token for a repository installation', async () => {
const repoInstall = await app.getRepositoryInstallation(
testValues.getTestRepositoryOwner(APPLICATION_NAME),
Expand All @@ -117,7 +127,7 @@ describe('GitHubApplication', () => {
const client = new github.getOctokit(accessToken.token)
, repoName = testValues.getTestRepository(APPLICATION_NAME)
, ownerName = testValues.getTestRepositoryOwner(APPLICATION_NAME)
, repo = await client.repos.get({
, repo = await client.rest.repos.get({
owner: ownerName,
repo: repoName,
});
Expand Down
Loading

0 comments on commit 33bc2a1

Please sign in to comment.