You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit de92e4d
Browse filesBrowse the repository at this point in the historyBrowse files
fix(arc): read the App's id from an existing App Secret
The role required every org's app_id even with
github_runner_arc_manage_secrets false, when it writes no App Secret
and reads the id from the existing Secret wherever it needs one, so
a consumer had to read that Secret itself just to pass the id back.
app_id is now required only on a host that writes the App Secret;
the fleet-wide expansion no longer asks for it, since only each
host's own check knows whether it writes the Secret.
With an existing App Secret the role now checks it holds
github_app_id, github_app_installation_id and
github_app_private_key, and that an app_id the org still sets
matches the Secret's github_app_id, before anything changes. The
Secret lookup is no_log, since its results hold the Secrets' data.
"""Expand github_runner_arc_orgs entries into one record per scale-set profile, and check them.
422
422
423
423
Pass every org configured anywhere in the inventory, not one host's, so that the cross-host checks (an org configured twice, two profiles sharing a namespace, more than one autoscaled profile) see the whole fleet.
424
424
425
425
Args:
426
-
orgs: github_runner_arc_orgs entries, each with name, app_id, image and a non-empty scale_set_profiles list, at most one of private_key and private_key_op_reference, and optionally app_secret_name. A profile may override its namespace and release_name, and set scale_set_labels in place of runs_on_label.
426
+
require_app_id: whether each org must set app_id. The role needs it only when it writes the App Secret; otherwise the App's id is in the existing App Secret, and an app_id that is set is only checked against it.
427
+
orgs: github_runner_arc_orgs entries, each with name, app_id (see require_app_id), image and a non-empty scale_set_profiles list, at most one of private_key and private_key_op_reference, and optionally app_secret_name. A profile may override its namespace and release_name, and set scale_set_labels in place of runs_on_label.
427
428
428
429
Returns:
429
430
A dict with ``errors`` (messages, empty when valid), ``profiles`` (one dict per profile with org_name, suffix, namespace, release, app_secret, scale_set_labels, values_file, node_selector, autoscale, sizing, max_runners (the static maxRunners the role sets, or None), label and settings, the profile's own keys) and ``autoscaled`` (the one profile flagged autoscale, or None).
errors.append(f"{org_name}: app_id is required when the role writes the App Secret (github_runner_arc_manage_secrets); with an existing App Secret it is read from that Secret's github_app_id")
448
+
ifnot_text(org, "image"):
449
+
errors.append(f"{org_name}: image is required")
448
450
# Presence only: reading the value would decrypt an ansible-vault string for no reason.
Copy file name to clipboardExpand all lines: roles/github_runner_arc/README.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ Before touching the cluster the role then checks the secrets it is about to writ
12
12
13
13
## Variables
14
14
15
-
-`github_runner_arc_orgs`: the orgs this host installs. Each entry has `name`, `app_id`, `image`, `private_key` (the App's PEM private key, from any source Ansible reads) or `private_key_op_reference` (an `op://` reference that the `github_runner_secrets_onepassword` adapter resolves into `private_key`), optionally `installation_id` (resolved from the App when empty), optionally `app_secret_name` (the App Secret's name in each of the org's namespaces, default `<org>-github-app`), and `scale_set_profiles`, a list of profiles:
15
+
-`github_runner_arc_orgs`: the orgs this host installs. Each entry has `name`, `app_id` (needed only when the role writes the App Secret, see `github_runner_arc_manage_secrets`), `image`, `private_key` (the App's PEM private key, from any source Ansible reads) or `private_key_op_reference` (an `op://` reference that the `github_runner_secrets_onepassword` adapter resolves into `private_key`), optionally `installation_id` (resolved from the App when empty), optionally `app_secret_name` (the App Secret's name in each of the org's namespaces, default `<org>-github-app`), and `scale_set_profiles`, a list of profiles:
16
16
-`suffix`: appended to the namespace (`arc-runners-<org><suffix>`) and release (`<org>-runners<suffix>`) names. Default empty.
17
17
-`namespace`, `release_name`: the profile's namespace and Helm release name, in place of the names derived from the suffix. The release name is also the scale set's name on GitHub, so two profiles of one org cannot share it.
18
18
-`values_file`: Helm values for the release, a Jinja template read from `github_runner_arc_values_dir` on the control node (or an absolute path). Without it the role's `templates/runner-scale-set-values.yaml.j2` is used, driven by the profile's own `max_runners`, `min_runners`, `container_mode` (`dind` for the chart's Docker-in-Docker mode) and `resources` (the runner container's requests and limits).
@@ -31,7 +31,7 @@ Before touching the cluster the role then checks the secrets it is about to writ
31
31
-`github_runner_arc_listener_probes_enabled`: readiness and liveness probes on each listener's metrics endpoint, so a listener that starts and then fails its GitHub authentication is not counted ready during a rollout. Needs metrics on.
32
32
-`github_runner_arc_node_label_key`, `github_runner_arc_node_label_value`: when the key is set, the controller, listeners and runner pods are restricted to nodes carrying the label. The role labels the nodes named in `github_runner_arc_labelled_nodes`.
33
33
-`github_runner_arc_ghcr_username`, `github_runner_arc_ghcr_token`, `github_runner_arc_heartbeat_gh_token`: secrets, as plain variables. The pull credential is needed only for a static pull Secret.
34
-
-`github_runner_arc_manage_secrets`: `false` stops the role writing the App Secrets and `heartbeat-gh-token`, and instead checks before any change that they already exist. The role then needs no `private_key` or `installation_id`.
34
+
-`github_runner_arc_manage_secrets`: `false` stops the role writing the App Secrets and `heartbeat-gh-token`, and instead checks before any change that they already exist, and that each App Secret holds `github_app_id`, `github_app_installation_id` and `github_app_private_key`. The role then needs no `private_key` or `installation_id`, and no `app_id` either: it reads the App's id from the Secret where it needs it. An `app_id` that is set must match the Secret's `github_app_id`.
35
35
-`github_runner_arc_manage_image_pull_secret`: `false` stops the role writing the pull Secret, so an existing one named `github_runner_arc_image_pull_secret_name` is used as it is, kept current by something else. No pull credential is needed and the pull check is skipped; the role checks the Secret exists. Follows `github_runner_arc_manage_secrets` unless set.
36
36
-`github_runner_arc_image_pull_secret_source`: where a pull Secret the role writes gets its credential: `static` (the default) from `github_runner_arc_ghcr_username` and `github_runner_arc_ghcr_token`, or `app` from each org's GitHub App, renewed in the cluster (see [Image pull Secret from the GitHub App](#image-pull-secret-from-the-github-app)). `github_runner_arc_image_pull_secret_renewal_schedule` (default every 15 minutes) and `github_runner_arc_image_pull_secret_renewer_image` configure the renewal.
37
37
-`github_runner_arc_image_pull_registry`, `github_runner_arc_image_pull_secret_name`, `github_runner_arc_verify_image_pull`: the pull Secret's registry and name, and whether to prove the credential before writing it.
Copy file name to clipboardExpand all lines: roles/github_runner_arc/defaults/main.yml
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
---
2
2
# ARC (Actions Runner Controller) in an existing Kubernetes cluster: the controller, each org's runner scale sets, and the fleet-health platform (heartbeat and autoscaler). Each part is gated on this host's own github_runner_arc_orgs or github_runner_arc_heartbeat_gist_id, so any host that can reach the cluster's API can be the one Ansible installs from: a k3s server host brought up by github_runner_cluster, or localhost with a kubeconfig (playbooks/arc.yml). On a host github_runner_cluster made an agent, this role installs nothing.
3
3
4
-
# Orgs whose runner scale sets this host installs. Each entry: name, app_id, image, private_key (the App's PEM private key, from any source Ansible reads) or private_key_op_reference (an op:// reference the 1Password adapter resolves into private_key), optionally installation_id (resolved from the App when empty), optionally app_secret_name (the App Secret's name in each of the org's namespaces, default <org>-github-app), and scale_set_profiles, a list of {suffix, namespace?, release_name?, values_file?, max_runners?, node_selector?, runs_on_label?, scale_set_labels?, autoscale?, sizing?}. sizing takes uniform node figures, or measured: true to read each eligible node's free capacity from the cluster at install time. Each profile becomes one namespace (namespace, default arc-runners-<org><suffix>), two Secrets in it, and one Helm release (release_name, default <org>-runners<suffix>); setting both overrides lets the role take over scale sets an existing install created under its own names. See the README for every profile key.
4
+
# Orgs whose runner scale sets this host installs. Each entry: name, app_id (needed only when the role writes the App Secret; otherwise read from the existing Secret, and checked against it when set), image, private_key (the App's PEM private key, from any source Ansible reads) or private_key_op_reference (an op:// reference the 1Password adapter resolves into private_key), optionally installation_id (resolved from the App when empty), optionally app_secret_name (the App Secret's name in each of the org's namespaces, default <org>-github-app), and scale_set_profiles, a list of {suffix, namespace?, release_name?, values_file?, max_runners?, node_selector?, runs_on_label?, scale_set_labels?, autoscale?, sizing?}. sizing takes uniform node figures, or measured: true to read each eligible node's free capacity from the cluster at install time. Each profile becomes one namespace (namespace, default arc-runners-<org><suffix>), two Secrets in it, and one Helm release (release_name, default <org>-runners<suffix>); setting both overrides lets the role take over scale sets an existing install created under its own names. See the README for every profile key.
5
5
github_runner_arc_orgs: []
6
6
7
7
# Directory on the control node that a relative scale_set_profiles[].values_file is read from. Values files are Jinja templates rendered on the control node, so the target host needs no checkout. A profile with no values_file uses the role's own templates/runner-scale-set-values.yaml.j2.
Copy file name to clipboardExpand all lines: roles/github_runner_arc/meta/argument_specs.yml
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ argument_specs:
9
9
elements: dict
10
10
default: []
11
11
description:
12
-
- Orgs whose runner scale sets this host installs. Each entry has name, app_id, image, private_key or private_key_op_reference, and optionally installation_id and app_secret_name (the App Secret's name, default <org>-github-app).
12
+
- Orgs whose runner scale sets this host installs. Each entry has name, app_id (needed only when the role writes the App Secret; otherwise the existing Secret supplies it), image, private_key or private_key_op_reference, and optionally installation_id and app_secret_name (the App Secret's name, default <org>-github-app).
13
13
- Each entry's scale_set_profiles is a non-empty list of profiles, each with suffix and optionally namespace (default arc-runners-<org><suffix>), release_name (default <org>-runners<suffix>), values_file, max_runners, min_runners, container_mode, resources, node_selector, runs_on_label, scale_set_labels, autoscale and sizing.
14
14
- max_runners, when set, is the release's maxRunners and takes precedence over its values file and its sizing.
15
15
- scale_set_labels replaces runs_on_label with the whole list of scaleSetLabels; an empty list sets none, so jobs target the release name.
Copy file name to clipboardExpand all lines: roles/github_runner_arc/tasks/check_existing_secrets.yml
+22Lines changed: 22 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -20,6 +20,8 @@
20
20
loop_control:
21
21
label: "{{ item.0 }}/{{ item.1 }}"
22
22
register: github_runner_arc_existing_secrets
23
+
# The results hold the Secrets' data.
24
+
no_log: true
23
25
24
26
- name: Fail when a referenced Secret does not exist
25
27
ansible.builtin.fail:
@@ -31,3 +33,23 @@
31
33
loop_control:
32
34
label: "{{ item.item.0 }}/{{ item.item.1 }}"
33
35
when: item.resources | length == 0
36
+
37
+
# The App Secrets this host does not write, with the org each belongs to. The role reads the App's id from here rather than requiring the org's app_id, so a Secret missing a key, or disagreeing with an app_id that is set, fails now rather than as a listener that cannot authenticate.
38
+
- name: Check each existing App Secret holds what the controller reads
{{ ('has no ' ~ (github_runner_arc_missing_keys | join(', ')) ~ '; it needs github_app_id, github_app_installation_id and github_app_private_key.')
43
+
if github_runner_arc_missing_keys | length > 0 else
44
+
('holds github_app_id ' ~ github_runner_arc_secret_app_id ~ ', but the org sets app_id ' ~ github_runner_arc_org_app_id ~ '. Correct or remove the org\'s app_id.') }}
45
+
loop: "{{ github_runner_arc_existing_secrets.results | zip(github_runner_arc_local.profiles) | list if not (github_runner_arc_manage_secrets | bool) else [] }}"
Copy file name to clipboardExpand all lines: roles/github_runner_arc/tasks/validate.yml
+3-2Lines changed: 3 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -4,13 +4,14 @@
4
4
# This host's orgs come from github_runner_arc_orgs as this play resolves it, which includes play and role variables and -e; another host's can only be read from hostvars, which holds its inventory variables and facts. Taking this host's from hostvars too would miss orgs passed as a play variable, and every check and lookup built on the fleet (the scale sets' namespaces that measured sizing leaves out, the autoscaled profile) would then not see them. An implicit localhost is not in groups['all'] at all.
5
5
- name: Expand every org configured anywhere in the inventory into its scale-set profiles
6
6
ansible.builtin.set_fact:
7
+
# Whether an org needs app_id depends on whether its host writes the App Secret, which only that host's own check knows, so the fleet-wide expansion (for the cross-host checks) does not ask for it.
self.assertIn("X: app_id is required", result["errors"])
109
+
self.assertIn("X: app_id is required when the role writes the App Secret (github_runner_arc_manage_secrets); with an existing App Secret it is read from that Secret's github_app_id", result["errors"])
110
110
self.assertIn("X: image is required", result["errors"])
111
111
self.assertIn("github_runner_arc_orgs[1] has no name", result["errors"])
112
112
self.assertIn("Y: scale_set_profiles must be a non-empty list", result["errors"])
self.assertEqual(arc.arc_profiles([without])["errors"], ["Example: app_id is required when the role writes the App Secret (github_runner_arc_manage_secrets); with an existing App Secret it is read from that Secret's github_app_id"])
118
+
self.assertIn("Example: image is required", arc.arc_profiles([{**without, "image": ""}], require_app_id=False)["errors"])
self.assertIn("Example: set private_key or private_key_op_reference, not both (the 1Password adapter fills private_key from the reference)", arc.arc_profiles([both])["errors"])
0 commit comments