Skip to content

Commit

Permalink
Merge pull request #8 from tomeon/embedded-distro-tests
Browse files Browse the repository at this point in the history
Add support for testing on embedded distributions
  • Loading branch information
jose1711 authored Feb 27, 2024
2 parents a78b444 + 64f7bca commit d0d06dd
Show file tree
Hide file tree
Showing 36 changed files with 1,250 additions and 30 deletions.
26 changes: 26 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Ignore files and directories when creating archives with `git archive`.
# This will exclude matching files from the role as distributed on Ansible
# Galaxy.

/.github/ export-ignore
/.github/** export-ignore
/scripts/ export-ignore
/scripts/** export-ignore
/tmp/ export-ignore
/tmp/** export-ignore
/vagrant/ export-ignore
/vagrant/** export-ignore

# XXX do not exclude the `/tests/` hierarchy, as doing so breaks GitHub
# Actions. That is, do not do this:
# /tests/ export-ignore
# /tests/** export-ignore

*.nix export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
CONTRIBUTING.md export-ignore
Vagrantfile export-ignore
event.json export-ignore
flake.lock export-ignore
16 changes: 14 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ on:
pull_request:
push:
workflow_dispatch:
env:
DEBIAN_FRONTEND: noninteractive
jobs:
native:
name: Run test playbook
runs-on: ubuntu-22.04
strategy:
matrix:
image:
- alpine:3
- archlinux/archlinux
- debian:11
- debian:12
Expand All @@ -21,8 +24,17 @@ jobs:
- uses: actions/checkout@v3
- name: Install testing dependencies
run: |
{ apk add --no-cache ansible sudo ; } || :
{
echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections \
&& echo 'tzdata tzdata/Zones/Europe select Bratislava' | debconf-set-selections \
&& apt -y update \
&& apt -y install --no-install-recommends ansible sudo
} || :
{ pacman --noconfirm -Sy archlinux-keyring && pacman --noconfirm -Su && pacman --noconfirm -S ansible sudo ; } || :
{ apt -y update && apt -y install ansible sudo ; } || :
- name: Run test playbook
# Force `kodi_service_enabled` to `false`, as we can't count on being
# able to use the distribution's service manager/supervisor (e.g.
# OpenRC under Alpine Linux) in a container.
run: |
ansible-playbook -vv --become -c local -i localhost, ./tests/test.yml
ansible-playbook -vv --become -c local -i localhost, -e kodi_service_enabled=false ./tests/test.yml
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
/.vagrant/

# Vagrant boxes
*.box

# Disk images
*.img
*.qcow2

# libvirt domain log files
*.log
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog for `jose1711.kodi_ansible_role`

This file lists all notable changes to this project.

## Unreleased

### Added

- Support for Alpine Linux (#8).
- Project contribution guide (#8).
- Support `system` boolean and `gid` numeric attributes in `kodi_groups`
entries, permitting greater end-user control over group creation logic (#8).
- Add the new `kodi_service` variable for managing the Kodi service,
emphasizing that this role supports both system and non-systemd service
managers (#8).

### Changed

- Create (if necessary) all groups in `kodi_groups` (#8).
- Use the `service` module rather than the `systemd` for managing the Kodi
service, thus supporting (e.g.) OpenRC on Alpine Linux (#8).
- Deprecate (but do not yet remove or ignore) the `kodi_systemd_service`
variable in favor of using the newly-introduced `kodi_service` variable (#8).
- Exclude testing- and development-only files from the role archive distributed
via Ansible Galaxy (#8).

### Fixed

- Use `root` as `kodi_user` on LibreELEC (#8).
- Use the `wait_for` module instead of the `pause` module; now it should be
possible to apply this role under the `free` strategy (#8).
154 changes: 154 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
[`tests/test.yml`]: /tests/test.yml
[`.github/workflows/ci.yml`]: /.github/workflows/ci.yml
[Nix development shell]: #nix-development-shell

# Contributing to `jose1711.kodi_ansible_role`

## Nix development shell

This project includes a development environment using the [Nix package
manager](https://nixos.org). The environment provides tools for working with
the `jose1711.kodi-ansible-role` codebase, including Ansible itself, plus
[Vagrant](#vagrant-environment) and helpers for executing [GitHub Actions
jobs](#github-actions-suite).

Please see [here](https://nixos.org/download#download-nix) for instructions on
installing the Nix package manager, and see
[here](https://nix.dev/tutorials/first-steps/) for a getting-started-with-Nix
tutorial.

Once you have installed the Nix package manager, you can enter the
Nix development shell by running the following command from inside this
project's working tree:

```console
$ nix --extra-experimental-features 'flakes nix-command' develop
```

This will start a new shell with development tooling available (print the value
of the `PATH` environment variable inside the shell for some of the gory
details).

When you are done with your development environment, you can exit the shell as
usual (press `<ctrl-d>`, or run `exit`).

## Vagrant environment

This project includes a Vagrant environment for testing the
`jose1711.kodi_ansible_role` Ansible role on several Linux distributions. As
of this writing, those distributions are:

1. Alpine Linux (3.19)
2. Arch Linux
3. Debian (12 (bookworm))
4. Ubuntu (22.04 LTS (Jammy Jellyfish))
5. LibreELEC (11.0.6)
6. OSMC (20240205)

This Vagrant environment supports the
[`libvirt` provider](https://vagrant-libvirt.github.io/vagrant-libvirt/)[^provider-support].

[^provider-support]: Some of the Vagrant boxes employed in this environment may
support other providers, but the LibreELEC and OSMC boxes
support **only** `libvirt`.

To use this Vagrant environment, you will need to install Vagrant itself, as
well as the `vagrant-libvirt` provider plugin[^vagrant-in-nix-devshell]. Please
see the [`vagrant-libvirt` installation instructions](https://vagrant-libvirt.github.io/vagrant-libvirt/installation.html)
for guidance on installing and using the provider plugin.

[^vagrant-in-nix-devshell]: The [Nix development shell][] provides the
`vagrant` executable and the `vagrant-libvirt`
provider plugin.

Vagrant provisions its machines using the [`tests/test.yml`][] playbook, which
installs the prerequisites for running Ansible against those machines and then
applies this Ansible role.

> [!IMPORTANT]
> If you add support for a new distribution to this Ansible role, please try to
> add a Vagrant guest that runs this distribution, and, if necessary, update
> [`tests/test.yml`][] to install Ansible's dependencies on this guest.
> Additionally, please add a description of the distribution to the list above.
### Just-in-time Vagrant box builds

The LibreELEC and OSMC machines use custom Vagrant boxes built with scripts
that live in this project
([`create-libreelec-box`](/scripts/create-libreelec-box) and
[`create-osmc-box`](/scripts/create-osmc-box), respectively). The Vagrant
environment is configured to build these boxes on-demand; effectively, the
boxes are built upon `vagrant up` if they are not already installed. This
means that, in order to run these machines, you'll need to ensure that all
box-building prerequisites are available on your Vagrant host
system[^box-building-in-nix-devshell].

[^box-building-in-nix-devshell]: The [Nix development shell][] provides all of
these prerequisites.

#### Using OSMC under Vagrant

The OSMC box requires some special handling:

1. OSMC publishes images for a small number of platforms and architectures
([Raspberry Pi, Vero, and Apple TV](https://osmc.tv/download/)); there are
no prebuilt `x86_64` images (as there are [for LibreELEC](https://archive.libreelec.tv/archive/)).
2. The kernel included in OSMC images does not support certain modules required
for proper operation under [QEMU's `virt` Generic Virtual Platform](https://www.qemu.org/docs/master/system/riscv/virt.html).

To avoid having to run the OSMC guest under [QEMU's Raspberry Pi board emulation](https://www.qemu.org/docs/master/system/arm/raspi.html),
which is effective but very slow, we instead build a custom kernel and initial
RAM disk for the `aarch64` architecture and run the guest using the `virt`
platform. This makes the OSMC guest run faster, but comes with some downsides:

1. The kernel and initial RAM disk are built with [the NixOS module
system](https://nix.dev/tutorials/module-system/module-system.html), and
building the OSMC Vagrant box therefore requires installing and running the
Nix package manager.
2. If the `libvirt` daemon runs as an unprivileged user, it may not be able to
load the kernel and initial RAM disk from the location under
[`VAGRANT_HOME`](https://developer.hashicorp.com/vagrant/docs/other/environmental-variables#vagrant_home)
where Vagrant extracted the `.box` file, so the OSMC box includes a
`Vagrantfile` that, among other things, copies the kernel and initial RAM
disk to a world-readable location before Vagrant boots the OSMC guest (and
attempts to clean up these files before Vagrant exits).

> [!WARNING]
> There is a bug in Vagrant that may cause problems on the first boot of the
> OSMC machine. If the first `vagrant up` (or `vagrant up osmc`) hangs or
> crashes, please try re-running the command.
## GitHub Actions suite

This project uses [GitHub Actions](https://docs.github.com/en/actions) for
automated testing. The main workflow definition is
[`.github/workflows/ci.yml`][]. As of this writing, the workflow applies this
Ansible role to containers based upon the following images:

1. `alpine:3`
2. `archlinux/archlinux`
3. `debian:11`
4. `debian:12`
5. `ubuntu:22.04`
6. `ubuntu:23.04`

Please ensure that the workflow succeeds when run against your branch.

You can run the GitHub Actions workflow locally using
[`act`](https://github.com/nektos/act):[^act-in-nix-devshell]

[^act-in-nix-devshell]: The [Nix development shell][] provides the `act`
executable.

```console
$ act -j native
```

This will run the `native` job from [`.github/workflows/ci.yml`][].

> [!IMPORTANT]
> If you add support for a new distribution to this Ansible role, please try to
> find a Docker image that uses this distribution and add it to the list of
> images in [`.github/workflows/ci.yml`][], and, if necessary, update
> [`tests/test.yml`][] to install Ansible's dependencies on containers using
> this image. Additionally, please add the new image to the list above.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Role Variables
[`vars/default.yml`]: vars/default.yml

- `kodi_user`: the user account used for running the Kodi service on the target machine. Default: `"kodi"`.
- `kodi_groups`: if `kodi_user` is created by this role, it will be added to these groups. Default: `["audio", "video", "input"]`.
- `kodi_groups`: if `kodi_user` is created by this role, it will be added to these groups. Entries in the list may be (a) strings (`"somegroup"`) or (b) dictionaries of the form `{"name": "someuser", "gid": 11111, "system": False}`, where `name` is the group name, `gid` is the GID, and `system` is a boolean specifying whether the group is a so-called "system" group; see `ansible-doc group` for more on the meaning of these parameters. Note that, in the dictionary form, `gid` and `system` may be omitted. Default: `["audio", "video", "input"]`.
- `kodi_shell`: if `kodi_user` is created by this role, it will use this value as its login shell. Default: `"/bin/bash"`.
- `kodi_user_create`: whether to create the user account specified in `kodi_user`. Default: `True` (except on LibreELEC and OSMC, where it is set to `False`).
- `kodi_data_dir`: path to the directory storing Kodi data (addons, user data, etc.). Default: `~{{ kodi_user }}/.kodi` (the `.kodi` subdirectory of the home directory of the `kodi_user` user).
Expand Down Expand Up @@ -107,7 +107,9 @@ Role Variables
- `kodi_subtitles_languages`: Comma-separated list of subtitle languages. Default: not defined.
- `kodi_weather_provider`: Hostname of the weather data provider. Default: not defined.
- `kodi_include_default_config`: a boolean indicating whether or not to include the variable definitions from [`vars/default.yml`][].yml). Default: `False`.
- `kodi_systemd_service`: the name of the systemd service running Kodi. Default: not defined, except on LibreELEC where it is set to `kodi` and on OSMC where it is set to `mediacenter`.
- `kodi_systemd_service`: the name of the systemd service running Kodi. Default: not defined. **This variable is deprecated**; please use `kodi_service` instead.
- `kodi_service`: the name of the service running Kodi. Default: not defined, except on Alpine and LibreELEC where it is set to `kodi` and on OSMC where it is set to `mediacenter`. Note that setting this variable to `{{ none }}` or `{{ omit }}` will disable service management even on systems where it would be attempted by default.
- `kodi_service_enabled`: whether to attempt to manage Kodi via the service specified in `kodi_service`. By default, `True` by default on systems where `kodi_service` is defined and set to a value other than `{{ omit }}` or `{{ none }}`, and `False` otherwise.
- `kodi_check_process_cmd`: the command to use for checking whether Kodi is currently running (Kodi must be shut off before changing its configuration). See [the platform-specific variables files](/vars) for the values of this variable.
- `kodi_check_process_executable`: the executable to use for running `kodi_check_process_cmd`. See [the platform-specific variables files](/vars) for the values of this variable.
- `kodi_query_version_cmd`: the command to use for determining the version of Kodi in use. This command only runs if `kodi_version` is undefined. See [the platform-specific variables files](/vars) for the values of this variable.
Expand Down Expand Up @@ -156,6 +158,12 @@ Example Playbook
- { role: jose1711.kodi_ansible_role, kodi_language: en_US }
```

Contributing to `jose1711.kodi-ansible-role`
--------------------------------------------

Please see [`CONTRIBUTING.md`](/CONTRIBUTING.md) for notes on contributing to
this project.

License
-------

Expand Down
80 changes: 80 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
require_relative 'vagrant/lib/vagrant/libreelec/plugin'

# All boxes used below support libvirt; the LibreELEC and OSMC boxes *only*
# support libvirt.
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'

# If possible, tie on-demand box building to box addition events; otherwise,
# perform builds on `vagrant up`.
def trigger_box_build(config, &block)
if Vagrant.version?(">= 2.4.0") || ENV.fetch('VAGRANT_EXPERIMENTAL', '').split(/,/).include?('typed_triggers')
config.trigger.before(%I[Vagrant::Action::Builtin::BoxAdd Vagrant::Action::Building::HandleBox], type: :action, &block)
else
config.trigger.before(:up, &block)
end
end

Vagrant.configure(2) do |config|
# We don't need to sync this folder onto the guest, and disabling the synced
# folder prevents Vagrant from bailing out on guests that lack `rsync` (e.g.
# LibreELEC).
config.vm.synced_folder '.', '/vagrant', disabled: true

config.vm.provider :libvirt do |domain|
# Listen on all addresses
domain.graphics_ip = '::'
end

config.vm.define :alpine do |alpine|
alpine.vm.box = 'generic/alpine319'
end

config.vm.define :archlinux do |archlinux|
archlinux.vm.box = 'archlinux/archlinux'
end
Expand All @@ -11,6 +41,56 @@ Vagrant.configure(2) do |config|
ubuntu.vm.box = 'generic/ubuntu2204'
end

config.vm.define :libreelec do |libreelec|
box = File.expand_path('tmp/libreelec/LibreELEC-Generic.x86_64-11.0.6.box', __dir__)

trigger_box_build(libreelec) do |t|
t.name = 'Create LibreELEC box'
t.info = "Ensuring that the LibreELEC box at #{box.inspect} exists"
unless File.exist?(box)
t.run = {
path: File.expand_path('scripts/create-libreelec-box', __dir__),
args: [box],
}
end
end

libreelec.vm.box_url = "file://#{box}"
libreelec.vm.box = 'libreelec'
end

config.vm.define :osmc do |osmc|
box = File.expand_path('tmp/osmc/OSMC_TGT_rbp4_20240205.box', __dir__)

trigger_box_build(osmc) do |t|
t.name = 'Create OSMC box'
t.info = "Ensuring that the OSMC box at #{box.inspect} exists"
t.warn = <<~WARN.gsub(/(?<!^)\n/, ' ')
The `osmc` box's Vagrantfile defines a Vagrant trigger that installs a
custom kernel and initial ramdisk to a location accessible to the libvirt daemon.
This trigger *must* run in order to boot machines using the `osmc` box.
A bug in Vagrant prevents triggers defined in a box's Vagrantfile from
running when the box is first added; see https://github.com/hashicorp/vagrant/issues/11901.
If your OSMC virtual machine hangs upon boot, or if Vagrant crashes
bringing up the OSMC virtual machine, please try running `vagrant up` a
second time, as Vagrant will then properly execute the `up` trigger
mentioned above.
WARN
unless File.exist?(box)
t.run = {
path: File.expand_path('scripts/create-osmc-box', __dir__),
args: [box],
}
end
end

osmc.vm.box_url = "file://#{box}"
osmc.vm.box = 'osmc'
end

config.vm.provision :ansible, type: 'ansible' do |ansible|
ansible.playbook = 'tests/test.yml'
ansible.verbose = 'vv'
Expand Down
Loading

0 comments on commit d0d06dd

Please sign in to comment.