Skip to content
Merged
2 changes: 2 additions & 0 deletions .env.bootstrap.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ GITHUB_RUNNER_COMPOSE_PROJECT=

# The GitHub organisation the runners register with, and its GitHub App (see playbooks/github_app_setup.yml to create one). The installation ID is resolved automatically from the App ID and key when left empty.
RUNNER_ORG=
# A repository of RUNNER_ORG to register the runners in instead of the organisation, which makes RUNNER_ORG a user or an organisation (see Repository scope in roles/github_runner_arc/README.md); its App needs the repository-scope permissions. Empty registers organisation runners.
RUNNER_REPOSITORY=
RUNNER_APP_ID=
RUNNER_APP_INSTALLATION_ID=
RUNNER_APP_PRIVATE_KEY_PATH=./secrets/app-private-key.pem
Expand Down
18 changes: 9 additions & 9 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jq -n \
{suffix: "", autoscale: ((env.AUTOSCALER_USABLE_BUDGET_GI // "") != "")}
+ (if (env.RUNNER_VALUES_FILE // "") != "" then {values_file: env.RUNNER_VALUES_FILE} else {max_runners: env.RUNNER_MAX_RUNNERS} end)
]
}]
} + opt("repository"; "RUNNER_REPOSITORY")]
end
),
github_runner_arc_values_dir: $repo_root,
Expand Down
114 changes: 81 additions & 33 deletions plugins/filter/arc.py

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions plugins/filter/onepassword.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@
def onepassword_fill_org_keys(orgs: Sequence[Mapping[str, Any]], org_private_keys: Mapping[str, str]) -> list[dict[str, Any]]:
"""Give every org that names its App private key by 1Password reference the key itself.

:param orgs: github_runner_arc_orgs entries. :param org_private_keys: the keys read from 1Password, by org name. :returns: the entries in the same order, each one with a private_key_op_reference replaced by private_key holding the key read for it, and every other entry unchanged. :raises AnsibleFilterError: if orgs is not a list, or no key was read for an org that has a reference.
:param orgs: github_runner_arc_orgs entries. :param org_private_keys: the keys read from 1Password, by 1Password reference, so entries of one login (an organisation and its repositories) that name different keys each get their own, and entries naming one reference share it. :returns: the entries in the same order, each one with a private_key_op_reference replaced by private_key holding the key read for it, and every other entry unchanged. :raises AnsibleFilterError: if orgs is not a list, or no key was read for an org that has a reference.
"""
if not isinstance(orgs, Sequence) or isinstance(orgs, (str, bytes)):
raise AnsibleFilterError("github_runner_arc_orgs must be a list")
filled: list[dict[str, Any]] = []
for org in orgs:
entry = dict(org)
if "private_key_op_reference" in entry:
name = entry.get("name")
if name not in org_private_keys:
raise AnsibleFilterError(f"No App private key was read from 1Password for org {name}")
del entry["private_key_op_reference"]
entry["private_key"] = org_private_keys[name]
reference = entry.pop("private_key_op_reference")
if reference not in org_private_keys:
raise AnsibleFilterError(f"No App private key was read from 1Password for {entry.get('name')} ({reference})")
entry["private_key"] = org_private_keys[reference]
filled.append(entry)
return filled

Expand Down
60 changes: 52 additions & 8 deletions roles/github_runner_arc/README.md

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions roles/github_runner_arc/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,28 +204,34 @@ github_runner_arc_runner_reaper_image: "{{ github_runner_arc_image_pull_secret_r
# A backstop that needs no GitHub call: activeDeadlineSeconds on every runner pod, after which the kubelet stops the pod whatever it is doing and ARC treats it as a failed runner. It counts from when the pod starts, including any time the runner waits idle for a job, so set it above the longest a runner may wait plus the longest job a fleet runs. Unset leaves the runner pods without one.
github_runner_arc_runner_active_deadline_seconds:

# GitHub App creation through the manifest flow (tasks/app_setup.yml, run by playbooks/github_app_setup.yml). The App needs only organisation self-hosted runner write access; add packages: read to the permissions if its installation token should also pull private packages, and actions: read if the runner reaper is on.
# GitHub App creation through the manifest flow (tasks/app_setup.yml, run by playbooks/github_app_setup.yml). github_runner_arc_app_setup_org is the login of the account the App is registered and installed on: an organisation, or with github_runner_arc_app_setup_owner_type user a personal account.
github_runner_arc_app_setup_org: ""
# organization registers the App on the organisation (GitHub's organisation App settings), user on a personal account (the account's own App settings). A personal account has no organisation runners, so user needs github_runner_arc_app_setup_repository.
github_runner_arc_app_setup_owner_type: organization
# A repository of that account, for an App whose scale sets register in that repository alone (a github_runner_arc_orgs entry with repository). Empty means an App for the organisation's own scale sets.
github_runner_arc_app_setup_repository: ""
github_runner_arc_app_setup_name: ""
github_runner_arc_app_setup_description: "Registers self-hosted GitHub Actions runners through Actions Runner Controller."
# The App's homepage and the page GitHub redirects to with the one-time code. Empty means the organisation's GitHub page.
# The App's homepage and the page GitHub redirects to with the one-time code. Empty means the account's GitHub page.
github_runner_arc_app_setup_homepage_url: ""
github_runner_arc_app_setup_permissions:
organization_self_hosted_runners: write
# The App's permissions. Empty means what ARC documents for the scope (github_runner_arc_app_setup_scope_permissions in vars/main.yml): organisation self-hosted runners write for an organisation, repository administration write and metadata read for a repository. A value replaces them whole, so to add packages: read (the installation token also pulls private packages) or actions: read (the runner reaper is on) name the scope's own permissions beside it.
github_runner_arc_app_setup_permissions: {}
github_runner_arc_app_manifest_code: ""
github_runner_arc_app_private_key_path: ""
# How long phase 2 waits for the App to be installed: retries times delay seconds.
github_runner_arc_app_setup_install_retries: 40
github_runner_arc_app_setup_install_delay: 15
# The phase 2 command the manifest form and phase 1's instructions tell the person to run, without the code and key path, which the role appends. Its default runs this collection's playbook from a checkout; a playbook that wraps the flow names itself here, with whatever arguments it needs.
github_runner_arc_app_setup_command: >-
ansible-playbook playbooks/github_app_setup.yml -e github_runner_arc_app_setup_org={{ github_runner_arc_app_setup_org }}
ansible-playbook playbooks/github_app_setup.yml -e github_runner_arc_app_setup_org={{ github_runner_arc_app_setup_org }}{{
' -e github_runner_arc_app_setup_owner_type=' ~ github_runner_arc_app_setup_owner_type if github_runner_arc_app_setup_owner_type != 'organization' else '' }}{{
' -e github_runner_arc_app_setup_repository=' ~ github_runner_arc_app_setup_repository if github_runner_arc_app_setup_repository | length > 0 else '' }}
-e '{"github_runner_arc_app_setup_name": "{{ github_runner_arc_app_setup_name }}"}'
# Whether phase 2 writes the App Secret (github_app_id, github_app_installation_id and github_app_private_key) into the cluster once the App is installed, through github_runner_arc_kubeconfig_path, creating the namespaces it needs. The cluster is reached, and the namespaces created, before the one-time code is used, so a broken cluster path costs only a rerun. false leaves the key in its file for the operator to store.
github_runner_arc_app_setup_write_secret: false
# The namespaces the App Secret is written into. Empty means those of the organisation's scale-set profiles in github_runner_arc_orgs.
# The namespaces the App Secret is written into. Empty means those of the scale-set profiles of the github_runner_arc_orgs entry for the App's scope (the organisation, or with github_runner_arc_app_setup_repository that repository).
github_runner_arc_app_setup_secret_namespaces: []
# The App Secret's name. Empty means the organisation's app_secret_name in github_runner_arc_orgs, or <org>-github-app, as the role's install uses.
# The App Secret's name. Empty means the app_secret_name of the github_runner_arc_orgs entry for the App's scope, or the name the role's install derives for that scope (<org>-github-app, or <owner>--<repository>-github-app).
github_runner_arc_app_setup_secret_name: ""
# Whether to keep the private key file once the App Secret holds the key. false removes it, so the key is on disk only until the Secret is written; a run that stops earlier, for example because nobody installed the App within the wait, leaves the file so the App can still be finished without a new code.
github_runner_arc_app_setup_keep_private_key: false
Expand Down
25 changes: 18 additions & 7 deletions roles/github_runner_arc/meta/argument_specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ argument_specs:
elements: dict
default: []
description:
- 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).
- 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, capabilities, capability_store, tolerations, autoscale, sizing and burst.
- Scopes 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 repository, installation_id and app_secret_name (the App Secret's name, default <org>-github-app).
- Without repository an entry registers organisation runners in the organisation name names (githubConfigUrl https://github.com/<name>). With repository it registers repository runners in that one repository (https://github.com/<name>/<repository>), where name may be a user or an organisation; its derived names use <name>--<repository> (lower-cased, with any character a DNS label does not allow as '-') in place of <org>, and its App needs the repository-scope permissions. The scope (name, or name/repository) identifies an entry, so one login may have an organisation entry and any number of repository entries.
- 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>, at most 53 characters, Helm's limit), values_file, max_runners, min_runners, container_mode, resources, node_selector, runs_on_label, scale_set_labels, capabilities, capability_store, tolerations, autoscale, sizing and burst.
- capability_store, a boolean (default false), makes the profile's runner pods copy each tool-cache and system-library capability's payload, and the runner's externals for a Docker-in-Docker sidecar the values write out, into the capability store (github_runner_arc_capability_store_host_path), which the pods of a node share, instead of into every pod's own volumes, and mount the result read-only. Every image those copies come from must be pinned by digest, and the store needs the node cache pruner, so a profile that sets it is refused while github_runner_arc_node_cache_size_limit_gib is 0 or github_runner_arc_node_label_key is empty, since the pruner runs only on the nodes carrying that label. A job that can reach a privileged Docker daemon can write to the store, so give it only to a profile whose workflows are all of one trust level.
- max_runners, when set, is the release's maxRunners and takes precedence over its values file and its sizing.
- scale_set_labels replaces runs_on_label with the whole list of scaleSetLabels; an empty list sets none, so jobs target the release name.
Expand Down Expand Up @@ -412,7 +413,16 @@ argument_specs:
github_runner_arc_app_setup_org:
type: str
default: ""
description: Organisation the App setup creates the App on.
description: Login of the account the App setup creates the App on and waits for it to be installed on, an organisation or with github_runner_arc_app_setup_owner_type user a personal account.
github_runner_arc_app_setup_owner_type:
type: str
choices: [organization, user]
default: organization
description: Whether github_runner_arc_app_setup_org is an organisation or a personal account, which decides where GitHub's manifest flow registers the App. user needs github_runner_arc_app_setup_repository.
github_runner_arc_app_setup_repository:
type: str
default: ""
description: A repository of that account whose runners the App is for, matching a github_runner_arc_orgs entry with repository. Empty means the organisation's own runners.
github_runner_arc_app_setup_name:
type: str
default: ""
Expand All @@ -423,10 +433,11 @@ argument_specs:
github_runner_arc_app_setup_homepage_url:
type: str
default: ""
description: The App's homepage and redirect page. Empty means the organisation's GitHub page.
description: The App's homepage and redirect page. Empty means the account's GitHub page.
github_runner_arc_app_setup_permissions:
type: dict
description: The App's permissions.
default: {}
description: The App's permissions. Empty means those ARC documents for the scope (organisation self-hosted runners write for an organisation; repository administration write and metadata read for a repository); a value replaces them whole.
github_runner_arc_app_manifest_code:
type: str
default: ""
Expand All @@ -448,11 +459,11 @@ argument_specs:
type: list
elements: str
default: []
description: Namespaces the App Secret is written into. Empty means those of the organisation's scale-set profiles in github_runner_arc_orgs.
description: Namespaces the App Secret is written into. Empty means those of the scale-set profiles of the github_runner_arc_orgs entry for the App's scope.
github_runner_arc_app_setup_secret_name:
type: str
default: ""
description: The App Secret's name. Empty means the organisation's app_secret_name in github_runner_arc_orgs, or <org>-github-app.
description: The App Secret's name. Empty means the app_secret_name of the github_runner_arc_orgs entry for the App's scope, or the name the role derives for that scope.
github_runner_arc_app_setup_keep_private_key:
type: bool
default: false
Expand Down
Loading
Loading