Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### Data types

* [`Rootless_gitlab_runner::Apt_source`](#Rootless_gitlab_runner--Apt_source): An apt source's location and signing-key endpoint.
* [`Rootless_gitlab_runner::Username`](#Rootless_gitlab_runner--Username): A strict POSIX-portable Linux username.

## Classes
Expand Down Expand Up @@ -234,14 +235,8 @@ Struct[{
install => Array[String[1]],
sources => Struct[{
manage => Boolean,
docker => Struct[{
location => Stdlib::HTTPUrl,
key_source => Stdlib::HTTPUrl,
}],
gitlab_runner => Struct[{
location => Stdlib::HTTPUrl,
key_source => Stdlib::HTTPUrl,
}],
docker => Rootless_gitlab_runner::Apt_source,
gitlab_runner => Rootless_gitlab_runner::Apt_source,
}],
}]
```
Expand All @@ -253,7 +248,7 @@ Options:
* **:install** `Array[String[1]]`: Packages to ensure installed. The empty default installs nothing. Install
only: the module never removes packages absent from the list and never
pins or upgrades.
* **:sources** `Hash`: The apt sources the `install` list installs from (Docker's and GitLab
* **:sources** `Struct[{ manage => Boolean, docker => Apt_source, gitlab_runner => Apt_source }]`: The apt sources the `install` list installs from (Docker's and GitLab
Runner's, via `puppetlabs/apt`). `sources.manage` (default false) decides
ownership: keep it off where apt sources are owned elsewhere (central
configuration management, e.g. Foreman/Katello, or a mirror). The
Expand Down Expand Up @@ -317,11 +312,11 @@ Options:
* **:manage** `Boolean`: Whether to manage the runner system service, its privilege-drop systemd
drop-in, and the configuration directory's mode so a privilege-dropped
manager can traverse to its configuration file. Default false.
* **:environment** `Optional[Array[String]]`: `Environment=` lines (KEY=value) rendered into the service drop-in. When
* **:environment** `Optional[Array[Pattern[/\A[^\r\n]+\z/]]]`: `Environment=` lines (KEY=value) rendered into the service drop-in. When
unset, defaults to pointing DOCKER_HOST at the rootless docker socket
derived from `runner_account.uid`. Each line must be a single line — a
value containing a newline is rejected, so it cannot inject an extra
systemd directive into the drop-in.
derived from `runner_account.uid`. The `Pattern` enforces the type: each
line must be non-empty and single-line — a value containing a newline is
rejected, so it cannot inject an extra systemd directive into the drop-in.
* **:timeout_stop_sec** `Optional[Variant[Integer[0], String[1]]]`: `TimeoutStopSec=` written into the manager service drop-in — how long
systemd waits for a graceful drain before escalating to `SIGKILL`. Unset
by default, so systemd's `DefaultTimeoutStopSec` (typically 90s) applies;
Expand Down Expand Up @@ -360,10 +355,11 @@ Options:
(`/usr/local/sbin/rootless-gitlab-runner-apply`), the single definition
of the apply command for timer-driven and manual runs. Default false.
* **:control_repository_path** `Stdlib::Absolutepath`: Root-owned checkout of the control repository on the host (the apply and
self-update target). The apply's manifest, module directory and Hiera
configuration derive strictly from the documented layout beneath it
(`puppet/manifests/site.pp`, `puppet/modules`, `puppet/hiera.yaml`).
Default `/opt/gitlab-runner-infra`.
self-update target). The apply's manifest, module directory, Hiera
configuration and optional `Puppetfile` derive strictly from the documented
layout beneath it (`puppet/manifests/site.pp`, `puppet/modules`,
`puppet/hiera.yaml`, and `Puppetfile` at the root — r10k installs it each
tick when present). Default `/opt/gitlab-runner-infra`.
* **:control_repository_branch** `String[1]`: Branch the self-update loop follows (protected, signed). Default `main`.
* **:puppet_confdir** `Stdlib::Absolutepath`: Isolated Puppet confdir for the apply — never the central agent's.
Default `/etc/gitlab-runner-infra/puppet`.
Expand All @@ -375,7 +371,7 @@ non-login apply can find them. Default `/opt/puppetlabs/bin`; the
AIO-reuse topology sets `/opt/puppetlabs/puppet/bin` (gem executables
live there, not in the symlink farm).
* **:healthcheck_interval** `String[1]`: systemd time span between healthcheck runs. Default `15min`.
* **:self_update** `Hash`: The self-update loop: `manage` (default false) installs a oneshot
* **:self_update** `Struct[{ manage => Boolean, apply_interval => String[1], apply_timeout => String[1] }]`: The self-update loop: `manage` (default false) installs a oneshot
service + timer that fetch the control repository, verify the commit
signature, reset to the remote branch, run `r10k puppetfile install` and
re-apply — plus the healthcheck script + timer. Only valid on a
Expand Down Expand Up @@ -427,6 +423,23 @@ the raw string to escape

## Data types

### <a name="Rootless_gitlab_runner--Apt_source"></a>`Rootless_gitlab_runner::Apt_source`

`location` and `key_source` are verbatim `apt::source` parameter names: the
repository URL (suite = OS codename) and the URL of its armored signing key,
stored as an apt keyring. Both the Docker and GitLab Runner sources under
`packages.sources` share this shape, pointing at a vendor repository or a
mirror.

Alias of

```puppet
Struct[{
location => Stdlib::HTTPUrl,
key_source => Stdlib::HTTPUrl,
}]
```

### <a name="Rootless_gitlab_runner--Username"></a>`Rootless_gitlab_runner::Username`

The runner user is interpolated raw into shell execs (the subuid/subgid
Expand Down
29 changes: 17 additions & 12 deletions manifests/config.pp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
class rootless_gitlab_runner::config {
assert_private()

$runner_name = $rootless_gitlab_runner::runner_account['name']
# Primary group, defaulting to the account name (derived in init.pp). Owners
# stay the account name; only the group ownerships derive from it.
# Identity locals hoisted in init.pp. Owners stay the account name; only the
# group ownerships derive from $runner_group (the account's primary group,
# defaulting to the name).
$runner_name = $rootless_gitlab_runner::runner_name
$runner_group = $rootless_gitlab_runner::runner_group
$runner_home = $rootless_gitlab_runner::runner_account['home']
$runner_home = $rootless_gitlab_runner::runner_home
$configuration_file_path = $rootless_gitlab_runner::configuration_file['path']

$socket_path = $rootless_gitlab_runner::socket_path
Expand Down Expand Up @@ -138,14 +139,18 @@
# side effects — and Puppet's file type never creates parents. Own the whole
# parent chain explicitly (ordered after the bring-up by the class order),
# so the first apply on a fresh host can always place the drop-in, even if a
# future setuptool changes what it creates. Puppet autorequires each managed
# parent, so the chain applies top-down.
$dropin_path = "${runner_home}/.config/systemd/user/docker.service.d/no-detach-netns.conf"
$dropin_dir = dirname($dropin_path)
$user_units_dir = dirname($dropin_dir)
$user_systemd_dir = dirname($user_units_dir)
$user_config_dir = dirname($user_systemd_dir)
file { [$user_config_dir, $user_systemd_dir, $user_units_dir, $dropin_dir]:
# future setuptool changes what it creates. The chain is derived bottom-up in
# init.pp (each directory its parent plus one element) and shared with the
# rootless bring-up; Puppet autorequires each managed parent, so it still
# applies top-down. Only the drop-in directory and file are leaves here.
$dropin_dir = "${rootless_gitlab_runner::user_systemd_dir}/docker.service.d"
$dropin_path = "${dropin_dir}/no-detach-netns.conf"
file { [
$rootless_gitlab_runner::user_config_dir,
$rootless_gitlab_runner::user_config_systemd_dir,
$rootless_gitlab_runner::user_systemd_dir,
$dropin_dir,
]:
ensure => directory,
owner => $runner_name,
group => $runner_group,
Expand Down
81 changes: 51 additions & 30 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
# Packages to ensure installed. The empty default installs nothing. Install
# only: the module never removes packages absent from the list and never
# pins or upgrades.
# @option packages [Hash] :sources
# @option packages [Struct[{ manage => Boolean, docker => Apt_source, gitlab_runner => Apt_source }]] :sources
# The apt sources the `install` list installs from (Docker's and GitLab
# Runner's, via `puppetlabs/apt`). `sources.manage` (default false) decides
# ownership: keep it off where apt sources are owned elsewhere (central
Expand Down Expand Up @@ -173,12 +173,12 @@
# Whether to manage the runner system service, its privilege-drop systemd
# drop-in, and the configuration directory's mode so a privilege-dropped
# manager can traverse to its configuration file. Default false.
# @option runner_service [Optional[Array[String]]] :environment
# @option runner_service [Optional[Array[Pattern[/\A[^\r\n]+\z/]]]] :environment
# `Environment=` lines (KEY=value) rendered into the service drop-in. When
# unset, defaults to pointing DOCKER_HOST at the rootless docker socket
# derived from `runner_account.uid`. Each line must be a single line — a
# value containing a newline is rejected, so it cannot inject an extra
# systemd directive into the drop-in.
# derived from `runner_account.uid`. The `Pattern` enforces the type: each
# line must be non-empty and single-line — a value containing a newline is
# rejected, so it cannot inject an extra systemd directive into the drop-in.
# @option runner_service [Optional[Variant[Integer[0], String[1]]]] :timeout_stop_sec
# `TimeoutStopSec=` written into the manager service drop-in — how long
# systemd waits for a graceful drain before escalating to `SIGKILL`. Unset
Expand All @@ -196,10 +196,11 @@
# of the apply command for timer-driven and manual runs. Default false.
# @option standalone [Stdlib::Absolutepath] :control_repository_path
# Root-owned checkout of the control repository on the host (the apply and
# self-update target). The apply's manifest, module directory and Hiera
# configuration derive strictly from the documented layout beneath it
# (`puppet/manifests/site.pp`, `puppet/modules`, `puppet/hiera.yaml`).
# Default `/opt/gitlab-runner-infra`.
# self-update target). The apply's manifest, module directory, Hiera
# configuration and optional `Puppetfile` derive strictly from the documented
# layout beneath it (`puppet/manifests/site.pp`, `puppet/modules`,
# `puppet/hiera.yaml`, and `Puppetfile` at the root — r10k installs it each
# tick when present). Default `/opt/gitlab-runner-infra`.
# @option standalone [String[1]] :control_repository_branch
# Branch the self-update loop follows (protected, signed). Default `main`.
# @option standalone [Stdlib::Absolutepath] :puppet_confdir
Expand All @@ -216,7 +217,7 @@
# live there, not in the symlink farm).
# @option standalone [String[1]] :healthcheck_interval
# systemd time span between healthcheck runs. Default `15min`.
# @option standalone [Hash] :self_update
# @option standalone [Struct[{ manage => Boolean, apply_interval => String[1], apply_timeout => String[1] }]] :self_update
# The self-update loop: `manage` (default false) installs a oneshot
# service + timer that fetch the control repository, verify the commit
# signature, reset to the remote branch, run `r10k puppetfile install` and
Expand Down Expand Up @@ -246,14 +247,8 @@
install => Array[String[1]],
sources => Struct[{
manage => Boolean,
docker => Struct[{
location => Stdlib::HTTPUrl,
key_source => Stdlib::HTTPUrl,
}],
gitlab_runner => Struct[{
location => Stdlib::HTTPUrl,
key_source => Stdlib::HTTPUrl,
}],
docker => Rootless_gitlab_runner::Apt_source,
gitlab_runner => Rootless_gitlab_runner::Apt_source,
}],
}] $packages,
Struct[{
Expand Down Expand Up @@ -301,22 +296,28 @@
]))
}

# The account's primary group, derived once and read as a group by every
# concern (the group resource and the user's gid in user.pp, and every
# managed file's group in config.pp). The data layer cannot express "same as
# the account name", so the default is an absent key and the fallback lives
# here: an unset group follows the account name — correct by construction
# where the module creates the account — while a set group names the
# differently named primary group of an externally provisioned account.
$runner_group = pick($runner_account['group'], $runner_account['name'])
# The runner account's identity, hoisted once so every concern reads a local
# instead of re-subscripting runner_account per file. The primary group is
# read as a group everywhere the account name used to double as one (the
# group resource and the user's gid in user.pp, every managed file's group in
# config.pp); it defaults to the account name, since the data layer cannot
# express "same as the name" — an unset group falls back here, correct by
# construction where the module creates the account, while a set group names
# the differently named primary group of an externally provisioned account.
$runner_name = $runner_account['name']
$runner_home = $runner_account['home']
$runner_group = pick($runner_account['group'], $runner_name)

# Defaults merged under every runner entry; keys set on the entry win.
$effective_runners = $runners.map |$r| { $runner_defaults + $r }

# Rootless runtime paths derived from the uid where known: the module
# itself installs the daemon socket at /run/user/<uid>/docker.sock, so the
# path is derivation, not configuration. An exotic external socket is
# expressible via runner_service.environment.
# path is derivation, not configuration. The one escape hatch is narrow:
# runner_service.environment can point the manager service's DOCKER_HOST at
# a socket elsewhere. Nothing else follows it — the socket_mount volume and
# the healthcheck probe keep using the derived path — so it is not a general
# "external daemon" switch.
if $runner_account['uid'] =~ Integer {
$runtime_dir = "/run/user/${runner_account['uid']}"
$socket_path = "${runtime_dir}/docker.sock"
Expand All @@ -329,8 +330,8 @@
# root Puppet provides neither XDG_RUNTIME_DIR nor DBUS_SESSION_BUS_ADDRESS,
# and systemctl --user / the setuptool fail without them.
$runner_user_env = [
"HOME=${runner_account['home']}",
"USER=${runner_account['name']}",
"HOME=${runner_home}",
"USER=${runner_name}",
"XDG_RUNTIME_DIR=${runtime_dir}",
"DBUS_SESSION_BUS_ADDRESS=unix:path=${runtime_dir}/bus",
]
Expand All @@ -342,6 +343,11 @@
]))
}

# The inner `undef => []` arm (environment unset and no derivable socket) is
# reached only when runner_service.manage is off: the fail-loud guard above
# rejects that exact combination while the service is managed. So [] is the
# never-consumed value that keeps the selector total — service.pp renders the
# drop-in only under manage — not a deliberately empty Environment.
$real_service_environment = $runner_service['environment'] ? {
undef => $socket_path ? {
undef => [],
Expand All @@ -350,6 +356,21 @@
default => $runner_service['environment'],
}

# The runner manager's systemd unit name is the one the gitlab-runner package
# defines, not configuration. Derived once and shared by the service class
# and the healthcheck so the literal has a single home.
$service_name = 'gitlab-runner'

# The runner account's systemd user unit directory (~/.config/systemd/user)
# and its parents, built bottom-up from the home so each directory is its
# parent plus one path element (no dirname() walk back up). Puppet's file
# type never creates parents, so config.pp manages the whole chain to place
# the no-detach-netns drop-in; rootless_docker.pp reads $user_systemd_dir for
# the docker.service the setuptool generates there.
$user_config_dir = "${runner_home}/.config"
$user_config_systemd_dir = "${user_config_dir}/systemd"
$user_systemd_dir = "${user_config_systemd_dir}/user"

contain rootless_gitlab_runner::apt_repos
contain rootless_gitlab_runner::packages
contain rootless_gitlab_runner::user
Expand Down
10 changes: 6 additions & 4 deletions manifests/rootless_docker.pp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
assert_private()

if $rootless_gitlab_runner::rootless_docker['manage'] {
$runner_user = $rootless_gitlab_runner::runner_account['name']
$runner_home = $rootless_gitlab_runner::runner_account['home']
$runner_user = $rootless_gitlab_runner::runner_name
$runner_home = $rootless_gitlab_runner::runner_home
$runner_uid = $rootless_gitlab_runner::runner_account['uid']
$runtime_dir = $rootless_gitlab_runner::runtime_dir
$user_env = $rootless_gitlab_runner::runner_user_env
# The setuptool ships at the path the docker-ce-rootless-extras package
# defines; if it is absent the install exec below fails loud (a missing
# command is a command failure, not a guard failure).
$setuptool = '/usr/bin/dockerd-rootless-setuptool.sh'
# The user unit the setuptool generates, under the shared systemd user dir.
$docker_user_unit = "${rootless_gitlab_runner::user_systemd_dir}/docker.service"

# A fresh docker-ce install leaves the rootful system daemon running: the
# package postinst starts docker.service + docker.socket as root, and
Expand Down Expand Up @@ -128,8 +130,8 @@
# success. Assert the user unit exists after install, turning a silent skip
# into a hard failure.
exec { 'rootless_gitlab_runner setuptool install':
command => "${setuptool} install && test -f ${runner_home}/.config/systemd/user/docker.service",
creates => "${runner_home}/.config/systemd/user/docker.service",
command => "${setuptool} install && test -f ${docker_user_unit}",
creates => $docker_user_unit,
user => $runner_user,
environment => $user_env,
path => ['/usr/bin', '/bin'],
Expand Down
15 changes: 8 additions & 7 deletions manifests/self_update.pp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
group => 'root',
mode => '0755',
content => epp('rootless_gitlab_runner/apply.sh.epp', {
'control_repository_path' => $control_repository_path,
'manifest_path' => "${control_repository_path}/puppet/manifests/site.pp",
'module_directory' => "${control_repository_path}/puppet/modules",
'hiera_config' => "${control_repository_path}/puppet/hiera.yaml",
'confdir' => $standalone['puppet_confdir'],
'vardir' => $standalone['puppet_vardir'],
'puppetfile_path' => "${control_repository_path}/Puppetfile",
'manifest_path' => "${control_repository_path}/puppet/manifests/site.pp",
'module_directory' => "${control_repository_path}/puppet/modules",
'hiera_config' => "${control_repository_path}/puppet/hiera.yaml",
'confdir' => $standalone['puppet_confdir'],
'vardir' => $standalone['puppet_vardir'],
}),
}
}
Expand All @@ -49,11 +49,12 @@
group => 'root',
mode => '0755',
content => epp('rootless_gitlab_runner/healthcheck.sh.epp', {
'runner_name' => $rootless_gitlab_runner::runner_account['name'],
'runner_name' => $rootless_gitlab_runner::runner_name,
'runtime_dir' => $rootless_gitlab_runner::runtime_dir,
'socket_path' => $rootless_gitlab_runner::socket_path,
'control_repository_path' => $control_repository_path,
'control_repository_branch' => $standalone['control_repository_branch'],
'service_name' => $rootless_gitlab_runner::service_name,
}),
}

Expand Down
Loading