-
Notifications
You must be signed in to change notification settings - Fork 25
feat: jobmanager token authentication #587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
cca2d7f
adapt charm
cbartz b2faf78
jobmanager_api abstraction WIP checkin
cbartz a52a737
checkin jobmanager api complete
cbartz 6bd4fae
adapt JobManagerPlatform.build
cbartz 59d492f
first lint
cbartz 3580c62
fix integration test
cbartz 999939a
lint
cbartz fa1882c
set status type to str
cbartz 2ef696e
cleanup
cbartz 3db4fdc
lint
cbartz 7e3ebad
fix token change flush when reusing same jobmanager token
cbartz a5ef508
create api client on each request
cbartz 648973e
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz b4b3db7
address pr review
cbartz 7307332
remove comment
cbartz 3cb3a53
address review comment
cbartz c8f90fc
remove comment
cbartz edded67
remove SelfHostedRunnerLabel class
cbartz 2d4ef00
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz f47f79a
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz 9c7b571
remove unused UnsupportedArch code
cbartz dfd765e
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz 7fc51ca
use url from jobmanager api
cbartz f41959b
use if else for label
cbartz 3e658fb
use jobmanager api stub in unit test
cbartz 5bf0d5e
add jobmanager provider stub
cbartz e3e821e
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz 7b84c53
describe DCO020, DCO030, DCO050
cbartz 27a9bda
Merge remote-tracking branch 'origin/feat/jobmanager-token-auth-ISD-3…
cbartz 9bef95b
Merge branch 'main' into feat/jobmanager-token-auth-ISD-3752
cbartz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
github-runner-manager/src/github_runner_manager/jobmanager_api.py
|
cbartz marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| # Copyright 2025 Canonical Ltd. | ||
|
yanksyoon marked this conversation as resolved.
|
||
| # See LICENSE file for licensing details. | ||
|
|
||
| """Module containing logic to handle calls to the jobmanager api.""" | ||
| from enum import Enum | ||
|
|
||
| import jobmanager_client | ||
| from jobmanager_client.exceptions import ApiException, NotFoundException | ||
| from pydantic import BaseModel | ||
| from urllib3.exceptions import RequestError | ||
|
|
||
|
|
||
| class JobManagerAPIError(Exception): | ||
| """Base exception for JobManager API errors.""" | ||
|
|
||
|
|
||
| class JobManagerAPINotFoundError(JobManagerAPIError): | ||
|
yhaliaw marked this conversation as resolved.
|
||
| """Exception raised when a runner is not found in the JobManager API.""" | ||
|
|
||
|
|
||
| class JobStatus(str, Enum): | ||
|
yanksyoon marked this conversation as resolved.
|
||
| """Status of a job on the JobManager. | ||
|
|
||
| Attributes: | ||
| IN_PROGRESS: Represents a job that is in progress. | ||
| PENDING: Represents a job that is pending. | ||
| """ | ||
|
|
||
| IN_PROGRESS = "IN_PROGRESS" | ||
| PENDING = "PENDING" | ||
|
|
||
|
|
||
| class Job(BaseModel): | ||
| """Represents a job on the JobManagerAPI. | ||
|
|
||
| Attributes: | ||
| status: The status of the job. | ||
| """ | ||
|
|
||
| status: str | None | ||
|
yanksyoon marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class RunnerStatus(str, Enum): | ||
| """Status of a runner on the JobManager. | ||
|
|
||
| Attributes: | ||
| IN_PROGRESS: Represents a runner that is in progress. | ||
| PENDING: Represents a runner that is pending. | ||
| """ | ||
|
|
||
| IN_PROGRESS = "IN_PROGRESS" | ||
| PENDING = "PENDING" | ||
|
|
||
|
|
||
| class RunnerRegistration(BaseModel): | ||
| """Represents a runner registration response from the JobManagerAPI. | ||
|
|
||
| Attributes: | ||
| id: The ID of the registered runner. | ||
| token: The token for the registered runner. | ||
| """ | ||
|
|
||
| id: int | ||
| token: str | ||
|
|
||
|
|
||
| class RunnerHealth(BaseModel): | ||
| """Represents the health status of a runner on the JobManagerAPI. | ||
|
|
||
| Attributes: | ||
| status: The health status of the runner. | ||
| deletable: Indicates if the runner can be deleted. | ||
| """ | ||
|
|
||
| status: str | ||
|
yanksyoon marked this conversation as resolved.
|
||
| deletable: bool | ||
|
|
||
|
|
||
| class JobManagerAPI: | ||
| """Handles interactions with the JobManager API.""" | ||
|
|
||
| # The job manager api uses an autogenerated api client that uses urllib3 connection pools | ||
| # that are not multiprocessing safe: https://github.com/urllib3/urllib3/issues/850 | ||
| # Therefore, we create a new ApiClient for each request and close resources | ||
| # to avoid issues with multiprocessing in the application. | ||
|
|
||
| def __init__(self, token: str, url: str): | ||
| """Initialize the JobManagerAPI with a token and URL. | ||
|
|
||
| Args: | ||
| token: The authentication token for the JobManager API. | ||
| url: The base URL for the JobManager API. | ||
| """ | ||
| self._token = token | ||
| self.url = url | ||
|
|
||
| def get_runner_health(self, runner_id: int) -> RunnerHealth: | ||
| """Fetch the health status of a runner by its ID from the JobManager API. | ||
|
|
||
| Args: | ||
| runner_id: The ID of the runner to fetch health status for. | ||
|
|
||
| Raises: | ||
| JobManagerAPINotFoundError: If the runner with the given ID is not found. | ||
| JobManagerAPIError: If there is an error fetching the runner health. | ||
|
|
||
| Returns: | ||
| RunnerHealth: The health status of the runner. | ||
| """ | ||
| with self._create_api_client() as api_client: | ||
| runners_api = jobmanager_client.RunnersApi(api_client=api_client) | ||
| try: | ||
| response = runners_api.get_runner_health_v1_runners_runner_id_health_get(runner_id) | ||
| except NotFoundException as err: | ||
| raise JobManagerAPINotFoundError( | ||
| f"Health for runner with ID {runner_id} not found in JobManager API." | ||
| ) from err | ||
| except (ApiException, RequestError, ValueError) as exc: | ||
| raise JobManagerAPIError( | ||
| f"Error fetching runner health for ID {runner_id}: {exc}" | ||
| ) from exc | ||
| return RunnerHealth(status=response.status, deletable=response.deletable) | ||
|
|
||
| def register_runner(self, name: str, labels: list[str]) -> RunnerRegistration: | ||
| """Register a new runner with the JobManager API. | ||
|
|
||
| Args: | ||
| name: The name of the runner to register. | ||
| labels: A list of labels to associate with the runner. | ||
|
|
||
| Returns: | ||
| RunnerRegistration: The registration details of the runner, including ID and token. | ||
|
|
||
| Raises: | ||
| JobManagerAPIError: If there is an error registering the runner. | ||
| """ | ||
| with self._create_api_client() as api_client: | ||
| runners_api = jobmanager_client.RunnersApi(api_client=api_client) | ||
| runner_register_request = jobmanager_client.RunnerCreate(name=name, labels=labels) | ||
|
|
||
| try: | ||
| response = runners_api.register_runner_v1_runners_register_post( | ||
| runner_register_request | ||
| ) | ||
| except (ApiException, RequestError, ValueError) as exc: | ||
| raise JobManagerAPIError(f"Error registering runner: {exc}") from exc | ||
| return RunnerRegistration(id=response.id, token=response.token) | ||
|
|
||
| def get_job(self, job_id: int) -> Job: | ||
| """Fetch a job by its ID from the JobManager API. | ||
|
|
||
| Args: | ||
| job_id: The ID of the job to fetch. | ||
|
|
||
| Returns: | ||
| Job: The job object containing its status. | ||
|
|
||
| Raises: | ||
| JobManagerAPINotFoundError: If the job with the given ID is not found. | ||
| JobManagerAPIError: If there is an error fetching the job. | ||
| """ | ||
| with self._create_api_client() as api_client: | ||
| jobs_api = jobmanager_client.JobsApi(api_client=api_client) | ||
| try: | ||
| response = jobs_api.get_job_v1_jobs_job_id_get(job_id) | ||
| except NotFoundException as err: | ||
|
yanksyoon marked this conversation as resolved.
|
||
| raise JobManagerAPINotFoundError( | ||
| f"Job with ID {job_id} not found in JobManager API." | ||
| ) from err | ||
| except (ApiException, RequestError, ValueError) as exc: | ||
| raise JobManagerAPIError(f"Error fetching job with ID {job_id}: {exc}") from exc | ||
| return Job(status=response.status) | ||
|
|
||
| def _create_api_client(self) -> jobmanager_client.ApiClient: | ||
| """Create a new API client for the JobManager API. | ||
|
|
||
| Returns: | ||
| jobmanager_client.ApiClient: A new API client configured with the JobManager | ||
| API URL and token. | ||
| """ | ||
| config = jobmanager_client.Configuration(host=self.url) | ||
| api_client = jobmanager_client.ApiClient(configuration=config) | ||
| api_client.set_default_header("Authorization", f"Bearer {self._token}") | ||
| return api_client | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.