Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7e6d22c
fix: add runner user alias for GARM compatibility (ISD-5726)
florentianayuwono May 27, 2026
f7ba88d
chore: bump version to 0.14.1 and update changelog for ISD-5726
florentianayuwono May 27, 2026
2c1403a
Merge branch 'main' into fix/garm-image-incompatibility-isd-5726
florentianayuwono May 27, 2026
7decb67
update changelog
florentianayuwono May 27, 2026
a9fdb20
test: update cloud-init snapshot for runner user alias (ISD-5726)
florentianayuwono May 27, 2026
a5c21f2
docs: add inline comments explaining useradd flags for runner alias
florentianayuwono May 27, 2026
1eb55fb
fix: add --non-unique flag and /home/runner symlink for GARM compatib…
florentianayuwono May 27, 2026
09571dd
fix: robustly create /home/runner symlink for GARM compatibility
florentianayuwono May 27, 2026
a3f64da
Merge branch 'main' into fix/garm-image-incompatibility-isd-5726
florentianayuwono May 29, 2026
a67328b
TMP: Remove cleanup of image to build a test image
yhaliaw May 29, 2026
d96359f
Revert "TMP: Remove cleanup of image to build a test image"
yhaliaw May 29, 2026
22d9225
remove cleanup
florentianayuwono May 29, 2026
5c3d59c
Merge branch 'main' into fix/garm-image-incompatibility-isd-5726
florentianayuwono Jun 2, 2026
42570c0
fix: run cloud-init clean before snapshot to ensure fresh cloud-init …
florentianayuwono Jun 2, 2026
9d253c7
add flags
florentianayuwono Jun 2, 2026
7d3db77
enable build
florentianayuwono Jun 2, 2026
6797aaa
add flag
florentianayuwono Jun 2, 2026
4e4fe5e
Apply suggestions from code review
florentianayuwono Jun 11, 2026
67330aa
address review
florentianayuwono Jun 11, 2026
39c2e58
add comment
florentianayuwono Jun 12, 2026
5eac226
fix docs link
florentianayuwono Jun 12, 2026
ad088c4
pin relation hook
florentianayuwono Jun 12, 2026
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
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Applicable spec: <link>

### Checklist

- [ ] The [charm style guide](https://juju.is/docs/sdk/styleguide) was applied
- [ ] The [charm style guide](https://documentation.ubuntu.com/juju/3.6/reference/charm) was applied
- [ ] The [contributing guide](https://github.com/canonical/is-charms-contributing-guide) was applied
- [ ] The changes are compliant with [ISD054 - Managing Charm Complexity](https://discourse.charmhub.io/t/specification-isd014-managing-charm-complexity/11619)
- [ ] The documentation for charmhub is updated.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration_test_app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- uses: canonical/setup-lxd@v0.1.3
- uses: actions/setup-python@v6
with:
python-version: '3.14'
python-version: "3.14"
- name: Install tox
run: |
sudo apt-get update
Expand Down
2 changes: 1 addition & 1 deletion app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[project]
name = "github-runner-image-builder"
version = "0.14.0"
version = "0.14.1"
authors = [
{ name = "Canonical IS DevOps", email = "is-devops-team@canonical.com" },
]
Expand Down
6 changes: 6 additions & 0 deletions app/src/github_runner_image_builder/openstack_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,12 @@ def run(
script_secrets=image_config.script_config.script_secrets,
ssh_conn=ssh_conn,
)
# Cleaning is needed to be compatible with GARM
logger.info("Cleaning cloud-init state before snapshot.")
ssh_conn.run(
"sudo cloud-init clean --logs --machine-id --seed --configs all",
timeout=EXTERNAL_SCRIPT_GENERAL_TIMEOUT,
)
Comment thread
florentianayuwono marked this conversation as resolved.
Comment on lines +348 to +352

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Adding an explanatory comment that this is necessary to be compatible with GARM would be nice, to avoid the code being removed (and currently there are no tests that would catch that).

_shutoff_server(conn=conn, server=builder)
image = store.create_snapshot(
cloud_name=cloud_config.cloud_name,
Expand Down
28 changes: 28 additions & 0 deletions app/src/github_runner_image_builder/templates/cloud-init.sh.j2
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,34 @@ function configure_system_users() {
/usr/sbin/groupadd -f microk8s
/usr/sbin/groupadd -f docker
/usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo ubuntu

# Create runner user as alias to ubuntu for GARM compatibility.
# GARM expects a runner user with /home/runner/actions-runner path.
echo "Configuring runner user alias for GARM compatibility"
UBUNTU_UID=$(/usr/bin/id -u ubuntu)
UBUNTU_GID=$(/usr/bin/id -g ubuntu)
Comment thread
florentianayuwono marked this conversation as resolved.
# --non-unique: allow reusing ubuntu's UID (duplicate UIDs are rejected by default).
# --uid/--gid: share ubuntu's UID/GID so both users have identical file permissions.
# --no-create-home: skip creating /home/runner; runner's home is set to /home/ubuntu instead.
if /usr/bin/id -u runner >/dev/null 2>&1; then
echo "runner user already exists; ensuring UID/GID/home match ubuntu"
/usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu runner
else
/usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu runner
fi
/usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo runner
# Symlink /home/runner -> /home/ubuntu so GARM's hardcoded /home/runner/actions-runner path resolves correctly.
# If /home/runner exists as a plain directory (not a symlink), remove it first; otherwise ln -sfnT cannot
# replace it. A non-empty directory is an unexpected state, so fail with a clear error instead of letting
# ln fail later with a less specific message.
# -T: treat destination as a file path, never as a directory target.
if [ -d /home/runner ] && ! [ -L /home/runner ]; then
if ! rmdir /home/runner 2>/dev/null; then
echo "ERROR: /home/runner exists and is not empty; cannot replace it with a symlink to /home/ubuntu" >&2
return 1
fi
fi
/usr/bin/ln -sfnT /home/ubuntu /home/runner
}


Expand Down
28 changes: 28 additions & 0 deletions app/tests/unit/test_openstack_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,34 @@ def test__generate_cloud_init_script(
/usr/sbin/groupadd -f microk8s
/usr/sbin/groupadd -f docker
/usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo ubuntu

# Create runner user as alias to ubuntu for GARM compatibility.
# GARM expects a runner user with /home/runner/actions-runner path.
echo "Configuring runner user alias for GARM compatibility"
UBUNTU_UID=$(/usr/bin/id -u ubuntu)
UBUNTU_GID=$(/usr/bin/id -g ubuntu)
# --non-unique: allow reusing ubuntu's UID (duplicate UIDs are rejected by default).
# --uid/--gid: share ubuntu's UID/GID so both users have identical file permissions.
# --no-create-home: skip creating /home/runner; runner's home is set to /home/ubuntu instead.
if /usr/bin/id -u runner >/dev/null 2>&1; then
echo "runner user already exists; ensuring UID/GID/home match ubuntu"
/usr/sbin/usermod --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --home /home/ubuntu runner
else
/usr/sbin/useradd --non-unique --uid "$UBUNTU_UID" --gid "$UBUNTU_GID" --no-create-home --home-dir /home/ubuntu runner
fi
/usr/sbin/usermod --append --groups docker,microk8s,lxd,sudo runner
# Symlink /home/runner -> /home/ubuntu so GARM's hardcoded /home/runner/actions-runner path resolves correctly.
# If /home/runner exists as a plain directory (not a symlink), remove it first; otherwise ln -sfnT cannot
# replace it. A non-empty directory is an unexpected state, so fail with a clear error instead of letting
# ln fail later with a less specific message.
# -T: treat destination as a file path, never as a directory target.
if [ -d /home/runner ] && ! [ -L /home/runner ]; then
if ! rmdir /home/runner 2>/dev/null; then
echo "ERROR: /home/runner exists and is not empty; cannot replace it with a symlink to /home/ubuntu" >&2
return 1
fi
fi
/usr/bin/ln -sfnT /home/ubuntu /home/runner
}}


Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<!-- vale Canonical.007-Headings-sentence-case = NO -->

## [#223 Fix GARM image incompatibility](https://github.com/canonical/github-runner-image-builder-operator/pull/223) (2026-05-27)

- Add `runner` user as an alias to the `ubuntu` user (same UID/GID, same home directory) so GARM can boot runners from images produced by this charm.

## [#213 Fix proxy setup]

- Fix proxy setup for image-relation joined hook.
Expand All @@ -11,6 +15,7 @@
## [#221 Add resource recommendation for charm deployment](https://github.com/canonical/github-runner-image-builder-operator/pull/221)

<!-- vale Canonical.013-Spell-out-numbers-below-10 = NO -->

- Add 2 vCPUs, 8 GiB RAM, and 20 GiB disk OpenStack flavor recommendation.
<!-- vale Canonical.013-Spell-out-numbers-below-10 = YES -->

Expand Down
6 changes: 3 additions & 3 deletions docs/reference/charm-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ storing OpenStack credentials on disk and initializing the image-builder applica
2. [config-changed](https://documentation.ubuntu.com/juju/3.6/reference/hook/#config-changed): The configuration of the charm has changed. The charm applies the configuration (e.g. changes to proxy or OpenStack credentials).
3. `run`: This is a custom event that is periodically triggered by a cron job. It is used to call the image-builder application to build the image.
4. `run-action`: This is an action event fired by the user to manually trigger the image-builder to build the image.
5. `image-relation-changed`: This is a [relation event](https://juju.is/docs/sdk/relation-events) that fires when relation data changes. It also triggers the image-builder to build the image.
5. `image-relation-changed`: This is a [relation event](https://documentation.ubuntu.com/juju/3.6/reference/hook/#relation-hooks) that fires when relation data changes. It also triggers the image-builder to build the image.
Once the build is complete, the image-builder will upload the image taking into account the newly changed relation data (e.g. if the OpenStack project has changed).

> See more about events in the Juju docs: [Event](https://juju.is/docs/sdk/event)
> See more about events in the ops docs: [Event](https://documentation.ubuntu.com/juju/3.6/reference/hook/)

## Charm code overview

The `src/charm.py` is the default entry point for a charm and has the GithubRunnerImageBuilderCharm Python class which inherits from CharmBase. CharmBase is the base class
from which all charms are formed, defined by [Ops](https://juju.is/docs/sdk/ops) (Python framework for developing charms).
from which all charms are formed, defined by [Ops](https://documentation.ubuntu.com/ops/latest/) (Python framework for developing charms).

> See more in the Juju docs: [Charm](https://documentation.ubuntu.com/juju/3.6/reference/charm/)

Expand Down
Loading