diff --git a/.ostree/README.md b/.ostree/README.md new file mode 100644 index 0000000..f5e6931 --- /dev/null +++ b/.ostree/README.md @@ -0,0 +1,3 @@ +*NOTE*: The `*.txt` files are used by `get_ostree_data.sh` to create the lists +of packages, and to find other system roles used by this role. DO NOT use them +directly. diff --git a/.ostree/get_ostree_data.sh b/.ostree/get_ostree_data.sh new file mode 100755 index 0000000..65830d6 --- /dev/null +++ b/.ostree/get_ostree_data.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ostree_dir="${OSTREE_DIR:-"$(dirname "$(realpath "$0")")"}" + +if [ -z "${4:-}" ] || [ "${1:-}" = help ] || [ "${1:-}" = -h ]; then + cat <&2 echo ERROR - could not find role "$role" - please use ANSIBLE_COLLECTIONS_PATH + exit 2 +} + +get_packages() { + local ostree_dir pkgtype pkgfile rolefile + ostree_dir="$1" + for pkgtype in "${pkgtypes[@]}"; do + for suff in "" "-$distro" "-${distro}-${major_ver}" "-${distro}-${ver}"; do + pkgfile="$ostree_dir/packages-${pkgtype}${suff}.txt" + if [ -f "$pkgfile" ]; then + cat "$pkgfile" + fi + done + rolefile="$ostree_dir/roles-${pkgtype}.txt" + if [ -f "$rolefile" ]; then + local roles role rolepath + roles="$(cat "$rolefile")" + for role in $roles; do + rolepath="$(get_rolepath "$ostree_dir" "$role")" + if [ -z "$rolepath" ]; then + 1>&2 echo ERROR - could not find role "$role" - please use ANSIBLE_COLLECTIONS_PATH + exit 2 + fi + get_packages "$rolepath" + done + fi + done | sort -u +} + +format_packages_json() { + local comma pkgs pkg + comma="" + pkgs="[" + while read -r pkg; do + pkgs="${pkgs}${comma}\"${pkg}\"" + comma=, + done + pkgs="${pkgs}]" + echo "$pkgs" +} + +format_packages_raw() { + cat +} + +format_packages_yaml() { + while read -r pkg; do + echo "- $pkg" + done +} + +format_packages_toml() { + while read -r pkg; do + echo "[[packages]]" + echo "name = \"$pkg\"" + echo "version = \"*\"" + done +} + +distro="${distro_ver%%-*}" +ver="${distro_ver##*-}" +if [[ "$ver" =~ ^([0-9]*) ]]; then + major_ver="${BASH_REMATCH[1]}" +else + echo ERROR: cannot parse major version number from version "$ver" + exit 1 +fi + +"get_$category" "$ostree_dir" | "format_${category}_$format" diff --git a/.ostree/packages-runtime.txt b/.ostree/packages-runtime.txt new file mode 100644 index 0000000..ae5e93e --- /dev/null +++ b/.ostree/packages-runtime.txt @@ -0,0 +1 @@ +grubby diff --git a/README-ostree.md b/README-ostree.md new file mode 100644 index 0000000..a9f0185 --- /dev/null +++ b/README-ostree.md @@ -0,0 +1,66 @@ +# rpm-ostree + +The role supports running on [rpm-ostree](https://coreos.github.io/rpm-ostree/) +systems. The primary issue is that the `/usr` filesystem is read-only, and the +role cannot install packages. Instead, it will just verify that the necessary +packages and any other `/usr` files are pre-installed. The role will change the +package manager to one that is compatible with `rpm-ostree` systems. + +## Building + +To build an ostree image for a particular operating system distribution and +version, use the script `.ostree/get_ostree_data.sh` to get the list of +packages. If the role uses other system roles, then the script will include the +packages for the other roles in the list it outputs. The list of packages will +be sorted in alphanumeric order. + +Usage: + +```bash +.ostree/get_ostree_data.sh packages runtime DISTRO-VERSION FORMAT +``` + +`DISTRO-VERSION` is in the format that Ansible uses for `ansible_distribution` +and `ansible_distribution_version` - for example, `Fedora-38`, `CentOS-8`, +`RedHat-9.4` + +`FORMAT` is one of `toml`, `json`, `yaml`, `raw` + +* `toml` - each package in a TOML `[[packages]]` element + +```toml +[[packages]] +name = "package-a" +version = "*" +[[packages]] +name = "package-b" +version = "*" +... +``` + +* `yaml` - a YAML list of packages + +```yaml +- package-a +- package-b +... +``` + +* `json` - a JSON list of packages + +```json +["package-a","package-b",...] +``` + +* `raw` - a plain text list of packages, one per line + +```bash +package-a +package-b +... +``` + +What format you choose depends on which image builder you are using. For +example, if you are using something based on +[osbuild-composer](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/composing_installing_and_managing_rhel_for_edge_images/index#creating-an-image-builder-blueprint-for-a-rhel-for-edge-image-using-the-command-line-interface_composing-a-rhel-for-edge-image-using-image-builder-command-line), +you will probably want to use the `toml` output format. diff --git a/README.md b/README.md index c24b9ae..ccd510c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,18 @@ An Ansible role for bootloader and kernel command line management. ## Requirements -None +See below + +### Collection requirements + +If you don't want to manage `ostree` systems, the role has no requirements. + +If you want to manage `ostree` systems, the role requires additional modules +from external collections. Please use the following command to install them: + +```bash +ansible-galaxy collection install -vv -r meta/collection-requirements.yml +``` ## Role Variables @@ -239,6 +250,10 @@ For example: - linux-system-roles.bootloader ``` +## rpm-ostree + +See README-ostree.md + ## License MIT diff --git a/meta/collection-requirements.yml b/meta/collection-requirements.yml new file mode 100644 index 0000000..a0cd255 --- /dev/null +++ b/meta/collection-requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - name: ansible.posix diff --git a/tasks/main.yml b/tasks/main.yml index c15579c..e1a464f 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -7,6 +7,8 @@ package: name: "{{ __bootloader_packages }}" state: present + use: "{{ (__bootloader_is_ostree | d(false)) | + ternary('ansible.posix.rhel_rpm_ostree', omit) }}" - name: Ensure boot loader settings bootloader_settings: @@ -15,11 +17,31 @@ - Fix default kernel boot parameters - Reboot system +- name: Get grub directory stat + stat: + path: "{{ __bootloader_default_grub | dirname }}" + register: __bootloader_grub_dir_stat + +- name: Ensure grub directory exists + file: + path: "{{ __bootloader_default_grub | dirname }}" + state: directory + mode: "{{ __bootloader_grub_dir_stat.stat.exists | + ternary(__bootloader_grub_dir_stat.stat.mode, '0755') }}" + +- name: Get grub config stat + stat: + path: "{{ __bootloader_default_grub }}" + register: __bootloader_grub_stat + - name: Update boot loader timeout configuration lineinfile: path: "{{ __bootloader_default_grub }}" regexp: '^GRUB_TIMEOUT=.*' line: 'GRUB_TIMEOUT={{ bootloader_timeout }}' + create: true + mode: "{{ __bootloader_grub_stat.stat.exists | + ternary(__bootloader_grub_stat.stat.mode, '0644') }}" notify: Rebuild grub config - name: Determine platform type diff --git a/tasks/set_vars.yml b/tasks/set_vars.yml index 83ecbae..c41b73d 100644 --- a/tasks/set_vars.yml +++ b/tasks/set_vars.yml @@ -6,6 +6,18 @@ when: __bootloader_required_facts | difference(ansible_facts.keys() | list) | length > 0 +- name: Determine if system is ostree and set flag + when: not __bootloader_is_ostree is defined + block: + - name: Check if system is ostree + stat: + path: /run/ostree-booted + register: __ostree_booted_stat + + - name: Set flag to indicate system is ostree + set_fact: + __bootloader_is_ostree: "{{ __ostree_booted_stat.stat.exists }}" + - name: Set platform/version specific variables include_vars: "{{ __bootloader_vars_file }}" loop: diff --git a/tests/tests_add_rm.yml b/tests/tests_add_rm.yml index cec9f42..06359ee 100644 --- a/tests/tests_add_rm.yml +++ b/tests/tests_add_rm.yml @@ -14,6 +14,10 @@ include_role: name: linux-system-roles.bootloader + - name: Skip test on ostree systems + meta: end_host + when: __bootloader_is_ostree + # Images in CI might have a grub timeout set to a different other than the # default 5 value. # In this case, the above invocation require handlers to be flushed. diff --git a/tests/tests_include_vars_from_parent.yml b/tests/tests_include_vars_from_parent.yml index c6c5d18..03d55f3 100644 --- a/tests/tests_include_vars_from_parent.yml +++ b/tests/tests_include_vars_from_parent.yml @@ -1,46 +1,59 @@ --- -- name: Test roles variables override +- name: Test role include variable override hosts: all gather_facts: true tasks: - - name: Create var file in caller that can override the one in called role - delegate_to: localhost - copy: - # usually the fake file will cause the called role to crash of - # overriding happens, but if not, set a variable that will - # allow to detect the bug - content: "__caller_override: true" - # XXX ugly, self-modifying code - changes the "caller" role on - # the controller - dest: "{{ playbook_dir }}/roles/caller/vars/{{ item }}.yml" - mode: 0600 - loop: "{{ varfiles | unique }}" - # In case the playbook is executed against multiple hosts, use - # only the first one. Otherwise the hosts would stomp on each - # other since they are changing files on the controller. - when: inventory_hostname == ansible_play_hosts_all[0] - vars: - # change to hostvars['localhost']['ansible_facts'] to use the - # information for localhost - facts: "{{ ansible_facts }}" - versions: - - "{{ facts['distribution_version'] }}" - - "{{ facts['distribution_major_version'] }}" - separators: ["-", "_"] - # create all variants like CentOS, CentOS_8.1, CentOS-8.1, - # CentOS-8, CentOS-8.1 - # more formally: - # {{ ansible_distribution }}-{{ ansible_distribution_version }} - # {{ ansible_distribution }}-{{ ansible_distribution_major_version }} - # {{ ansible_distribution }} - # {{ ansible_os_family }} - # and the same for _ as separator. - varfiles: "{{ [facts['distribution']] | product(separators) | - map('join') | product(versions) | map('join') | list + - [facts['distribution'], facts['os_family']] }}" + - name: Run test in a block to clean up in always + block: + - name: >- + Create var file in caller that can override the one in called role + delegate_to: localhost + copy: + # usually the fake file will cause the called role to crash of + # overriding happens, but if not, set a variable that will + # allow to detect the bug + content: "__caller_override: true" + # XXX ugly, self-modifying code - changes the "caller" role on + # the controller + dest: "{{ playbook_dir }}/roles/caller/vars/{{ item }}.yml" + mode: preserve + loop: "{{ varfiles | unique }}" + # In case the playbook is executed against multiple hosts, use + # only the first one. Otherwise the hosts would stomp on each + # other since they are changing files on the controller. + when: inventory_hostname == ansible_play_hosts_all[0] + vars: + # change to hostvars['localhost']['ansible_facts'] to use the + # information for localhost + facts: "{{ ansible_facts }}" + versions: + - "{{ facts['distribution_version'] }}" + - "{{ facts['distribution_major_version'] }}" + separators: ["-", "_"] + # create all variants like CentOS, CentOS_8.1, CentOS-8.1, + # CentOS-8, CentOS-8.1 + # more formally: + # {{ ansible_distribution }}-{{ ansible_distribution_version }} + # {{ ansible_distribution }}-\ + # {{ ansible_distribution_major_version }} + # {{ ansible_distribution }} + # {{ ansible_os_family }} + # and the same for _ as separator. + varfiles: "{{ [facts['distribution']] | product(separators) | + map('join') | product(versions) | map('join') | list + + [facts['distribution'], facts['os_family']] }}" + register: __varfiles_created - - name: Import role - import_role: - name: caller - vars: - roletoinclude: linux-system-roles.bootloader + - name: Import role + import_role: + name: caller + vars: + roletoinclude: linux-system-roles.bootloader + always: + - name: Cleanup + file: + path: "{{ item.dest }}" + state: absent + loop: "{{ __varfiles_created.results }}" + delegate_to: localhost + when: inventory_hostname == ansible_play_hosts_all[0] diff --git a/tests/tests_settings.yml b/tests/tests_settings.yml index 09888eb..ce62632 100644 --- a/tests/tests_settings.yml +++ b/tests/tests_settings.yml @@ -14,6 +14,10 @@ include_role: name: linux-system-roles.bootloader + - name: Skip test on ostree systems + meta: end_host + when: __bootloader_is_ostree + # Images in CI might have a grub timeout set to a different other than the # default 5 value. # In this case, the above invocation require handlers to be flushed.