|
1 | | -"""App Module""" |
2 | | -import os |
| 1 | +"""Routes Module""" |
3 | 2 | import re |
4 | 3 | import logging |
5 | | -import argparse |
6 | | -import datetime |
7 | 4 | import xml.etree.ElementTree as ET |
8 | | -from concurrent.futures import ThreadPoolExecutor |
9 | | - |
10 | | -import jwt |
| 5 | +import datetime |
11 | 6 | import requests |
12 | 7 | from flask import Flask, request, make_response, jsonify |
13 | 8 | from flask_basicauth import BasicAuth |
| 9 | +from helpers import get_token, get_all_workflow_runs |
| 10 | +from config import BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD, TIMEOUT |
14 | 11 |
|
15 | 12 | logging.basicConfig(level=logging.INFO) |
16 | 13 | logger = logging.getLogger(__name__) |
17 | 14 |
|
18 | 15 | app = Flask('github-cctray') |
19 | | -app.config['BASIC_AUTH_USERNAME'] = os.environ.get("BASIC_AUTH_USERNAME") |
20 | | -app.config['BASIC_AUTH_PASSWORD'] = os.environ.get("BASIC_AUTH_PASSWORD") |
| 16 | +app.config['BASIC_AUTH_USERNAME'] = BASIC_AUTH_USERNAME |
| 17 | +app.config['BASIC_AUTH_PASSWORD'] = BASIC_AUTH_PASSWORD |
21 | 18 |
|
22 | 19 | basic_auth = BasicAuth(app) |
23 | 20 |
|
24 | | -API_BASE_URL = "https://api.github.com" |
25 | | -MAX_WORKERS = 10 |
26 | | -TIMEOUT = 10 |
27 | | - |
28 | | -def get_token(): |
29 | | - """Sets the GitHub API token based on the selected mode |
30 | | -
|
31 | | - Returns: |
32 | | - token: Either the personal access token or the GitHub App access token |
33 | | - """ |
34 | | - parser = argparse.ArgumentParser() |
35 | | - parser.add_argument( |
36 | | - "--mode", |
37 | | - choices=[ |
38 | | - "pat-auth", |
39 | | - "app-auth"], |
40 | | - default="pat-auth", |
41 | | - help="Authentication mode") |
42 | | - args = parser.parse_args() |
43 | | - |
44 | | - token = request.args.get("token") |
45 | | - |
46 | | - if token: |
47 | | - return token |
48 | | - elif args.mode == "pat-auth": |
49 | | - token = os.environ.get("GITHUB_TOKEN") |
50 | | - elif args.mode == "app-auth": |
51 | | - app_auth_id = os.environ.get("APP_AUTH_ID") |
52 | | - app_auth_private_key = os.environ.get("APP_AUTH_PRIVATE_KEY") |
53 | | - app_auth_installation_id = os.environ.get("APP_AUTH_INSTALLATION_ID") |
54 | | - app_auth_base_url = "https://api.github.com" |
55 | | - |
56 | | - now = datetime.datetime.utcnow() |
57 | | - iat = int((now - datetime.datetime(1970, 1, 1)).total_seconds()) |
58 | | - exp = iat + 600 |
59 | | - payload = { |
60 | | - "iat": iat, |
61 | | - "exp": exp, |
62 | | - "iss": app_auth_id |
63 | | - } |
64 | | - encoded_jwt = jwt.encode( |
65 | | - payload, |
66 | | - app_auth_private_key, |
67 | | - algorithm="RS256") |
68 | | - headers = { |
69 | | - "Authorization": f"Bearer {encoded_jwt}", |
70 | | - "Accept": "application/vnd.github.v3+json" |
71 | | - } |
72 | | - response = requests.post( |
73 | | - f"{app_auth_base_url}/app/installations/{app_auth_installation_id}/access_tokens", |
74 | | - headers=headers, |
75 | | - timeout=TIMEOUT) |
76 | | - |
77 | | - if response.status_code == 201: |
78 | | - token = response.json()["token"] |
79 | | - else: |
80 | | - raise Exception( |
81 | | - f"Failed to obtain access token: {response.status_code} {response.text}") |
82 | | - |
83 | | - return token |
84 | | - |
85 | | - |
86 | | -def get_workflows(owner, repo, headers): |
87 | | - """Get the workflows for a given owner and repo from the GitHub API. |
88 | | -
|
89 | | - Args: |
90 | | - owner (str): The owner of the repository. |
91 | | - repo (str): The repository name. |
92 | | - headers (dict): HTTP headers to be sent with the request. |
93 | | -
|
94 | | - Returns: |
95 | | - list: A list of workflows for the given repository. |
96 | | -
|
97 | | - Raises: |
98 | | - requests.HTTPError: If the request to the GitHub API fails. |
99 | | - """ |
100 | | - endpoint = f"{API_BASE_URL}/repos/{owner}/{repo}/actions/workflows" |
101 | | - response = requests.get(endpoint, headers=headers, timeout=TIMEOUT) |
102 | | - response.raise_for_status() |
103 | | - return response.json()["workflows"] |
104 | | - |
105 | | - |
106 | | -def get_workflow_runs(workflow, headers): |
107 | | - """Get the workflow runs for a specific workflow from the GitHub API. |
108 | | -
|
109 | | - Args: |
110 | | - workflow (dict): The workflow information. |
111 | | - headers (dict): HTTP headers to be sent with the request. |
112 | | -
|
113 | | - Returns: |
114 | | - list: A list of workflow runs for the given workflow. |
115 | | -
|
116 | | - Raises: |
117 | | - requests.HTTPError: If the request to the GitHub API fails. |
118 | | - """ |
119 | | - url = f"{workflow['url']}/runs" |
120 | | - response = requests.get(url, headers=headers, timeout=TIMEOUT) |
121 | | - response.raise_for_status() |
122 | | - return response.json()["workflow_runs"] |
123 | | - |
124 | | - |
125 | | -def get_all_workflow_runs(owner, repo, token): |
126 | | - """Get all workflow runs for a given owner, repo, and token. |
127 | | -
|
128 | | - Args: |
129 | | - owner (str): The owner of the repository. |
130 | | - repo (str): The repository name. |
131 | | - token (str): The GitHub token for authentication. |
132 | | -
|
133 | | - Returns: |
134 | | - list: A list of all workflow runs for the given repository. |
135 | | -
|
136 | | - Raises: |
137 | | - requests.HTTPError: If the request to the GitHub API fails. |
138 | | - """ |
139 | | - headers = { |
140 | | - "Authorization": f"Bearer {token}", |
141 | | - "Accept": "application/vnd.github.v3+json" |
142 | | - } |
143 | | - |
144 | | - workflows = get_workflows(owner, repo, headers) |
145 | | - with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: |
146 | | - futures = [ |
147 | | - executor.submit( |
148 | | - get_workflow_runs, |
149 | | - workflow, |
150 | | - headers) for workflow in workflows] |
151 | | - |
152 | | - results = [] |
153 | | - for future in futures: |
154 | | - data = future.result() |
155 | | - results += data |
156 | | - |
157 | | - return results |
158 | | - |
159 | | - |
160 | 21 | @app.route('/') |
161 | 22 | @basic_auth.required |
162 | 23 | def index(): |
@@ -225,7 +86,7 @@ def health(): |
225 | 86 | Returns: |
226 | 87 | flask.Response: JSON response containing the status and version. |
227 | 88 | """ |
228 | | - with open('CHANGELOG.md', 'r', encoding='utf-8') as changelog_file: |
| 89 | + with open('../CHANGELOG.md', 'r', encoding='utf-8') as changelog_file: |
229 | 90 | changelog_content = changelog_file.read() |
230 | 91 |
|
231 | 92 | latest_version_match = re.search( |
@@ -296,9 +157,3 @@ def handle_error(exception): |
296 | 157 | logger.error("An error occurred: %s", str(exception)) |
297 | 158 | error_message = f"An error occurred: {str(exception)}" |
298 | 159 | return error_message, 500 |
299 | | - |
300 | | - |
301 | | -if __name__ == '__main__': |
302 | | - from waitress import serve |
303 | | - |
304 | | - serve(app, host='0.0.0.0', port=8000) |
0 commit comments