GitHub Appを使ってGitHub APIを叩く (Python)

# 内容

Python でGitHub AppのJWTを生成、Installation Access Tokenを生成してからGitHub APIをたたく。

GitHub App user access tokenを使うことも可能だが今回は割愛

![Screenshot 2024-11-09 at 22.50.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7059/54f58354-2eb8-5bf1-485b-2376557c978f.png)


# 準備

```
pip install cryptography PyJWT
```

# JWTの取得

```py
import sys
import time
import os

import jwt

def get_jwt(pem_path: str, client_id: str) -> str:

    # Open PEM
    with open(pem_path, 'rb') as pem_file:
        signing_key = pem_file.read()

    payload = {
        # Issued at time
        'iat': int(time.time()),
        # JWT expiration time (10 minutes maximum)
        'exp': int(time.time()) + 600,

        # GitHub App's client ID
        'iss': client_id
    }

    # Create JWT
    encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256')

    print(f"JWT:  {encoded_jwt}")
    return encoded_jwt

if __name__ == "__main__":
    pem_path = os.environ["GITHUB_APP_PEM_PATH"]
    client_id = os.environ["GITHUB_APP_CLIENT_ID"]
    jwt = get_jwt(pem_path, client_id)
```

# JWTの確認

```py
def check_jwt(jwt: str) -> dict:
    """Check JWT by sending a request to GitHub API
    """
    url = 'https://api.github.com/app'
    headers = {
        'Authorization': f'Bearer {jwt}',
        'Accept': 'application/vnd.github.v3+json'
    }
    r = requests.get(url=url, headers=headers)
    return r.json()

if __name__ == "__main__":
    pem_path = os.environ["GITHUB_APP_PEM_PATH"]
    client_id = os.environ["GITHUB_APP_CLIENT_ID"]
    jwt = get_jwt(pem_path, client_id)
    print(check_jwt(jwt))
```

Appの情報が出てくればOK ✅️

# Installation Access Tokenの取得

```py
def get_installation_id(jwt: str) -> int:
    url = 'https://api.github.com/app/installations'
    headers = {
        'Authorization': f'Bearer {jwt}',
        'Accept': 'application/vnd.github.v3+json'
    }
    r = requests.get(url=url, headers=headers)
    return r.json()[0]['id']
```

を追加して

```py
if __name__ == "__main__":
    pem_path = os.environ["GITHUB_APP_PEM_PATH"]
    client_id = os.environ["GITHUB_APP_CLIENT_ID"]
    installation_id = os.getenv("GITHUB_APP_INSTALLATION_ID")
    jwt = get_jwt(pem_path, client_id)
    print(check(jwt))
    if installation_id is None:
        installation_id = get_installation_id(jwt)
    print(installation_id)
```

これでinstallation_idを取得 installation_idは `GITHUB_APP_INSTALLATION_ID` に入れておけば毎回APIを叩かなくて済むようになる。

:::note info
GitHub Webhook Event使う場合には payloadに installation idも含まれるので自分で取得しなくていい
:::


# Installation Access Tokenを使ってAPIを叩く

```py
def send_gh_api_request(access_token: str, path: str) -> dict:
    """
    access_token(str) : Installation Access Token or PAT
    path(str) : API path e.g /{owner}/{repo}/pulls?&state=closed
    """

    url = 'https://api.github.com/repos' + path
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Accept': 'application/vnd.github+json'
    }
    r = requests.get(url=url, headers=headers)
    return r.json()
res = send_gh_api_request(iat, "/<owner>/<repo>/pulls?&state=closed")
```

CloseされたPRを取得できた ✅️

# Ref

- https://github.com/jpadilla/pyjwt/issues/790
- https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app
- https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app
- https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app