Skip to content

Commit

Permalink
feat: Job label validation (#17)
Browse files Browse the repository at this point in the history
* add public interface

* add validation

* improve line break for comment

* use sets for Labels

* Define labels to ignore

* Revert "Define labels to ignore"

This reverts commit 660d87c.

* add case-insensitive matching

* bump library version
  • Loading branch information
cbartz authored Sep 27, 2024
1 parent 2866c28 commit 30fcc50
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 42 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[project]
name = "github-runner-manager"
version = "0.2.2"
version = "0.3.0"
authors = [
{ name = "Canonical IS DevOps", email = "[email protected]" },
]
Expand Down
22 changes: 11 additions & 11 deletions src-docs/openstack_cloud.openstack_runner_manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Manager for self-hosted runner on OpenStack.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L73"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L74"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `OpenStackCloudConfig`
Configuration for OpenStack cloud authorisation information.
Expand Down Expand Up @@ -47,7 +47,7 @@ __init__(clouds_config: dict[str, dict], cloud: str) → None

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L86"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L87"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `OpenStackServerConfig`
Configuration for OpenStack server.
Expand Down Expand Up @@ -78,7 +78,7 @@ __init__(image: str, flavor: str, network: str) → None

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L101"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L102"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `OpenStackRunnerManagerConfig`
Configuration for OpenStack runner manager.
Expand Down Expand Up @@ -119,7 +119,7 @@ __init__(

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L135"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L136"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `OpenStackRunnerManager`
Manage self-hosted runner on OpenStack cloud.
Expand All @@ -130,7 +130,7 @@ Manage self-hosted runner on OpenStack cloud.

- <b>`name_prefix`</b>: The name prefix of the runners created.

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L142"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L143"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `__init__`

Expand Down Expand Up @@ -162,7 +162,7 @@ The prefix of runner names.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L354"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L355"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `cleanup`

Expand All @@ -185,7 +185,7 @@ Cleanup runner and resource on the cloud.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L181"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L182"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `create_runner`

Expand Down Expand Up @@ -215,7 +215,7 @@ Create a self-hosted runner.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L289"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L290"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `delete_runner`

Expand All @@ -239,7 +239,7 @@ Delete self-hosted runners.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L323"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L324"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `flush_runners`

Expand All @@ -262,7 +262,7 @@ Remove idle and/or busy runners.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L230"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L231"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `get_runner`

Expand All @@ -285,7 +285,7 @@ Get a self-hosted runner by instance id.

---

<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L257"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/openstack_cloud/openstack_runner_manager.py#L258"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>method</kbd> `get_runners`

Expand Down
16 changes: 9 additions & 7 deletions src-docs/reactive.consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ Module responsible for consuming jobs from the message queue.

---

<a href="../src/github_runner_manager/reactive/consumer.py#L73"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/reactive/consumer.py#L75"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `consume`

```python
consume(
queue_config: QueueConfig,
runner_manager: RunnerManager,
github_client: GithubClient
github_client: GithubClient,
supported_labels: set[str]
) → None
```

Expand All @@ -31,6 +32,7 @@ Log the job details and acknowledge the message. If the job details are invalid,
- <b>`queue_config`</b>: The configuration for the message queue.
- <b>`runner_manager`</b>: The runner manager used to create the runner.
- <b>`github_client`</b>: The GitHub client to use to check the job status.
- <b>`supported_labels`</b>: The supported labels for the runner. If the job has unsupported labels, the message is requeued.



Expand All @@ -41,7 +43,7 @@ Log the job details and acknowledge the message. If the job details are invalid,

---

<a href="../reactive/consumer/signal_handler#L170"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../reactive/consumer/signal_handler#L200"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `signal_handler`

Expand All @@ -62,7 +64,7 @@ The signal handler exits the process.

---

<a href="../src/github_runner_manager/reactive/consumer.py#L27"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/reactive/consumer.py#L29"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `JobPickedUpStates`
The states of a job that indicate it has been picked up.
Expand All @@ -80,7 +82,7 @@ The states of a job that indicate it has been picked up.

---

<a href="../src/github_runner_manager/reactive/consumer.py#L39"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/reactive/consumer.py#L41"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `JobDetails`
A class to translate the payload.
Expand All @@ -97,7 +99,7 @@ A class to translate the payload.

---

<a href="../src/github_runner_manager/reactive/consumer.py#L50"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/reactive/consumer.py#L52"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `check_job_url_path_is_not_empty`

Expand Down Expand Up @@ -127,7 +129,7 @@ Check that the job_url path is not empty.

---

<a href="../src/github_runner_manager/reactive/consumer.py#L69"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/github_runner_manager/reactive/consumer.py#L71"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>class</kbd> `JobError`
Raised when a job error occurs.
Expand Down
1 change: 1 addition & 0 deletions src-docs/reactive.types_.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ The configuration for the reactive runner to spawn.
- <b>`runner_manager`</b>: The runner manager configuration.
- <b>`cloud_runner_manager`</b>: The OpenStack runner manager configuration.
- <b>`github_token`</b>: str
- <b>`supported_labels`</b>: The supported labels for the runner.



Expand Down
10 changes: 4 additions & 6 deletions src/github_runner_manager/github_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,14 +269,12 @@ def _to_job_info(job: dict) -> JobInfo:
Returns:
The JobInfo object.
"""
# datetime strings should be in ISO 8601 format,
# but they can also use Z instead of
# +00:00, which is not supported by datetime.fromisoformat
# datetime strings should be in ISO 8601 format, but they can also use Z instead of +00:00,
# which is not supported by datetime.fromisoformat
created_at = datetime.fromisoformat(job["created_at"].replace("Z", "+00:00"))
started_at = datetime.fromisoformat(job["started_at"].replace("Z", "+00:00"))
# conclusion could be null per api schema, so we need to handle that
# though we would assume that it should always be present,
# as the job should be finished
# conclusion could be null per api schema, so we need to handle that,
# though we would assume that it should always be present, as the job should be finished.
conclusion = job.get("conclusion", None)

status = job["status"]
Expand Down
46 changes: 38 additions & 8 deletions src/github_runner_manager/reactive/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

logger = logging.getLogger(__name__)

Labels = set[str]


class JobPickedUpStates(str, Enum):
"""The states of a job that indicate it has been picked up.
Expand All @@ -44,7 +46,7 @@ class JobDetails(BaseModel):
url: The URL of the job to check its status.
"""

labels: list[str]
labels: Labels
url: HttpUrl

@validator("url")
Expand All @@ -71,7 +73,10 @@ class JobError(Exception):


def consume(
queue_config: QueueConfig, runner_manager: RunnerManager, github_client: GithubClient
queue_config: QueueConfig,
runner_manager: RunnerManager,
github_client: GithubClient,
supported_labels: Labels,
) -> None:
"""Consume a job from the message queue.
Expand All @@ -82,6 +87,8 @@ def consume(
queue_config: The configuration for the message queue.
runner_manager: The runner manager used to create the runner.
github_client: The GitHub client to use to check the job status.
supported_labels: The supported labels for the runner. If the job has unsupported labels,
the message is requeued.
Raises:
JobError: If the job details are invalid.
Expand All @@ -100,12 +107,35 @@ def consume(
job_details.labels,
job_details.url,
)
_spawn_runner(
runner_manager=runner_manager,
job_url=job_details.url,
msg=msg,
github_client=github_client,
)
if not _validate_labels(
labels=job_details.labels, supported_labels=supported_labels
):
logger.error(
"Found unsupported job labels in %s. "
"Will not spawn a runner and requeue the message.",
job_details.labels,
)
msg.reject(requeue=True)
else:
_spawn_runner(
runner_manager=runner_manager,
job_url=job_details.url,
msg=msg,
github_client=github_client,
)


def _validate_labels(labels: Labels, supported_labels: Labels) -> bool:
"""Validate the labels of the job.
Args:
labels: The labels of the job.
supported_labels: The supported labels for the runner.
Returns:
True if the labels are valid, False otherwise.
"""
return {label.lower() for label in labels} <= {label.lower() for label in supported_labels}


def _spawn_runner(
Expand Down
7 changes: 6 additions & 1 deletion src/github_runner_manager/reactive/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ def main() -> None:
config=runner_config.runner_manager,
)
github_client = GithubClient(token=runner_config.github_token)
consume(queue_config=queue_config, runner_manager=runner_manager, github_client=github_client)
consume(
queue_config=queue_config,
runner_manager=runner_manager,
github_client=github_client,
supported_labels=runner_config.supported_labels,
)


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions src/github_runner_manager/reactive/types_.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ class RunnerConfig(BaseModel):
runner_manager: The runner manager configuration.
cloud_runner_manager: The OpenStack runner manager configuration.
github_token: str
supported_labels: The supported labels for the runner.
"""

queue: QueueConfig
runner_manager: RunnerManagerConfig
cloud_runner_manager: OpenStackRunnerManagerConfig
github_token: str
supported_labels: set[str]
Loading

0 comments on commit 30fcc50

Please sign in to comment.