From a807139233eb729ae51dc77544cdebc33dd8a2c0 Mon Sep 17 00:00:00 2001 From: Niek Palm Date: Wed, 10 Mar 2021 15:25:44 +0100 Subject: [PATCH] fix: handle error if app is not installed (#3) * fix: handle error if app is not installed * chore(docs): small fixes * chore(ci): fix error --- .github/workflows/ci.yml | 11 ++++++----- Dockerfile | 5 ----- README.md | 40 +++++++++++++++++++++++----------------- dist/index.js | 29 +++++++++++++++++------------ src/auth.test.ts | 31 ++++++++++++++++++++++++++----- src/auth.ts | 33 ++++++++++++++++++--------------- 6 files changed, 90 insertions(+), 59 deletions(-) delete mode 100644 Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be60d33..a5ba146 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,6 @@ name: 'CI' on: push: - branches: - - main - - develop pull_request: jobs: @@ -34,7 +31,7 @@ jobs: file_pattern: dist/* release: - if: github.event_name != 'pull_request' && contains('refs/heads/develop, refs/heads/main', github.ref) + if: github.event_name != 'pull_request' needs: build runs-on: ubuntu-latest steps: @@ -50,10 +47,14 @@ jobs: auth_type: installation org: philips-software + - name: Extract branch name + run: echo ::set-output name=short_ref::${GITHUB_REF#refs/*/} + id: branch + - name: Dry run release env: GITHUB_TOKEN: ${{ steps.app.outputs.token }} - run: yarn && yarn run release -d + run: yarn && yarn run release -d -b ${{ steps.branch.outputs.short_ref }} - name: Release if: github.event_name != 'pull_request' && contains('refs/heads/main', github.ref) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 492d121..0000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM node:14-slim -COPY dist /action - -ENTRYPOINT ["node", "/action/index.js"] - diff --git a/README.md b/README.md index 5d086ce..5ed3c67 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,29 @@ A GitHub action that obtains a token for an app authorization which can used ins Before the action can be used the APP has to be installed on the subject repository or in the organization scope. -## Action input parameters -| Parameter | Description | required | default | -| ---------------------- | :------------------------------------------------------------ | -------- | ------- | -| app_id | Application id | true | | -| app_base64_private_key | Application SSH private key as base64 | true | | -| auth_type | Authorization type | false | app | -| org | Name of the org, if not provided will be read from the event. | false | | +## Inputs -## Action output parameters +| parameter | description | required | default | +| ---------------------- | ------------------------------------------------------------- | -------- | ------- | +| app_id | Application ID | `true` | | +| app_base64_private_key | Application SSH private key as base64 | `true` | | +| auth_type | Authorization type, either app or installation | `false` | app | +| org | Name of the org, if not provided will be read from the event. | `false` | | -| Parameter | Description | -| --------- | :------------------------- | -| token | GitHub authorization token | -### Example +## Outputs + +| parameter | description | +| --------- | ----------------- | +| token | Application token | + + +## Runs + +This action is an `node12` action. + +## Usage Below an example snippet how to use the action. @@ -46,12 +53,11 @@ Standard commands such as lint, test and build are available via yarn. Check [pa ### Test locally -To run the action core function locally a simple wrapper is provided in [src/local.ts]. To run the wrapper ensure you have set the environment variables `INPUT_APP_REPO`, `APP_ID` and `APP_BASE64_PRIVATE_KEY`. `APP_REPO` takes a `philips-internal` repository name. Next run `yarn run watch`. - +Running locally requires you hav have an app in your org that is installed on one or more repositories. To run the local ensure you have set the environment variables: -const appType = core.getInput('auth_type', { required: true }) as 'installation' | 'app'; -const appId = Number(core.getInput('app_id', { required: true })); -const appBase64PrivateKey = core.getInput('app_base64_private_key +- `INPUT_AUTH_TYPE` : `app` or `token` +- `INPUT_APP_ID` and `INPUT_APP_BASE64_PRIVATE_KEY`: The app id and app ssh key. +- `INPUT_ORG`: The org in which the app is installed. **Example:** diff --git a/dist/index.js b/dist/index.js index 24da623..6b201c1 100644 --- a/dist/index.js +++ b/dist/index.js @@ -15282,18 +15282,23 @@ exports.getAppToken = getAppToken; const getAppInstallationToken = (privateKey, appId, org) => __awaiter(void 0, void 0, void 0, function* () { const appToken = yield exports.getAppToken(privateKey, appId); const octokit = new rest_1.Octokit({ auth: appToken }); - const installationId = yield octokit.apps.getOrgInstallation({ - org, - }); - const authAuthInstallation = auth_app_1.createAppAuth({ - appId, - privateKey, - installationId: installationId.data.id, - }); - const auth = yield authAuthInstallation({ - type: 'installation', - }); - return auth.token; + try { + const installationId = yield octokit.apps.getOrgInstallation({ + org, + }); + const authAuthInstallation = auth_app_1.createAppAuth({ + appId, + privateKey, + installationId: installationId.data.id, + }); + const auth = yield authAuthInstallation({ + type: 'installation', + }); + return auth.token; + } + catch (e) { + throw new Error(`Cannot find installation for app with id: ${appId}. Did you installed your app in a repo?`); + } }); exports.getAppInstallationToken = getAppInstallationToken; const getToken = (parameters) => __awaiter(void 0, void 0, void 0, function* () { diff --git a/src/auth.test.ts b/src/auth.test.ts index 0e18b6f..62d76c1 100644 --- a/src/auth.test.ts +++ b/src/auth.test.ts @@ -14,9 +14,11 @@ const defaultParameters: Parameters = { base64PrivateKey: testBase64PrivateKey, }; -function mockGetOrgInstallation(id: number): void { - nock('https://api.github.com').persist().get(`/orgs/${testOrg}/installation`).reply(200, { id }); +function mockGetOrgInstallation(id: number, org: string): void { + const responseCode = id < 0 ? 403 : 200; + nock('https://api.github.com').persist().get(`/orgs/${org}/installation`).reply(responseCode, { id }); } + function mockAppInstallationToken(id: string): void { nock('https://api.github.com') .persist() @@ -32,7 +34,7 @@ describe('Auth type installation', () => { beforeEach(() => { jest.restoreAllMocks(); jest.clearAllMocks(); - mockGetOrgInstallation(47); + mockGetOrgInstallation(47, testOrg); mockAppInstallationToken('47'); }); @@ -53,11 +55,30 @@ describe('Auth type installation', () => { }); }); -describe('Auth type app', () => { +describe('App not installed.', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + jest.resetAllMocks(); + mockGetOrgInstallation(-1, 'org-without-apps'); + mockAppInstallationToken('-1'); + }); + + test('Should throw exception for app that is not installed..', async () => { + await expect( + getToken({ + ...defaultParameters, + org: 'org-without-apps', + }), + ).rejects.toThrow(); + }); +}); + +describe('Auth type 222', () => { beforeEach(() => { jest.restoreAllMocks(); jest.clearAllMocks(); - mockGetOrgInstallation(47); + mockGetOrgInstallation(47, testOrg); }); test('Should throw exception for invalid private key.', async () => { diff --git a/src/auth.ts b/src/auth.ts index a897b6e..1cac34f 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -24,21 +24,24 @@ export const getAppInstallationToken = async (privateKey: string, appId: number, const appToken = await getAppToken(privateKey, appId); const octokit = new Octokit({ auth: appToken }); - const installationId = await octokit.apps.getOrgInstallation({ - org, - }); - - const authAuthInstallation = createAppAuth({ - appId, - privateKey, - installationId: installationId.data.id, - }); - - const auth = await authAuthInstallation({ - type: 'installation', - }); - - return auth.token; + try { + const installationId = await octokit.apps.getOrgInstallation({ + org, + }); + const authAuthInstallation = createAppAuth({ + appId, + privateKey, + installationId: installationId.data.id, + }); + + const auth = await authAuthInstallation({ + type: 'installation', + }); + + return auth.token; + } catch (e) { + throw new Error(`Cannot find installation for app with id: ${appId}. Did you installed your app in a repo?`); + } }; export const getToken = async (parameters: Parameters): Promise => {