5

I am creating a build pipeline with Github which will be create a check-run on every pull-request to analyse the performance of my application. I have created and installed the Github app to my repo and generated the private key. I need to perform Authenticating as an installation to get an access-token.

But as per the docs to get an access-token for installation, first i have to get list of installations of that app and have to find the specific installation from that list. But i am clueless how to identify the specific installation_id of app for pull request event raised on particular repository that installed my app.

I don't know what i am missing.

  • 1
    Webhooks that are sent for a GitHub App contain this in the payload. https://developer.github.com/apps/quickstart-guides/setting-up-your-development-environment/#authenticating-as-an-installation – osowskit Sep 17 '19 at 02:54
  • But how does the payload contain installation id. Based on what condition, specific installation been passed as payload. I don't know ruby. i am writing in nodeJs – Saravanan Ramupillai Sep 17 '19 at 03:13
  • 1
    The payload is JSON with a top-level key of `installation` and has an `id`. I highly recommend this framework in node that should save you from doing this yourself https://probot.github.io/ – osowskit Sep 17 '19 at 13:17

3 Answers3

6

If your CI/CD doesn't work with GitHub's webhooks (because of the firewall, for instance), you can do something like this:

#!/bin/bash

HEADER=$( echo -n {\"alg\":\"RS256\"} | base64 | tr -d '=' )
PAYLOAD=$( echo -n \{\"iat\":$(date -u '+%s'),\"exp\":$(( $( date -u '+%s' ) + 600 )),\"iss\":$GITHUB_APP_ID\} | base64 | tr -d '\n' | tr -d '=' | tr / _ | tr + - )
SIGNATURE=$( echo -n "$HEADER.$PAYLOAD" | openssl dgst -sha256 -sign ./private_key -binary | openssl base64 | tr -d '\n' | tr -d '=' | tr / _ | tr + - )
TOKEN=$HEADER.$PAYLOAD.$SIGNATURE

INSTALLATION_ID=$( curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github.v3+json" https://api.github.com/app/installations | jq .[0].id )
INSTALLATION_TOKEN=$( curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github.v3+json" -d '{"permissions":{ "checks":"write"}}' https://api.github.com/app/installations/$INSTALLATION_ID/access_tokens | jq .token | tr -d '"' )
echo $INSTALLATION_TOKEN

Or in Python:

# Implementation of: https://docs.github.com/en/developers/apps/authenticating-with-github-apps#authenticating-as-a-github-app
# Inspired by https://gist.github.com/pelson/47c0c89a3522ed8da5cc305afc2562b0

# TL;DR
# Only GitHub App has an access to annotations API. In order to access this API, we need to
# generate a token based on GitHub App's private key and ID. The token (aka JWT) is valid for only 10 min.

import json
import os
import time

import jwt
import requests
from cryptography.hazmat.backends import default_backend

path_to_github_app_key = os.environ["PATH_TO_GITHUB_APP_KEY"]
cert_bytes = open(path_to_github_app_key, "r").read().encode()
github_app_id = os.environ["GITHUB_APP_ID"]
private_key = default_backend().load_pem_private_key(cert_bytes, None)
time_since_epoch_in_seconds = int(time.time())

payload = {
    # issued at time, 60 seconds in the past to allow for clock drift
    "iat": time_since_epoch_in_seconds - 60,
    # JWT expiration time (10 minute maximum)
    "exp": time_since_epoch_in_seconds + (10 * 60),
    # GitHub App's identifier
    "iss": github_app_id,
}

encoded_payload = jwt.encode(payload, private_key, algorithm="RS256")

headers = {
    "Authorization": "Bearer {}".format(encoded_payload),
    "Accept": "application/vnd.github.v3+json",
}

resp = requests.get("https://api.github.com/app/installations", headers=headers)

installation_id = json.loads(resp.content.decode())[0]["id"]

data = '{"permissions":{ "checks":"write"}}'
resp = requests.post(
    "https://api.github.com/app/installations/"
    + str(installation_id)
    + "/access_tokens",
    headers=headers,
    data=data,
)

installation_token = json.loads(resp.content.decode())["token"]
print(installation_token)

It took me a while, but this code works. You need to have GITHUB_APP_ID and file private_key containing private key generated by GitHub.

Then you can edit check runs like this:

curl -s -H "Authorization: token $GITHUB_APP_INSTALLATION_TOKEN" -H "application/vnd.github.v3+json" -d @body.json https://api.github.com/<username/organization>/$GITHUB_REPO/check-runs

Where body.json is a JSON file containing the following data: https://developer.github.com/v3/checks/runs/

For example:

# Create check "Linter" and set its status to "queued".
# https://developer.github.com/v3/checks/runs/

import json
import os

import requests

github_repo = os.environ["GITHUB_REPO"]
github_token = os.environ["GITHUB_APP_INSTALLATION_TOKEN"]
head_sha = os.environ["COMMIT_SHA"]

check = {
    "name": "Linter",
    "head_sha": head_sha,
    "status": "queued",
    "output": {
        "title": "Waiting...",
        "summary": "Check code with linter",
        "text": "Execute 'golangci-lint run'",
    },
}
data = json.dumps(check)

headers = {
    "Authorization": "token " + github_token,
    "Accept": "application/vnd.github.v3+json",
}

# Send request to GitHub.
requests.post(
    "https://api.github.com/repos/<username/organization>/" + github_repo + "/check-runs",
    headers=headers,
    data=data,
)
SantaXL
  • 618
  • 8
  • 19
2

Essentially, to be code agnostic, you can call the https://api.github.com/app/installations API method to get the latest installation. But, you'd have to be sure the latest one is the one you need, or that you have received the correct user/organization so that you can search through your results for the right one. Otherwise, you'll need to use the webhook (https://developer.github.com/apps/quickstart-guides/setting-up-your-development-environment/#authenticating-as-an-installation).

Jeremy L.
  • 853
  • 8
  • 15
2

On Github website after installing the app go to:

  • Settings
  • Developer Settings
  • Github Apps
  • Click on Edit next to the app
  • Advanced in left sidebar
  • Click on the notification for installation.created

There you find installation.id in the body of the response.

nice_pink
  • 435
  • 1
  • 3
  • 16
  • This will only be relevant if you have a single user of your application (highly unlikely for most github apps), as every repo or org you add to you organization will have a unique installation ID assigned. Rather then get it from the UI, its better to parse the payload posted to /api/github/hook for the installation -> ID value. If you use the one from the UI and have more than one repo/org granted under your App, you could start posting the API calls to the wrong repo/org. – decodebytes Jan 28 '23 at 10:54