Updates for 0.7.0
All checks were successful
CI / test (push) Successful in 2m2s

This commit is contained in:
Miguel Jacq 2026-06-21 09:50:20 +10:00
parent 9229606588
commit 8c5d99b4b6
Signed by: mig5
GPG key ID: 03906B4110AAD3B8
2 changed files with 68 additions and 22 deletions

View file

@ -162,7 +162,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
<br />
<div class="small mb-0">Keep in mind that a lot of software config files are also good candidates for being Jinja templates with abstracted vars for separate hosts.</div>
<br />
<div class="small mb-0">Enroll does use my companion tool <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll cannot turn a config file into a template, it copies the raw file instead. JinjaTurtle templating is currently most useful for Ansible output; Puppet and Salt output stay closer to raw managed files and target-native data.</div>
<div class="small mb-0">Enroll does use my companion tool <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll cannot turn a config file into a template, it copies the raw file instead. JinjaTurtle creates Jinja2 templates for both Ansible and Salt, and because it's a cheerful turtle, it generates <code>.erb</code> templates if you're manifesting Puppet code, too!</div>
</div>
</section>
@ -265,7 +265,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
<section id="templates" class="scroll-mt-nav mb-5">
<h2 class="section-title fw-bold">JinjaTurtle integration</h2>
<p class="text-secondary">If <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible, instead of just storing the 'raw' files. Puppet and Salt output currently focus on raw managed files plus target-native data.</p>
<p class="text-secondary">If <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible and Salt, and even erb files for Puppet (as of version 0.5.5).</p>
<div class="row g-3">
<div class="col-md-6">
<div class="feature-card p-4 h-100">

View file

@ -18,20 +18,20 @@ summary: "Enroll can now render Puppet and Salt manifests alongside Ansible, det
<div class="row g-4">
<div class="col-lg-8">
<article class="feature-card p-4">
<p class="text-secondary">Welcome to the first of Enroll's new, erm, news section! To celebrate, Enroll 0.7.0 has been released, and makes manifest rendering target-selectable based on your preferred config management tool! Ansible remains the default, but Puppet and Salt are now possible too (with a few small exceptions, read on).</p>
<p class="text-secondary">Welcome to the first of Enroll's new, erm, news section! To celebrate, Enroll 0.7.0 has been released, and makes manifest rendering target-selectable based on your preferred config management tool! Ansible remains the default, but Puppet and Salt are now possible too.</p>
<h2 class="h4 fw-bold mt-4">Highlights</h2>
<h2 class="h4 fw-bold mt-4">Highlights in 0.7.0</h2>
<ul>
<li><code>--target puppet</code> renders Puppet module/control-repo style output., and in <code>--fqdn</code> mode, renders per-host Hiera data.</li>
<li><code>--target salt</code> renders Salt state trees and, in <code>--fqdn</code> mode, Salt pillar data. Since it's Python, it also will make use of JinjaTurtle (if it finds it on your <code>$PATH</code>) to render templates, just like Ansible!</li>
<li><strong>Puppet support!</strong> <code>--target puppet</code> renders Puppet module/control-repo style output., and in <code>--fqdn</code> mode, renders per-host Hiera data.</li>
<li><strong>Salt Stack support!</strong> <code>--target salt</code> renders Salt state trees and, in <code>--fqdn</code> mode, Salt pillar data.</li>
<li>Ansible works basically as it always did, and is the default, but you can specify <code>--target ansible</code> too. As usual, in <code>--fqdn</code> mode, specific artifacts end up in <code>host_vars</code> inventory folders rather than polluting the 'golden' roles.</li>
<li>All three config management manifest renderers derive from the same harvest state. You can rendered repeatedly into different config management tools without re-harvesting the host!</li>
<li>Single-site output tries to combine package/service data by their package manager's <code>Section</code> (or equivalent metadata), to reduce role/module/state sprawl.</li>
<li>All three config management manifest renderers derive from the same harvest state as a single source of truth. Evaluating how different config managers work? You can rendered repeatedly into different config management tools without re-harvesting the host!</li>
<li>Single-site output tries to combine package/service data by their package manager's <code>Section</code> (or equivalent metadata), to reduce role/module/state sprawl and speed up execution.</li>
<li>Flatpak and Snap detection!</li>
<li>Docker image detection!</li>
</ul>
<h2 class="h4 fw-bold mt-4">Dry-run examples</h2>
<h2 class="h4 fw-bold mt-4">Dry-run examples: choose your own config management adventure!</h2>
<div class="codeblock terminal">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#news-070-noop"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="mb-0"><code id="news-070-noop"><span class="prompt">$</span> enroll harvest --out ./harvest
@ -46,20 +46,26 @@ summary: "Enroll can now render Puppet and Salt manifests alongside Ansible, det
<span class="prompt">$</span> salt-call --local --file-root ./salt/states state.apply test=True</code></pre>
</div>
<div class="asciicast" data-asciinema-id="1258154"><script src="https://asciinema.org/a/1258154.js" id="asciicast-1258154" async="true"></script></div>
<p>I recommend using Salt 3008.1 - on Debian 13 I encountered annoying noisy Python errors with version 3007, which are unrelated to Enroll.</p>
<p>Bonus: since Salt uses Jinja2 templates, it will take advantage of my other tool <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a>, if it's on your <code>PATH</code>, just like Ansible does!</p>
<p>And because I didn't want Puppet users to feel left out, version 0.5.5 of JinjaTurtle (despite its name) also supports <code>erb</code> templates as well. This means Puppet templates will be generated if you have JinjaTurtle and there are viable config files for it, too!</p>
<h2 class="h4 fw-bold mt-4">New grouping behaviour in roles/modules</h2>
<p class="text-secondary">Did you find the number of manifested roles overwhelming?</p>
<p>Previously, Enroll created an Ansible role (or, now, a Puppet module or Salt role) for pretty much every 'package' it found. In some cases (especially on desktops) this could result in hundreds of roles. Technically fine, but overwhelming to look at!</p>
<p>Previously, Enroll created an Ansible role (or, now, a Puppet module or Salt role) for pretty much every 'package' it found. In some cases (especially on desktops) this could result in hundreds of roles. Technically fine, but overwhelming to look at! It also made the playbooks a bit slow to run. If you have fewer roles that 'loop' over packages to install and config files to manage, Ansible gets faster.</p>
<p>As of 0.7.0, where Enroll can read that package metadata, it groups related package and service snapshots by the package manager's <code>Section</code> category (or comparable backend metadata), to make it less noisy. For example, network-related packages and config files might end up in a role called <code>net</code>. Meanwhile, vim, nano might both appear in <code>editors</code>, and mutt and Thunderbird may be in <code>mail</code>.</p>
<p>As of 0.7.0, where Enroll can read that package metadata, it groups related package and service snapshots by the package manager's <code>Section</code> category (or comparable backend metadata), to make it less noisy. For example, network-related packages and config files might end up in a role called <code>net</code>. Meanwhile, vim, nano might both appear in <code>editors</code>, and mutt and Thunderbird may be in <code>mail</code>. It's easier on the eye, and it's quicker to run the playbook end to end!</p>
<p>If you're not a fan of this new layout, you can pass <code>--no-common-roles</code> to enforce the previous behaviour. Also, if you use <code>--fqdn</code> for host-specific data-driven output, the 'common' roles are disabled automatically, because it's then safer to avoid 'bleed in' of unnecessary package installation on other hosts from a role that otherwise 'assumes too much' for all hosts.</p>
<p>Hello, opinions. If you're not a fan of this new layout, you can pass <code>--no-common-roles</code> to enforce the previous behaviour. Also, if you use <code>--fqdn</code> for host-specific data-driven output, the 'common' roles are disabled automatically, because it's then safer to avoid 'bleed in' of unnecessary package installation on other hosts from a role that otherwise 'assumes too much' for all hosts.</p>
<h2 class="h4 fw-bold mt-4">Flatpak and Snap detection</h2>
<p class="text-secondary">Beyond deb and rpm</p>
<p>When using Ansible, Enroll now attempts to detect Flatpak and Snaps present on the system. For Flatpaks, this includes user-specific Flatpaks as well as system-wide ones. Manifesting to Ansible will attempt to use the <code>community.general</code> collection to create <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/flatpak/" target="_blank" rel="noopener noreferrer">Flatpak</a> and <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/snap/" target="_blank" rel="noopener noreferrer">Snap</a> tasks to enforce the presence of those packages.</p>
<p>For now, unfortunately Flatpak/Snap manifesting will not occur for Puppet or Snap, just Ansible. Either way, the 'detection' still occurs at the <code>harvest</code> level, so you can still take advantage of that with other subcommands like <code>enroll diff</code>.</p>
<p class="text-secondary">Because the state of package management in the 2020s is a circus...</p>
<p>Enroll now attempts to detect Flatpak and Snaps present on the system. For Flatpaks, this includes user-specific Flatpaks as well as system-wide ones. Manifesting in Ansible will attempt to use the <code>community.general</code> collection to create <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/flatpak/" target="_blank" rel="noopener noreferrer">Flatpak</a> and <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/snap/" target="_blank" rel="noopener noreferrer">Snap</a> tasks to enforce the presence of those packages.</p>
<p>Flatpak/Snap manifesting is also available for Puppet and Snap, but it's slightly cruder through the use of guarded cmd/exec statements - I found this keeps things simpler than having to add third party modules/extensions (and the state of extensions in Salt Stack right now, <a href="https://salt.tips/the-great-salt-module-migration/" target="_blank" rel="noopener noreferrer">is a bit of a mess</a>, IMO).</p>
<div class="codeblock terminal">
<pre class="mb-0"><code id="news-070-snap"><span class="prompt">$</span> sudo ansible-playbook playbook.yml -i localhost, -c local --tags role_snap --diff
@ -80,11 +86,44 @@ ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangero
</code></pre>
</div>
<h2 class="h4 fw-bold mt-4">Docker image detection</h2>
<p class="text-secondary">Continuing to sniff out bits from your system...</p>
<p>The <code>harvest</code> now detects the presence of Docker images, if it has permission. In particular, it detects the SHA256 of the Docker image. All three renderers (Ansible, Salt and Puppet) will attempt to enforce the presence of those Docker images. For Puppet, you may need the <code>puppetlabs-docker</code> module installed. There are details in the manifested README.md of the Puppet code. For Ansible, you may need the <code>community.docker</code> collection, but on Debian 13 I found that it was already present by default in the official ansible Debian packages.</p>
<h2 class="h4 fw-bold mt-4">Docker/Podman image detection</h2>
<p class="text-secondary">Because it works on your machine....</p>
<p>The <code>harvest</code> now detects the presence of container images, if the user has permission to call Docker or Podman. In particular, it detects the SHA256 of the image instead of relying on floating tags.</p>
<p>All three renderers (Ansible, Salt and Puppet) will attempt to enforce the presence of those Docker images per their precise SHA256 hash, if they were present in the harvest but not on the machine upon applying a manifest.</p>
<p>For Ansible, you may need the <code>community.docker</code> collection, but on Debian 13 I found that it was already present by default in the official ansible Debian packages.</p>
<p>For Ansible, if using Podman, you'll need 1.20.0 or later. Enroll creates a <code>requirements.yml</code> to make it easy for you: its README.md will guide you to run <code>ansible-galaxy collection install -r requirements.yml</code> before running the playbook.</p>
<p>Please be aware that the Puppet and Salt renderers do not support the Flatpak/Snap package enforcement - only Ansible for now. You'll also need version 13+ of the <code>community.general</code> collection for this to work properly. A <code>requirements.yml</code> gets created with your manifest to help you install it if necessary.</p>
<div class="codeblock terminal">
<pre class="mb-0"><code id="news-070-snap"><span class="prompt">$</span> ansible-playbook -i localhost, -c local playbook.yml --check --diff --tags role_container_images
PLAY [Apply all roles on all hosts] *******************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [container_images : Pull Docker images by immutable registry digest] *****************************************************************************************************************************************
ok: [localhost] => (item={'architecture': 'amd64', 'created': '2026-06-17T22:44:41.625799723Z', 'engine': 'docker', 'home': None, 'image_id': 'sha256:eaf6f386053efdf0ad30236a394b4460e3669006afc9eb1220ae2047f330cc9c', 'notes': [], 'os': 'linux', 'platform': 'linux/amd64', 'pull_ref': 'nginx@sha256:42f2d24ae18df9b5251d1cc45548085656d2335e9338fd150a24e415462d151f', 'repo_digests': ['nginx@sha256:42f2d24ae18df9b5251d1cc45548085656d2335e9338fd150a24e415462d151f'], 'repo_tags': ['nginx:latest'], 'scope': 'system', 'size': 161308134, 'source': 'docker image inspect', 'tag_aliases': [{'ref': 'nginx:latest', 'repository': 'nginx', 'tag': 'latest'}], 'user': None, 'variant': None})
TASK [container_images : Tag Docker images with harvested tag aliases] ********************************************************************************************************************************************
ok: [localhost] => (item=[{'architecture': 'amd64', 'created': '2026-06-17T22:44:41.625799723Z', 'engine': 'docker', 'home': None, 'image_id': 'sha256:eaf6f386053efdf0ad30236a394b4460e3669006afc9eb1220ae2047f330cc9c', 'notes': [], 'os': 'linux', 'platform': 'linux/amd64', 'pull_ref': 'nginx@sha256:42f2d24ae18df9b5251d1cc45548085656d2335e9338fd150a24e415462d151f', 'repo_digests': ['nginx@sha256:42f2d24ae18df9b5251d1cc45548085656d2335e9338fd150a24e415462d151f'], 'repo_tags': ['nginx:latest'], 'scope': 'system', 'size': 161308134, 'source': 'docker image inspect', 'user': None, 'variant': None}, {'ref': 'nginx:latest', 'repository': 'nginx', 'tag': 'latest'}])
TASK [container_images : Pull system Podman images by immutable registry digest] **********************************************************************************************************************************
ok: [localhost] => (item={'architecture': 'amd64', 'created': '2026-06-16T00:01:29.967161902Z', 'engine': 'podman', 'home': None, 'image_id': 'sha256:d529dd0c6e5597ac7e4a3e2dea65c3fcc6173f4cae713c409265c1dd9914a11b', 'notes': [], 'os': 'linux', 'platform': 'linux/amd64', 'pull_ref': 'docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b', 'repo_digests': ['docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b', 'docker.io/library/alpine@sha256:79ff19e9084a00eece421b2523fb93e22d730e2c0e525905de047e848e56d95f'], 'repo_tags': ['docker.io/library/alpine:latest'], 'scope': 'system', 'size': 8709729, 'source': 'podman image inspect', 'tag_aliases': [{'ref': 'docker.io/library/alpine:latest', 'repository': 'docker.io/library/alpine', 'tag': 'latest'}], 'user': None, 'variant': None})
TASK [container_images : Tag system Podman images with harvested tag aliases] *************************************************************************************************************************************
ok: [localhost] => (item=[{'architecture': 'amd64', 'created': '2026-06-16T00:01:29.967161902Z', 'engine': 'podman', 'home': None, 'image_id': 'sha256:d529dd0c6e5597ac7e4a3e2dea65c3fcc6173f4cae713c409265c1dd9914a11b', 'notes': [], 'os': 'linux', 'platform': 'linux/amd64', 'pull_ref': 'docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b', 'repo_digests': ['docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b', 'docker.io/library/alpine@sha256:79ff19e9084a00eece421b2523fb93e22d730e2c0e525905de047e848e56d95f'], 'repo_tags': ['docker.io/library/alpine:latest'], 'scope': 'system', 'size': 8709729, 'source': 'podman image inspect', 'user': None, 'variant': None}, {'ref': 'docker.io/library/alpine:latest', 'repository': 'docker.io/library/alpine', 'tag': 'latest'}])
TASK [container_images : Pull user Podman images by immutable registry digest] ************************************************************************************************************************************
skipping: [localhost]
TASK [container_images : Tag user Podman images with harvested tag aliases] ***************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
</code></pre>
</div>
<p>I did not use community extensions/modules for Docker in the Salt and Puppet renderers, because, well, they are god-awful (the Salt one <a href="https://github.com/salt-extensions/saltext-dockermod/issues/6" target="_blank" rel="noopener noreferrer">simply doesn't work in 3008.1</a>, and the Puppet one is non-idempotent and I would argue cruder in its approach to image management than a guarded <code>Exec</code> call can be (and is).</p>
<h2 class="h4 fw-bold mt-4">Other smaller changes</h2>
<ul>
@ -95,7 +134,7 @@ ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangero
</ul>
<h2 class="h4 fw-bold mt-4">See you soon..</h2>
<p>I'm off to try and write more tests - we're at 83% coverage in pytest, and we also run a stack of 'noop' executions for Ansible, Puppet and Salt too now, <a href="https://git.mig5.net/mig5/enroll/actions/runs/592" target="_blank" rel="noopener noreferrer">in CI</a>.</p>
<p>I'm off to try and write more tests - we're at about 85% coverage in pytest, and we also run a stack of 'noop' executions for Ansible, Puppet and Salt too now, <a href="https://git.mig5.net/mig5/enroll/actions/runs/592" target="_blank" rel="noopener noreferrer">in CI</a>.</p>
<p>Thanks to everyone who has reached out with suggestions, constructive criticism, and bug reports! You're helping make Enroll better for everyone.</p>
</ul>
@ -104,7 +143,7 @@ ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangero
<div class="col-lg-4">
<div class="callout p-4">
<div class="fw-semibold mb-2">Targets</div>
<div class="fw-semibold mb-2">Target your preferred config manager</div>
<ul class="small mb-0">
<li><code>--target ansible</code> (default)</li>
<li><code>--target puppet</code></li>
@ -118,6 +157,13 @@ ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangero
<a class="btn btn-sm btn-outline-dark" href="../examples.html"><i class="bi bi-terminal"></i> Examples</a>
</div>
</div>
<div class="callout p-4">
<div class="fw-semibold mb-2">There's more to Enroll!</div>
<div class="d-grid gap-2">
<a class="btn btn-sm btn-outline-dark" href="../docs.html#diff"><i class="bi bi-diff"></i> Check out 'diff' mode</a>
<a class="btn btn-sm btn-outline-dark" href="../docs.html#explain"><i class="bi bi-question"></i> Explain/describe a harvest</a>
</div>
</div>
</div>
</div>
</div>