<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="linux-system-roles.github.io/atom.xml" rel="self" type="application/atom+xml" /><link href="linux-system-roles.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-06-23T12:35:54+00:00</updated><id>linux-system-roles.github.io/atom.xml</id><title type="html">Linux System Roles</title><subtitle>Welcome to the Linux System Roles project homepage!
</subtitle><author><name>Thomas Woerner</name></author><entry><title type="html">System Roles support for image mode (bootc) builds</title><link href="linux-system-roles.github.io/2025/06/role-bootc-support" rel="alternate" type="text/html" title="System Roles support for image mode (bootc) builds" /><published>2025-06-25T09:45:00+00:00</published><updated>2025-06-25T09:45:00+00:00</updated><id>linux-system-roles.github.io/2025/06/role-bootc-support</id><content type="html" xml:base="linux-system-roles.github.io/2025/06/role-bootc-support"><![CDATA[<h2 id="goal">Goal</h2>

<p>Image mode, aka. “bootable containers”, aka. “bootc” is an exciting new way to
build and deploy operating systems. A bootable container image can be used to
install or upgrade a real or virtual machine, similar to container images for
applications. This is currently supported for
<a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/using_image_mode_for_rhel_to_build_deploy_and_manage_operating_systems/index">Red Hat Enterprise Linux 9/10</a>
and <a href="https://docs.fedoraproject.org/en-US/bootc/">Fedora/CentOS</a>, but also in
other projects like <a href="https://universal-blue.org/">universal-blue</a>.</p>

<p>With system roles being the supported high-level API to set up
Fedora/RHEL/CentOS systems, we want to make them compatible with image mode
builds. In particular, we need to make them detect the “non-booted” environment
and adjust their behaviour to not e.g. try to start systemd units or talk to
network services, and defer all of that to the first boot. We also need to add
full bootc end-to-end integration tests to ensure this keeps working in the
future on all supported platforms.</p>

<h2 id="build-process">Build process</h2>

<p>This can work in two ways. Both ought to work, and which one you choose depends
on your available infrastructure and preferences.</p>

<h3 id="treat-a-container-build-as-an-ansible-host">Treat a container build as an Ansible host</h3>

<p>Start a container build with e.g.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>buildah from <span class="nt">--name</span> buildc quay.io/centos-bootc/centos-bootc:stream10
</code></pre></div></div>

<p>Create an inventory for the <a href="https://docs.ansible.com/ansible/latest/collections/containers/podman/buildah_connection.html">buildah connector</a>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>buildc ansible_host=buildc ansible_connection=buildah ansible_become=false ansible_remote_tmp=/tmp
</code></pre></div></div>

<p>Then run the system-roles playbooks on the “outside” against that inventory.</p>

<p>That matches the spirit of Ansible and is cleaner as Ansible itself and
system-roles do not need to be installed into the container. This is the
approach outlined in <a href="https://blog.tomecek.net/post/building-containers-with-buildah-and-ansible/">“Building Container Images with Buildah and
Ansible”</a>
and <a href="https://blog.tomecek.net/post/ansible-and-podman-can-play-together-now/">Ansible and Podman Can Play Together
Now</a>
and implemented in the
<a href="https://github.com/ansible-community/ansible-bender">ansible-bender</a> proof of
concept (⚠️ Warning: currently unmaintained).</p>

<h3 id="install-ansible-and-the-system-roles-into-the-container">Install Ansible and the system roles into the container</h3>

<p>The <code class="language-plaintext highlighter-rouge">Containerfile</code> looks roughly like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM quay.io/centos-bootc/centos-bootc:stream10
RUN dnf -y install ansible-core rhel-system-roles
COPY ./setup.yml .
RUN ansible-playbook setup.yml
</code></pre></div></div>

<p>Everything happens inside of the image build, and the playbooks run against
<code class="language-plaintext highlighter-rouge">localhost</code>. This could use a <a href="https://docs.docker.com/build/building/multi-stage/">multi-stage
build</a> to avoid having
Ansible and the roles in the final image. This is entirely self-contained and
thus works well in automatic container build pipelines.</p>

<p>⚠️  Warning: Unfortunately this is currently broken for many/most roles because
of an Ansible bug: <a href="https://github.com/ansible/ansible/issues/85380"><code class="language-plaintext highlighter-rouge">service:</code> fails in a container build environment</a>.
Once that is fixed, this approach will work well and might often be the
preferred choice.</p>

<h2 id="status">Status</h2>

<p>This effort is tracked in the <a href="https://issues.redhat.com/browse/RHEL-78157">RHEL-78157</a> epic.
At the time of writing, 15 roles are already supported, the other 22 still need to be updated.</p>

<p>Roles which support image mode builds have the <code class="language-plaintext highlighter-rouge">containerbuild</code> tag, which you
can see in the <a href="https://galaxy.ansible.com/ui/standalone/roles/linux-system-roles/firewall/">Ansible Galaxy view</a> (expand the tag list at the top), or in the source code in <a href="https://github.com/linux-system-roles/firewall/blob/main/meta/main.yml">meta/main.yml</a>.</p>

<p>Note that some roles also have a <code class="language-plaintext highlighter-rouge">container</code> tag, which means that they are
tested and supported in a running system container (i.e. a docker/podman
container with the <code class="language-plaintext highlighter-rouge">/sbin/init</code> entry point, or LXC/nspawn etc.), but not
during a non-booted container build.</p>

<h2 id="steps-for-converting-a-role">Steps for converting a role</h2>

<p>Helping out with that effort is very much appreciated! If you are interested in
making a particular role compatible with image mode builds, please follow these steps:</p>

<ol>
  <li>
    <p>Clone the role’s upstream git repository. Make sure that its <code class="language-plaintext highlighter-rouge">meta/main.yml</code>
file does <em>not</em> yet have a <code class="language-plaintext highlighter-rouge">containerbuild</code> tag – if it does, the role was
already converted. In that case, please update the status in the epic.</p>
  </li>
  <li>
    <p>Familiarize yourself with the purpose of the role, have a look at README.md,
and think about whether running the role in a container generally makes
sense. That should be the case for most of them, but e.g <code class="language-plaintext highlighter-rouge">storage</code> is
hardware specific and for the most part does not make sense in a container
build environment.</p>
  </li>
  <li>Make sure your developer machine can run tests in in general. Do the
<a href="https://github.com/linux-system-roles/tox-lsr?tab=readme-ov-file#integration-test-setup">integration test setup</a> and also read the following sections about running QEMU and container tests.
E.g. running a QEMU test should work:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tox <span class="nt">-e</span> qemu-ansible-core-2-20 <span class="nt">--</span> <span class="nt">--image-name</span> centos-9 <span class="nt">--log-level</span><span class="o">=</span>debug <span class="nt">--</span> tests/tests_default.yml
</code></pre></div>    </div>
  </li>
  <li>Do an initial run of the default or other test during a bootc container build, to get a first impression:
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LSR_CONTAINER_PROFILE</span><span class="o">=</span><span class="nb">false </span><span class="nv">LSR_CONTAINER_PRETTY</span><span class="o">=</span><span class="nb">false </span>tox <span class="nt">-e</span> container-ansible-core-2-20 <span class="nt">--</span> <span class="nt">--image-name</span> centos-9-bootc tests/tests_default.yml
</code></pre></div>    </div>
  </li>
  <li>
    <p>The most common causes of failures are <code class="language-plaintext highlighter-rouge">service_facts:</code> which just simply
doesn’t work in a container, and trying to set the <code class="language-plaintext highlighter-rouge">state:</code> of a unit in
<code class="language-plaintext highlighter-rouge">service:</code>. The existing PRs linked from <a href="https://issues.redhat.com/browse/RHEL-78157">RHEL-78157</a>
have plenty of examples what to do with these.</p>

    <p>The <a href="https://github.com/linux-system-roles/logging/pull/444">logging role PR</a>
is a good example for the standard approach of adding a
<code class="language-plaintext highlighter-rouge">__rolename_is_booted</code> flag to the role variables, and use that to
conditionalize operations and tests which
can’t work in a container. E.g. the above <code class="language-plaintext highlighter-rouge">service: status:</code> can be fixed
with</p>
    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">state</span><span class="pi">:</span> <span class="s2">"</span><span class="s">started"</span>
</code></pre></div>    </div>

    <p><code class="language-plaintext highlighter-rouge">service_facts:</code> can be replaced with <code class="language-plaintext highlighter-rouge">systemctl is-enabled</code> or similar, see e.g. the corresponding
<a href="https://github.com/linux-system-roles/mssql/commit/e9d16e0eafaf1859f65e28a00c3de6a5283b2536">mssql fix</a> or
<a href="https://github.com/linux-system-roles/firewall/commit/e88b15ea3821b6b90443d1c9f76987bafdad5595">firewall fix</a>.</p>

    <p>Do these “standard recipe” fixes to clear away the easy noise.</p>
  </li>
  <li>
    <p>Create a branch on your fork, and add a
<a href="https://github.com/martinpitt/lsr-selinux/commit/58c1065b4751f13a9201ca767b7eaa0f09aaa92b">temporary commit to run tests on branch pushes</a>, and another commit to
<a href="https://github.com/martinpitt/lsr-selinux/commit/56b18070f67c04d6f37a62bfa50f27cefd0a0779">enable tests on container builds and in system containers</a>.
With that you can iterate on your branch and get testing feedback without
creating a lot of PR noise for other developers on the project. Push to your
fork, go to the Actions page, and wait for the first test result.</p>
  </li>
  <li>
    <p>As described above, the <code class="language-plaintext highlighter-rouge">container</code> tag means that the role is supported and
works in (booted) system containers. In most cases this is fairly easy to
fix, and nice to have, as running tests and iterating is faster, and
debugging is also a bit easier. In some cases running in system containers
is hard (like in the selinux or podman roles), in that case don’t bother and
remove that tag again.</p>
  </li>
  <li>
    <p>Go through the other failures. You can download the log archive and/or run
the individual tests locally. The following command helps for easier debugging – it
keeps the container running for inspection after a failure, and removes
containers and temp files from the previous run:</p>

    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>buildah <span class="nb">rm</span> <span class="nt">--all</span><span class="p">;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /tmp/runcontainer.<span class="k">*</span><span class="p">;</span> <span class="nv">LSR_DEBUG</span><span class="o">=</span>1 <span class="nv">LSR_CONTAINER_PROFILE</span><span class="o">=</span><span class="nb">false </span><span class="nv">LSR_CONTAINER_PRETTY</span><span class="o">=</span><span class="nb">false </span>tox <span class="nt">-e</span> container-ansible-core-2-20 <span class="nt">--</span> <span class="nt">--image-name</span> centos-9-bootc tests/tests_default.yml
</code></pre></div>    </div>

    <p>You can enter the container and debug with <code class="language-plaintext highlighter-rouge">buildah run tests_default bash</code>.
The container name corresponds to the test name; check <code class="language-plaintext highlighter-rouge">buildah ps</code>.</p>
  </li>
  <li>
    <p>Fix the role and tests until you get a green result. Finally clean up and
sort your commits into
<a href="https://github.com/linux-system-roles/postgresql/commit/089421478730b6bff88c42f1ac56eec9836ae852">fix: Skip runtime operations in non-systemd environments</a>,
and <a href="https://github.com/linux-system-roles/postgresql/commit/fea9e802473805344d6e062f99961a4231b4f129">feat: Support this role in container builds</a>.
Any role specific or more intrusive and self-contained change should be in
separate commits before these.</p>
  </li>
  <li>
    <p>Add an end-to-end integration test which ensures that running the role
during a container build actually works as intended in a QEMU deployment.
If there is an existing integration test which has representative complexity
and calls the role just once (i.e. tests one scenario), you can convert it
like
<a href="https://github.com/linux-system-roles/sudo/commit/2a1569f846b24e427ba4bbe078ee5ce7bf81e13d">sudo’s bootc e2e test</a>.
If there is no existing test, you can also add a specific bootc e2e test
like in
<a href="https://github.com/linux-system-roles/sudo/pull/58/commits/42df7f14e54813e4d6d97bbc9d388f59cc25e09d">this demo PR</a>
or the
<a href="https://github.com/linux-system-roles/postgresql/commit/18be022885c3953678c70278f7503f0df3283f04">postgresql role</a>.</p>
  </li>
  <li>
    <p>To locally run the bootc e2e test, see <a href="https://github.com/linux-system-roles/tox-lsr?tab=readme-ov-file#image-mode-testing">Image mode testing tox-lsr docs</a>.</p>
  </li>
  <li>
    <p>Push the e2e test to your branch, iterate until green.</p>
  </li>
  <li>
    <p>Send a PR, link it from the Jira epic, get it landed, update the list in the
Jira epic again.</p>
  </li>
  <li>Celebrate 🎉 and brag about your contribution!</li>
</ol>]]></content><author><name>Martin Pitt</name></author><category term="announcement" /><summary type="html"><![CDATA[Goal]]></summary></entry><entry><title type="html">DevConf2021.cz - Presentation and Demo</title><link href="linux-system-roles.github.io/2021/02/DevConf2021-cz" rel="alternate" type="text/html" title="DevConf2021.cz - Presentation and Demo" /><published>2021-02-19T12:00:00+00:00</published><updated>2021-02-19T12:00:00+00:00</updated><id>linux-system-roles.github.io/2021/02/DevConf2021-cz</id><content type="html" xml:base="linux-system-roles.github.io/2021/02/DevConf2021-cz"><![CDATA[<p>There was a presentation entitled “Managing Standard Operating Envs with
Ansible” given at DevConf2021.cz.  Demo files and links to videos can be found
at
<a href="https://github.com/linux-system-roles/linux-system-roles.github.io/tree/master/demo/devconf2021-cz-demo/">DevConf2021.cz</a></p>]]></content><author><name>Rich Megginson</name></author><category term="talk" /><summary type="html"><![CDATA[There was a presentation entitled “Managing Standard Operating Envs with Ansible” given at DevConf2021.cz. Demo files and links to videos can be found at DevConf2021.cz]]></summary></entry><entry><title type="html">CI changes - Github Actions and tox-lsr</title><link href="linux-system-roles.github.io/2020/12/ci-changes" rel="alternate" type="text/html" title="CI changes - Github Actions and tox-lsr" /><published>2020-12-17T12:00:00+00:00</published><updated>2020-12-17T12:00:00+00:00</updated><id>linux-system-roles.github.io/2020/12/ci-changes</id><content type="html" xml:base="linux-system-roles.github.io/2020/12/ci-changes"><![CDATA[<p>We have recently moved our github CI to use <a href="https://docs.github.com/en/free-pro-team@latest/actions">Github
Actions</a> instead of
Travis.  Our organization template is here:
<a href="https://github.com/linux-system-roles/.github">https://github.com/linux-system-roles/.github</a></p>

<p>We currently aren’t using any of the more advanced features of Github Actions,
as we wanted to achieve parity with Travis as soon as possible.</p>

<p>We have also replaced all of the local scripts used for CI testing with
<a href="https://github.com/linux-system-roles/tox-lsr">tox-lsr</a>.  If you are a system
roles developer, you will need to modify your workflow in order to use the new
plugin.  See
<a href="https://github.com/linux-system-roles/tox-lsr/blob/main/README.md">README.md</a>
for more information.</p>]]></content><author><name>Rich Megginson</name></author><category term="talk" /><summary type="html"><![CDATA[We have recently moved our github CI to use Github Actions instead of Travis. Our organization template is here: https://github.com/linux-system-roles/.github]]></summary></entry><entry><title type="html">Introduction to Network Role</title><link href="linux-system-roles.github.io/2020/11/introduction-to-network-role" rel="alternate" type="text/html" title="Introduction to Network Role" /><published>2020-11-10T12:00:00+00:00</published><updated>2020-11-10T12:00:00+00:00</updated><id>linux-system-roles.github.io/2020/11/introduction-to-network-role</id><content type="html" xml:base="linux-system-roles.github.io/2020/11/introduction-to-network-role"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>The network role supports two providers: NetworkManager(nm) and initscripts. For
CentOS/RHEL 6, we only use initscripts as providers. For CentOS/RHEL 7+, we use
initscripts and nm as providers. Various networking profiles can be configured via
customized Ansible module. Several tasks will run for host networking setup, including
but not limited to, package installation, starting/enabling services. Network role CI
system  consists of Tox running unit tests and Test-harness running integration
tests. When we use Tox to run unit tests,  we can check code formatting using
Python Black, check YAML files formatting etc. Integration tests run in internal
OpenShift, watch configured GitHub repositories for PRs, check out new PR, run
all testing playbooks against all configured images, fresh machine for every test
playbook, sets statuses of PR and uploads results. For better testing efficiency,
in some playbooks, we can call internal Ansible modules instead of role to skip
redundant tasks, we can also group Ansible modules into blocks for more targeted
unit testing. Furthermore, there are helper scripts to get coverage from integration
tests via Ansible, basic unit for argument parsing, additional helper files for
assertion/test setup/logging.</p>

<h2 id="code-structure">Code structure</h2>

<p>The repository is structured as follows:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">./defaults/ </code> – Contains the default role configuration.</li>
  <li><code class="language-plaintext highlighter-rouge">./examples/</code> – Contains YAML examples for different configurations.</li>
  <li><code class="language-plaintext highlighter-rouge">./library/network_connections.py</code> – Contains the internal Ansible module,
which is the main script. It controls the communication between the role and
Ansible, imports the YAML configuration and applies the changes to the provider
(i.e. NetworkManager, initscripts).</li>
  <li><code class="language-plaintext highlighter-rouge">./meta/</code> – Metadata of the project.</li>
  <li><code class="language-plaintext highlighter-rouge">./module_utils/network_lsr/</code> – Contains other files that are useful for the
network role (e.g. the YAML argument validator)</li>
  <li><code class="language-plaintext highlighter-rouge">./tasks/</code> – Declaration of the different tasks that the role is going to execute.</li>
  <li><code class="language-plaintext highlighter-rouge">./tests/playbooks/</code> – Contains the complete tests for the role.</li>
  <li><code class="language-plaintext highlighter-rouge">./tests/tests_*.yml</code> are shims to run tests once for every provider.</li>
  <li><code class="language-plaintext highlighter-rouge">./tests/tasks/</code> contains task snippets that are used in multiple tests to avoid
having the same code repeated multiple times.</li>
  <li>Each file matching <code class="language-plaintext highlighter-rouge">tests_*.yml</code> is a test playbook which is run by the CI system.</li>
</ul>

<h2 id="how-to-run-test">How to run test</h2>

<h4 id="tox-unit-tests">Tox Unit Tests</h4>
<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">tox -l</code>, list all the unit testing, available unit testing options are:</p>

    <ul>
      <li>black</li>
      <li>pylint</li>
      <li>flake8</li>
      <li>yamllint</li>
      <li>py26</li>
      <li>py27</li>
      <li>py36</li>
      <li>py37</li>
      <li>py38</li>
      <li>collection</li>
      <li>custom</li>
    </ul>
  </li>
  <li>tox, run all the tests</li>
  <li>tox -e py36, <code class="language-plaintext highlighter-rouge">py36</code> is pyunit testing with Python 3.6</li>
  <li>tox -e yamllint, Check the YAML files are correctly formatted</li>
  <li>tox -e black, Check the formatting of the code with Python Black</li>
  <li>…</li>
</ul>

<h4 id="integration-test">Integration Test</h4>

<ul>
  <li>Download CentOS 6, CentOS 7, CentOS 8, Fedora images from
    <ul>
      <li>https://cloud.centos.org/centos/6/images/CentOS-6-x86_64-GenericCloud-1907.qcow2c</li>
      <li>https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2003.qcow2c</li>
      <li>https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2</li>
      <li>https://kojipkgs.fedoraproject.org/compose/cloud/</li>
    </ul>
  </li>
  <li>Install “standard-test-roles-inventory-qemu” package
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dnf <span class="nb">install </span>standard-test-roles-inventory-qemu
</code></pre></div>    </div>
  </li>
  <li>[TEST_DEBUG=1] TEST_SUBJECTS=<image> ansible-playbook -v[v] -i &lt;inventory file/script&gt; &lt;tests_….yml&gt;
</image>    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">TEST_SUBJECTS</span><span class="o">=</span>CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2 ansible-playbook <span class="nt">-v</span> <span class="nt">-i</span> /usr/share/ansible/inventory/standard-inventory-qcow2 tests/tests_default.yml
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="overview">Overview</h2>

<p>Network role enables users to configure the network on the target machine. This
role can be used to configure:</p>

<ul>
  <li>Ethernet interfaces</li>
  <li>Bridge interfaces</li>
  <li>Bonded interfaces</li>
  <li>VLAN interfaces</li>
  <li>MacVLAN interfaces</li>
  <li>Infiniband interfaces</li>
  <li>Wireless (WiFi) interfaces</li>
  <li>IP configuration</li>
  <li>802.1x authentication</li>
</ul>

<h2 id="examples-of-connections">Examples of Connections</h2>

<p>The network role updates or creates all connection profiles on the target system
as specified in the network_connections variable, which is a list of dictionaries
that include specific options.</p>

<h4 id="configuring-ethernet">Configuring Ethernet:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth0</span>
    <span class="c1">#persistent_state: present  # default</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">autoconnect</span><span class="pi">:</span> <span class="s">yes</span>
    <span class="na">mac</span><span class="pi">:</span> <span class="s">00:00:5e:00:53:5d</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">yes</span>
</code></pre></div></div>

<h4 id="configuring-bridge">Configuring Bridge:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">internal-br0</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">br0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bridge</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">auto6</span><span class="pi">:</span> <span class="s">no</span>
</code></pre></div></div>

<h4 id="configuring-bonded-interface">Configuring Bonded Interface:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">br0-bond0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bond</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">bond0</span>
    <span class="na">controller</span><span class="pi">:</span> <span class="s">internal-br0</span>
    <span class="na">port_type</span><span class="pi">:</span> <span class="s">bridge</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">br0-bond0-eth1</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">eth1</span>
    <span class="na">controller</span><span class="pi">:</span> <span class="s">br0-bond0</span>
    <span class="na">port_type</span><span class="pi">:</span> <span class="s">bond</span>
</code></pre></div></div>

<h4 id="configuring-vlans">Configuring VLANs:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth1-profile</span>
    <span class="na">autoconnet</span><span class="pi">:</span> <span class="s">no</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">eth1</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">auto6</span><span class="pi">:</span> <span class="s">no</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth1.6</span>
    <span class="na">autoconnect</span><span class="pi">:</span> <span class="s">no</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">vlan</span>
    <span class="na">parent</span><span class="pi">:</span> <span class="s">eth1-profile</span>
    <span class="na">vlan</span><span class="pi">:</span>
      <span class="na">id</span><span class="pi">:</span> <span class="m">6</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">address</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">192.0.2.5/24</span>
      <span class="na">auto6</span><span class="pi">:</span> <span class="s">no</span>
</code></pre></div></div>

<h4 id="configuring-infiniband">Configuring Infiniband:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ib0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">infiniband</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">ib0</span>

  <span class="c1"># Create a simple infiniband profile</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ib0-10</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">ib0.000a</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">infiniband</span>
    <span class="na">autoconnect</span><span class="pi">:</span> <span class="s">yes</span>
    <span class="na">infiniband_p_key</span><span class="pi">:</span> <span class="m">10</span>
    <span class="na">parent</span><span class="pi">:</span> <span class="s">ib0</span>
    <span class="na">state</span><span class="pi">:</span> <span class="s">up</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">auto6</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">address</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">198.51.100.133/30</span>
</code></pre></div></div>

<h4 id="configuring-macvlan">Configuring MACVLAN:</h4>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth0-profile</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">eth0</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">address</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">192.168.0.1/24</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">veth0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">macvlan</span>
    <span class="na">parent</span><span class="pi">:</span> <span class="s">eth0-profile</span>
    <span class="na">macvlan</span><span class="pi">:</span>
      <span class="na">mode</span><span class="pi">:</span> <span class="s">bridge</span>
      <span class="na">promiscuous</span><span class="pi">:</span> <span class="s">yes</span>
      <span class="na">tap</span><span class="pi">:</span> <span class="s">no</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">address</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">192.168.1.1/24</span>
</code></pre></div></div>

<h4 id="configuring-a-wireless-connection">Configuring a wireless connection:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wlan0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">wireless</span>
    <span class="na">interface_name</span><span class="pi">:</span> <span class="s">wlan0</span>
    <span class="na">wireless</span><span class="pi">:</span>
      <span class="na">ssid</span><span class="pi">:</span> <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">WPA2-PSK</span><span class="nv"> </span><span class="s">Network"</span>
      <span class="na">key_mgmt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">wpa-psk"</span>
      <span class="c1"># recommend vault encrypting the wireless password</span>
      <span class="c1"># see https://docs.ansible.com/ansible/latest/user_guide/vault.html</span>
      <span class="na">password</span><span class="pi">:</span> <span class="s2">"</span><span class="s">p@55w0rD"</span>
</code></pre></div></div>

<h4 id="setting-the-ip-configuration">Setting the IP configuration:</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">ip</span><span class="pi">:</span>
      <span class="na">route_metric4</span><span class="pi">:</span> <span class="m">100</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">no</span>
      <span class="c1">#dhcp4_send_hostname: no</span>
      <span class="na">gateway4</span><span class="pi">:</span> <span class="s">192.0.2.1</span>

      <span class="na">dns</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">192.0.2.2</span>
        <span class="pi">-</span> <span class="s">198.51.100.5</span>
      <span class="na">dns_search</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">example.com</span>
        <span class="pi">-</span> <span class="s">subdomain.example.com</span>

      <span class="na">route_metric6</span><span class="pi">:</span> <span class="s">-1</span>
      <span class="na">auto6</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">gateway6</span><span class="pi">:</span> <span class="s">2001:db8::1</span>

      <span class="na">address</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">192.0.2.3/24</span>
        <span class="pi">-</span> <span class="s">198.51.100.3/26</span>
        <span class="pi">-</span> <span class="s">2001:db8::80/7</span>

      <span class="na">route</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">network</span><span class="pi">:</span> <span class="s">198.51.100.128</span>
          <span class="na">prefix</span><span class="pi">:</span> <span class="m">26</span>
          <span class="na">gateway</span><span class="pi">:</span> <span class="s">198.51.100.1</span>
          <span class="na">metric</span><span class="pi">:</span> <span class="m">2</span>
        <span class="pi">-</span> <span class="na">network</span><span class="pi">:</span> <span class="s">198.51.100.64</span>
          <span class="na">prefix</span><span class="pi">:</span> <span class="m">26</span>
          <span class="na">gateway</span><span class="pi">:</span> <span class="s">198.51.100.6</span>
          <span class="na">metric</span><span class="pi">:</span> <span class="m">4</span>
      <span class="na">route_append_only</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">rule_append_only</span><span class="pi">:</span> <span class="s">yes</span>
</code></pre></div></div>

<h4 id="configuring-8021x">Configuring 802.1x:</h4>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network_connections</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">eth0</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">ethernet</span>
    <span class="na">ieee802_1x</span><span class="pi">:</span>
      <span class="na">identity</span><span class="pi">:</span> <span class="s">myhost</span>
      <span class="na">eap</span><span class="pi">:</span> <span class="s">tls</span>
      <span class="na">private_key</span><span class="pi">:</span> <span class="s">/etc/pki/tls/client.key</span>
      <span class="c1"># recommend vault encrypting the private key password</span>
      <span class="c1"># see https://docs.ansible.com/ansible/latest/user_guide/vault.html</span>
      <span class="na">private_key_password</span><span class="pi">:</span> <span class="s2">"</span><span class="s">p@55w0rD"</span>
      <span class="na">client_cert</span><span class="pi">:</span> <span class="s">/etc/pki/tls/client.pem</span>
      <span class="na">ca_cert</span><span class="pi">:</span> <span class="s">/etc/pki/tls/cacert.pem</span>
      <span class="na">domain_suffix_match</span><span class="pi">:</span> <span class="s">example.com</span>
</code></pre></div></div>

<h2 id="reference">Reference</h2>

<ol>
  <li>The external landing page for the system roles project, https://linux-system-roles.github.io/</li>
  <li>The external network role docs, https://github.com/linux-system-roles/network/</li>
</ol>]]></content><author><name>Wen Liang</name></author><category term="talk" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Separate INFO and DEBUG logs</title><link href="linux-system-roles.github.io/2020/10/separate-info-and-debug-logs" rel="alternate" type="text/html" title="Separate INFO and DEBUG logs" /><published>2020-10-08T12:00:00+00:00</published><updated>2020-10-08T12:00:00+00:00</updated><id>linux-system-roles.github.io/2020/10/separate-info-and-debug-logs</id><content type="html" xml:base="linux-system-roles.github.io/2020/10/separate-info-and-debug-logs"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Before refactoring logging of network module, the module collects all logging statements,
and at the end returns them as “warnings”, so that they are shown by ansible. Obviously,
these are not really warnings, but rather debug information..</p>

<h2 id="how-to-reproduce">How to reproduce</h2>

<p>We can reproduce this network module bug by doing qemu test.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">TEST_SUBJECTS</span><span class="o">=</span>CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2 ansible-playbook <span class="nt">-vv</span> <span class="nt">-i</span> /usr/share/ansible/inventory/standard-inventory-qcow2 ./tests/playbooks/tests_ethernet.yml
</code></pre></div></div>

<p><img src="../../images/wenliang_blog_figure1.png" alt="" /></p>

<h2 id="how-to-resolve-it">How to resolve it</h2>

<p>The logging messages should be returned in a different json field that is ignored by
ansible. Then, the tasks/main.yml should have a follow-up debug task that prints the
returned variable. In the failure case, the network_connections task must run ignoring
failures to reach the debug statement. Then, a follow up task should check whether the
network_connections task failed and abort.</p>

<h2 id="what-is-the-result">What is the result</h2>

<p>After bug fixed, we can also use the same qemu test to compare the result:</p>

<p><img src="../../images/wenliang_blog_figure2.png" alt="" /></p>

<h2 id="additional-test-cases">Additional test cases</h2>

<p>Beyond that, we also have some assertion to confirm that we indeed separate Info and Debug logs.
In <code class="language-plaintext highlighter-rouge">./tests/tests_default.yml</code>, we have the following testing code to assert no warning
in _network_connections_result.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Test executing the role with default parameters</span>
  <span class="na">hosts</span><span class="pi">:</span> <span class="s">all</span>
  <span class="na">roles</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">linux-system-roles.network</span>
  <span class="na">tasks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Test warning and info logs</span>
      <span class="na">assert</span><span class="pi">:</span>
        <span class="na">that</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s2">"</span><span class="s">'warnings'</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">__network_connections_result"</span>
        <span class="na">msg</span><span class="pi">:</span> <span class="s2">"</span><span class="s">There</span><span class="nv"> </span><span class="s">are</span><span class="nv"> </span><span class="s">warnings"</span>
</code></pre></div></div>

<p>In <code class="language-plaintext highlighter-rouge">./tests/tasks/assert_output_in_stderr_without_warnings.yml</code>, we assert no warning in
_network_connections_result, and assert stderr in _network_connections_result.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Assert</span><span class="nv"> </span><span class="s">that</span><span class="nv"> </span><span class="s">warnings</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">empty"</span>
  <span class="na">assert</span><span class="pi">:</span>
    <span class="na">that</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">'warnings'</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">__network_connections_result"</span>
    <span class="na">msg</span><span class="pi">:</span> <span class="s2">"</span><span class="s">There</span><span class="nv"> </span><span class="s">are</span><span class="nv"> </span><span class="s">unexpected</span><span class="nv"> </span><span class="s">warnings"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Assert</span><span class="nv"> </span><span class="s">that</span><span class="nv"> </span><span class="s">there</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">output</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">stderr"</span>
  <span class="na">assert</span><span class="pi">:</span>
    <span class="na">that</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">'stderr'</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">__network_connections_result"</span>
    <span class="na">msg</span><span class="pi">:</span> <span class="s2">"</span><span class="s">There</span><span class="nv"> </span><span class="s">are</span><span class="nv"> </span><span class="s">no</span><span class="nv"> </span><span class="s">messages</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">stderr"</span>
</code></pre></div></div>

<p>The following Ansible logs is extracted from same qemu testing result after the bug fixed:</p>

<p><img src="../../images/wenliang_blog_figure3.png" alt="" /></p>

<h2 id="demo-video">Demo video</h2>

<p>I made a demo video to show the bugs and refactoring logging of network module after bug fixed,
as well as additional test cases running result.</p>

<p><a href="https://www.youtube.com/watch?v=gmFN2wt8tv4"><img src="https://img.youtube.com/vi/gmFN2wt8tv4/0.jpg" alt="Separate INFO and DEBUG logs" /></a></p>

<h2 id="reference">Reference</h2>

<ol>
  <li>Refactor logging of network module, https://github.com/linux-system-roles/network/issues/29</li>
  <li>Separate debug and info logs from warnings, https://github.com/linux-system-roles/network/pull/207</li>
</ol>]]></content><author><name>Wen Liang</name></author><category term="bugfix" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Conversion to Collection - YAML roundtrip with ruamel</title><link href="linux-system-roles.github.io/2020/09/collection-conversion-ruamel-roundtrip" rel="alternate" type="text/html" title="Conversion to Collection - YAML roundtrip with ruamel" /><published>2020-09-22T12:00:00+00:00</published><updated>2020-09-22T12:00:00+00:00</updated><id>linux-system-roles.github.io/2020/09/collection-conversion-ruamel-roundtrip</id><content type="html" xml:base="linux-system-roles.github.io/2020/09/collection-conversion-ruamel-roundtrip"><![CDATA[<p>The System Roles team is working on making the roles available as a collection.
One of the challenges is that we have to continue to support the old style roles
for the foreseeable future due to customers using older versions of Ansible.
So rather than just create a github repository for the collection and do a
one-time conversion of all of the roles to collection format, we have decided to keep the existing
github role structure, and instead use a script to build the collection for
publishing in Galaxy.</p>

<h2 id="using-the-collections-keyword">Using the <code class="language-plaintext highlighter-rouge">collections:</code> keyword</h2>

<p>One strategy is to use the
<a href="https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook"><code class="language-plaintext highlighter-rouge">collections:</code></a>
keyword in the play.  For example:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Apply the kernel_settings role</span>
  <span class="na">hosts</span><span class="pi">:</span> <span class="s">all</span>
  <span class="na">roles</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">kernel_settings</span>
  <span class="na">tasks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">use the kernel_settings module</span>
      <span class="na">kernel_settings</span><span class="pi">:</span>
        <span class="s">...</span>
</code></pre></div></div>
<p>To use this role from a collection <code class="language-plaintext highlighter-rouge">fedora.system_roles</code>, you could use the
<code class="language-plaintext highlighter-rouge">collections:</code> keyword:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Apply the kernel_settings role</span>
  <span class="na">hosts</span><span class="pi">:</span> <span class="s">all</span>
  <span class="na">collections</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">fedora.system_roles</span>
  <span class="na">roles</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">kernel_settings</span>
  <span class="na">tasks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">use the kernel_settings module</span>
      <span class="na">kernel_settings</span><span class="pi">:</span>
        <span class="s">...</span>
</code></pre></div></div>
<p>However, the guidance we have received from the Ansible team is that we should
use FQRN (Fully Qualified Role Name) and FQCN (Fully Qualified Collection Name)
to avoid any naming collisions or ambiguity, and not to rely on the
<code class="language-plaintext highlighter-rouge">collections:</code> keyword.  This means we have a lot of conversion to do.  For Ansible YAML files, the two
main items are:</p>
<ul>
  <li>convert references to role <code class="language-plaintext highlighter-rouge">ROLENAME</code> and <code class="language-plaintext highlighter-rouge">linux-system-roles.ROLENAME</code> to
<code class="language-plaintext highlighter-rouge">fedora.system_roles.ROLENAME</code></li>
  <li>convert references to modules to use the FQCN e.g. <code class="language-plaintext highlighter-rouge">some_module:</code> to
<code class="language-plaintext highlighter-rouge">fedora.system_roles.some_module:</code></li>
</ul>

<h2 id="using-regular-expressions-to-searchreplace-strings">Using regular expressions to search/replace strings</h2>

<p>One solution is to use a regular expression match - just look for references to
<code class="language-plaintext highlighter-rouge">linux-system-roles.ROLENAME</code> and convert them to
<code class="language-plaintext highlighter-rouge">fedora.system_roles.ROLENAME</code>.  This works pretty well, but there is no
guarantee that there is some odd use of <code class="language-plaintext highlighter-rouge">linux-system-roles.ROLENAME</code> not
related to a role keyword.  It would be much better and safer if we could only
change those places where the role name is used in the semantic context of an
Ansible role reference.  For modules, it is quite tricky to do this
search/replace using a regexp.  To complicate matters, in the <code class="language-plaintext highlighter-rouge">network</code> role,
the module name <code class="language-plaintext highlighter-rouge">network_connections</code> is also used as a role variable name.  I’m
not sure how one would write a regexp that could detect the semantic context and
only replace the string <code class="language-plaintext highlighter-rouge">network_connections</code> with
<code class="language-plaintext highlighter-rouge">fedora.system_roles.network_connections</code> in the context of usage as an Ansible
module.</p>

<h2 id="using-the-ansible-parser">Using the Ansible parser</h2>

<p>The next solution was to use the Ansible parser
(<code class="language-plaintext highlighter-rouge">ansible.parsing.dataloader.DataLoader</code>) to read in the files with the full
semantic information.  We took inspiration from the <code class="language-plaintext highlighter-rouge">ansible-lint</code> code for
this, and used similar heuristics to determine the file and node types:</p>
<ul>
  <li>file location - files in the <code class="language-plaintext highlighter-rouge">vars/</code> and <code class="language-plaintext highlighter-rouge">defaults/</code> directories are not
<code class="language-plaintext highlighter-rouge">tasks/</code> files</li>
  <li>Ansible type - a tasks file has type <code class="language-plaintext highlighter-rouge">AnsibleSequence</code> not <code class="language-plaintext highlighter-rouge">AnsibleMapping</code></li>
  <li>node type - a <code class="language-plaintext highlighter-rouge">play</code> has one of the <code class="language-plaintext highlighter-rouge">play</code> keywords like <code class="language-plaintext highlighter-rouge">gather_facts</code>,
<code class="language-plaintext highlighter-rouge">tasks</code>, etc.</li>
</ul>

<p>For <code class="language-plaintext highlighter-rouge">task</code> nodes, we then use <code class="language-plaintext highlighter-rouge">ansible.parsing.mod_args.ModuleArgsParser</code> to
parse out the module name (as is done in <code class="language-plaintext highlighter-rouge">ansible-lint</code>).</p>

<p>For role references, we look for</p>
<ul>
  <li>a <code class="language-plaintext highlighter-rouge">task</code> with a module <code class="language-plaintext highlighter-rouge">include_role</code> or <code class="language-plaintext highlighter-rouge">import_role</code> with a <code class="language-plaintext highlighter-rouge">name</code> parameter</li>
  <li>a <code class="language-plaintext highlighter-rouge">play</code> with a <code class="language-plaintext highlighter-rouge">roles</code> keyword</li>
  <li>a <code class="language-plaintext highlighter-rouge">meta</code> with a <code class="language-plaintext highlighter-rouge">dependencies</code> keyword</li>
</ul>

<p>A role in a <code class="language-plaintext highlighter-rouge">roles</code> or <code class="language-plaintext highlighter-rouge">dependencies</code> may be referenced as</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>roles/dependencies:
  - ROLENAME
# OR
  - name: ROLENAME
    vars: ...
# OR
  - role: ROLENAME
    vars: ...
</code></pre></div></div>
<p>This allowed us to easily identify where the <code class="language-plaintext highlighter-rouge">ROLENAME</code> was referenced as a role
rather than something else, and to identify where the role modules were used.</p>

<p>The next problem - how to write out these converted files?  Just using a plain
YAML dump, even if nicely formatted, does not preserve all of our pre/post YAML
doc, comments, formatting, etc.  We thought it was important to keep this as
much as possible:</p>
<ul>
  <li>keep license headers in files</li>
  <li>helps visually determine if the collection conversion was successful</li>
  <li>when bugs come from customers using the collection, we can much better debug
and fix the source role if the line numbers and formatting match</li>
  <li>we’ll use this code when we eventually convert our repos in github to use the collection
format</li>
</ul>

<h2 id="using-ansible-and-ruamel">Using Ansible and ruamel</h2>

<p>The <code class="language-plaintext highlighter-rouge">ruamel.yaml</code> package has the ability to “round-trip” YAML files, preserving
comments, quoting, formatting, etc.  We borrowed another technique from
<code class="language-plaintext highlighter-rouge">ansible-lint</code> which parses and iterates Ansible files using both the Ansible
parser and the ruamel parser “in parallel” (<code class="language-plaintext highlighter-rouge">ansible-lint</code> is also comment
aware).  This is an excerpt from the role file parser class:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filepath</span><span class="p">,</span> <span class="n">rolename</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">filepath</span> <span class="o">=</span> <span class="n">filepath</span>
        <span class="n">dl</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span> <span class="o">=</span> <span class="n">dl</span><span class="p">.</span><span class="n">load_from_file</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">raise</span> <span class="n">LSRException</span><span class="p">(</span><span class="sa">f</span><span class="s">"file is empty </span><span class="si">{</span><span class="n">filepath</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">file_type</span> <span class="o">=</span> <span class="n">get_file_type</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">rolename</span> <span class="o">=</span> <span class="n">rolename</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span> <span class="o">=</span> <span class="n">YAML</span><span class="p">(</span><span class="n">typ</span><span class="o">=</span><span class="s">"rt"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">default_flow_style</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">preserve_quotes</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="n">buf</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">).</span><span class="n">read</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_data</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">indent</span><span class="p">(</span><span class="n">mapping</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">sequence</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">offset</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">outputfile</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">outputstream</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">stdout</span>
</code></pre></div></div>
<p>The class uses <code class="language-plaintext highlighter-rouge">ans_data</code> for looking at the data using Ansible semantics, and
uses <code class="language-plaintext highlighter-rouge">ruamel_data</code> for doing the modification and writing.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">file_type</span> <span class="o">==</span> <span class="s">"vars"</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">handle_vars</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_data</span><span class="p">)</span>
        <span class="k">elif</span> <span class="bp">self</span><span class="p">.</span><span class="n">file_type</span> <span class="o">==</span> <span class="s">"meta"</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">handle_meta</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_data</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">ans_data</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_data</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">handle_item</span><span class="p">(</span><span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">xform</span><span class="p">(</span><span class="n">thing</span><span class="p">):</span>
            <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">file_type</span> <span class="o">==</span> <span class="s">"tasks"</span><span class="p">:</span>
                <span class="n">thing</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="n">LSRFileTransformerBase</span><span class="p">.</span><span class="n">INDENT_RE</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> <span class="n">thing</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">thing</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">outputfile</span><span class="p">:</span>
            <span class="n">outstrm</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">outputfile</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">outstrm</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">outputstream</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ruamel_yaml</span><span class="p">.</span><span class="n">dump</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">ruamel_data</span><span class="p">,</span> <span class="n">outstrm</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">xform</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">handle_item</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">):</span>
        <span class="s">"""handle any type of item - call the appropriate handlers"""</span>
        <span class="n">ans_type</span> <span class="o">=</span> <span class="n">get_item_type</span><span class="p">(</span><span class="n">a_item</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">handle_vars</span><span class="p">(</span><span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">handle_other</span><span class="p">(</span><span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">ans_type</span> <span class="o">==</span> <span class="s">"task"</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">handle_task</span><span class="p">(</span><span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">handle_task_list</span><span class="p">(</span><span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">handle_task_list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a_item</span><span class="p">,</span> <span class="n">ru_item</span><span class="p">):</span>
        <span class="s">"""item has one or more fields which hold a list of Task objects"""</span>
        <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">TASK_LIST_KWS</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">a_item</span><span class="p">:</span>
                <span class="k">for</span> <span class="n">a_task</span><span class="p">,</span> <span class="n">ru_task</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">a_item</span><span class="p">[</span><span class="n">kw</span><span class="p">],</span> <span class="n">ru_item</span><span class="p">[</span><span class="n">kw</span><span class="p">]):</span>
                    <span class="bp">self</span><span class="p">.</span><span class="n">handle_item</span><span class="p">(</span><span class="n">a_task</span><span class="p">,</span> <span class="n">ru_task</span><span class="p">)</span>
</code></pre></div></div>
<p>The concrete class that uses this code provides callbacks for tasks, vars, meta,
and other, and the callback can change the data.  <code class="language-plaintext highlighter-rouge">a_task</code> is the <code class="language-plaintext highlighter-rouge">task</code> node
from the Ansible parser, and <code class="language-plaintext highlighter-rouge">ru_task</code> is the <code class="language-plaintext highlighter-rouge">task</code> node from the ruamel
parser.  <code class="language-plaintext highlighter-rouge">role_modules</code> is a <code class="language-plaintext highlighter-rouge">set</code> of names of the modules provided by the role.
<code class="language-plaintext highlighter-rouge">prefix</code> is e.g. <code class="language-plaintext highlighter-rouge">fedora.system_roles.</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">task_cb</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a_task</span><span class="p">,</span> <span class="n">ru_task</span><span class="p">,</span> <span class="n">module_name</span><span class="p">,</span> <span class="n">module_args</span><span class="p">,</span> <span class="n">delegate_to</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">module_name</span> <span class="o">==</span> <span class="s">"include_role"</span> <span class="ow">or</span> <span class="n">module_name</span> <span class="o">==</span> <span class="s">"import_role"</span><span class="p">:</span>
            <span class="n">rolename</span> <span class="o">=</span> <span class="n">ru_task</span><span class="p">[</span><span class="n">module_name</span><span class="p">][</span><span class="s">"name"</span><span class="p">]</span>
            <span class="n">lsr_rolename</span> <span class="o">=</span> <span class="s">"linux-system-roles."</span> <span class="o">+</span> <span class="bp">self</span><span class="p">.</span><span class="n">rolename</span>
            <span class="k">if</span> <span class="n">rolename</span> <span class="o">==</span> <span class="bp">self</span><span class="p">.</span><span class="n">rolename</span> <span class="ow">or</span> <span class="n">rolename</span> <span class="o">==</span> <span class="n">lsr_rolename</span><span class="p">:</span>
                <span class="n">ru_task</span><span class="p">[</span><span class="n">module_name</span><span class="p">][</span><span class="s">"name"</span><span class="p">]</span> <span class="o">=</span> <span class="n">prefix</span> <span class="o">+</span> <span class="bp">self</span><span class="p">.</span><span class="n">rolename</span>
        <span class="k">elif</span> <span class="n">module_name</span> <span class="ow">in</span> <span class="n">role_modules</span><span class="p">:</span>
            <span class="c1"># assumes ru_task is an orderreddict
</span>            <span class="n">idx</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">ru_task</span><span class="p">).</span><span class="n">index</span><span class="p">(</span><span class="n">module_name</span><span class="p">)</span>
            <span class="n">val</span> <span class="o">=</span> <span class="n">ru_task</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="n">module_name</span><span class="p">)</span>
            <span class="n">ru_task</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">prefix</span> <span class="o">+</span> <span class="n">module_name</span><span class="p">,</span> <span class="n">val</span><span class="p">)</span>
</code></pre></div></div>
<p>This produces an output file that is very close to the input - but not quite.</p>

<h2 id="problems-with-this-approach">Problems with this approach</h2>

<ul>
  <li>We can’t make ruamel do proper indentation of lists without having it do the
indentation at the first level.  For example:</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">first level</span>
  <span class="na">block</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">second level</span>
      <span class="na">something</span><span class="pi">:</span> <span class="s">something</span>
</code></pre></div></div>
<p>comes out as</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">first level</span>
    <span class="na">block</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">second level</span>
        <span class="na">something</span><span class="pi">:</span> <span class="s">something</span>
</code></pre></div></div>
<p>This is why we have the <code class="language-plaintext highlighter-rouge">xform</code> hack in the <code class="language-plaintext highlighter-rouge">write</code> method.</p>

<ul>
  <li>Even with the hack, comments are not indented correctly</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">first level</span>
  <span class="c1"># comment here</span>
  <span class="na">block</span><span class="pi">:</span>
    <span class="c1"># comment here</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">second level</span>
      <span class="na">something</span><span class="pi">:</span> <span class="s">something</span>
</code></pre></div></div>
<p>comes out as</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">first level</span>
  <span class="c1"># comment here</span>
    <span class="na">block</span><span class="pi">:</span>
    <span class="c1"># comment here</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">second level</span>
        <span class="na">something</span><span class="pi">:</span> <span class="s">something</span>
</code></pre></div></div>
<p>One approach would be to have <code class="language-plaintext highlighter-rouge">xform</code> skip the removal of the two extra spaces
at the beginning of the line if the first non-space character in the line is
<code class="language-plaintext highlighter-rouge">#</code>.  However, if you have <code class="language-plaintext highlighter-rouge">shell</code> scripts or embedded config files with
comments in them, these will then not be indented correctly, leading to
problems.  So for now, we just live with improperly indented Ansible comments.</p>

<ul>
  <li>Line wrapping is not preserved</li>
</ul>

<p>We use <code class="language-plaintext highlighter-rouge">yamllint</code> and have had to use some creative wrapping/folding to abide by
the line length restriction e.g.
<!--  --></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution']</span><span class="nv"> </span><span class="s">}}_</span><span class="se">\
</span>        <span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution_version']</span><span class="nv"> </span><span class="s">}}.yml"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution']</span><span class="nv"> </span><span class="s">}}_</span><span class="se">\
</span>        <span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution_major_version']</span><span class="nv"> </span><span class="s">}}.yml"</span>
</code></pre></div></div>
<!--  -->
<p>is converted to
<!--  --></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution']</span><span class="nv"> </span><span class="s">}}_{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution_version']</span><span class="se">\
</span>        <span class="se">\ </span><span class="s">}}.yml"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution']</span><span class="nv"> </span><span class="s">}}_{{</span><span class="nv"> </span><span class="s">ansible_facts['distribution_major_version']</span><span class="se">\
</span>        <span class="se">\ </span><span class="s">}}.yml"</span>
</code></pre></div></div>
<!--  -->
<p>that is, ruamel imposes its own line length and wrapping convention.</p>

<p>We also didn’t have to worry about how to handle usage of plugins inside of
<code class="language-plaintext highlighter-rouge">lookup</code> functions, which would seem to be a much more difficult problem.</p>]]></content><author><name>Rich Megginson</name></author><category term="talk" /><summary type="html"><![CDATA[The System Roles team is working on making the roles available as a collection. One of the challenges is that we have to continue to support the old style roles for the foreseeable future due to customers using older versions of Ansible. So rather than just create a github repository for the collection and do a one-time conversion of all of the roles to collection format, we have decided to keep the existing github role structure, and instead use a script to build the collection for publishing in Galaxy.]]></summary></entry><entry><title type="html">Release 2020-08-31</title><link href="linux-system-roles.github.io/2020/08/release" rel="alternate" type="text/html" title="Release 2020-08-31" /><published>2020-08-31T12:00:00+00:00</published><updated>2020-08-31T12:00:00+00:00</updated><id>linux-system-roles.github.io/2020/08/release</id><content type="html" xml:base="linux-system-roles.github.io/2020/08/release"><![CDATA[<p>Linux System Roles 2020-08-31 has been released to Galaxy.  This release has major enhancements to
the existing storage and network roles, as well as the addition of the following roles:</p>
<ul>
  <li>logging (e.g. rsyslog)</li>
  <li>metrics (e.g. pcp)</li>
  <li>certificate (e.g. certmonger - manage auto cert issuance and renewal)</li>
  <li>nbde_client, nbde_server (e.g. clevis, tang)</li>
  <li>tlog (session recording)</li>
  <li>kernel_settings (sysctl, sysfs)</li>
</ul>]]></content><author><name>Rich Megginson</name></author><category term="release" /><summary type="html"><![CDATA[Linux System Roles 2020-08-31 has been released to Galaxy. This release has major enhancements to the existing storage and network roles, as well as the addition of the following roles: logging (e.g. rsyslog) metrics (e.g. pcp) certificate (e.g. certmonger - manage auto cert issuance and renewal) nbde_client, nbde_server (e.g. clevis, tang) tlog (session recording) kernel_settings (sysctl, sysfs)]]></summary></entry></feed>