<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Configuration Management on Manuel Herrmann</title>
    <link>https://blog.0x17.de/categories/configuration-management/</link>
    <description>Recent content in Configuration Management on Manuel Herrmann</description>
    <image>
      <title>Manuel Herrmann</title>
      <url>https://blog.0x17.de/images/mh.jpg</url>
      <link>https://blog.0x17.de/images/mh.jpg</link>
    </image>
    <generator>Hugo -- 0.131.0</generator>
    <language>en</language>
    <lastBuildDate>Mon, 31 Mar 2025 23:00:00 +0200</lastBuildDate>
    <atom:link href="https://blog.0x17.de/categories/configuration-management/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Puppet vs. Ansible: Game-Changing Differences in Modern Infrastructure Automation</title>
      <link>https://blog.0x17.de/post/puppet-automation-insights/</link>
      <pubDate>Mon, 31 Mar 2025 23:00:00 +0200</pubDate>
      <guid>https://blog.0x17.de/post/puppet-automation-insights/</guid>
      <description>After working extensively with both Puppet and Ansible, I&amp;rsquo;ve discovered some powerful features in the Puppet ecosystem that deserve more attention. In this post, I&amp;rsquo;ll share my experiences with Hiera and Puppet Bolt, highlighting how they compare to Ansible&amp;rsquo;s approach.
Hiera vs. Ansible&amp;rsquo;s host_vars and group_vars One of the most significant differences between Puppet and Ansible is how they handle variable hierarchies. Ansible uses host_vars and group_vars directories, which provide a straightforward but somewhat rigid approach to configuration data.</description>
      <content:encoded><![CDATA[<p>After working extensively with both Puppet and Ansible, I&rsquo;ve discovered some powerful features in the Puppet ecosystem that deserve more attention. In this post, I&rsquo;ll share my experiences with Hiera and Puppet Bolt, highlighting how they compare to Ansible&rsquo;s approach.</p>
<p><img alt="intro" loading="lazy" src="/post/puppet-automation-insights/intro.png"></p>
<h2 id="hiera-vs-ansibles-host_vars-and-group_vars">Hiera vs. Ansible&rsquo;s host_vars and group_vars</h2>
<p>One of the most significant differences between Puppet and Ansible is how they handle variable hierarchies. Ansible uses <code>host_vars</code> and <code>group_vars</code> directories, which provide a straightforward but somewhat rigid approach to configuration data. In contrast, Puppet&rsquo;s <a href="https://www.puppet.com/docs/puppet/8/hiera_intro.html">Hiera</a> offers a much more sophisticated system.</p>
<h3 id="why-hiera-shines">Why Hiera Shines</h3>
<p>Hiera&rsquo;s approach to configuration lookup is fundamentally different and more flexible:</p>
<ol>
<li>
<p><strong>Fact-based lookups</strong>: Hiera can use any fact about a system to determine which configuration files to load. This goes far beyond Ansible&rsquo;s group-based approach, allowing for configurations based on operating system, hardware details, custom facts, or any combination thereof.</p>
</li>
<li>
<p><strong>Hierarchical merging</strong>: Hiera doesn&rsquo;t just stop at the first match like Ansible often does. Instead, it can merge data from multiple levels of the hierarchy, allowing for elegant inheritance patterns.</p>
</li>
<li>
<p><strong>Lookup specificity</strong>: When Hiera finds a key at one level of the hierarchy, it doesn&rsquo;t override it with values from less specific levels. This &ldquo;first match wins&rdquo; approach ensures that the most specific configuration always takes precedence.</p>
</li>
</ol>
<p>This flexibility makes Hiera an incredibly powerful tool for managing configurations across diverse environments. For instance, you can define a base configuration that applies to all systems, override specific settings for a particular OS, and then further customize for individual hosts—all without repetition.</p>
<h3 id="hiera-in-action-a-real-world-example">Hiera in Action: A Real-World Example</h3>
<p>Let&rsquo;s look at an actual Hiera configuration to understand its power (example taken from the <a href="https://www.puppet.com/docs/puppet/8/hiera_intro.html">official Puppet documentation</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#ae81ff">5</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">defaults</span>:  <span style="color:#75715e"># Used for any hierarchy level that omits these keys.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">datadir</span>: <span style="color:#ae81ff">data        </span> <span style="color:#75715e"># This path is relative to hiera.yaml&#39;s directory.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">data_hash</span>: <span style="color:#ae81ff">yaml_data </span> <span style="color:#75715e"># Use the built-in YAML backend.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">hierarchy</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-node data&#34;</span>                   <span style="color:#75715e"># Human-readable name.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;nodes/%{trusted.certname}.yaml&#34;</span>  <span style="color:#75715e"># File path, relative to datadir.</span>
</span></span><span style="display:flex;"><span>                                   <span style="color:#75715e"># ^^^ IMPORTANT: include the file extension!</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-datacenter business group data&#34;</span> <span style="color:#75715e"># Uses custom facts.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;location/%{facts.whereami}/%{facts.group}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Global business group data&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;groups/%{facts.group}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-datacenter secret data (encrypted)&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">lookup_key</span>: <span style="color:#ae81ff">eyaml_lookup_key  </span> <span style="color:#75715e"># Uses non-default backend.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;secrets/%{facts.whereami}.eyaml&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">options</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">pkcs7_private_key</span>: <span style="color:#ae81ff">/etc/puppetlabs/puppet/eyaml/private_key.pkcs7.pem</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">pkcs7_public_key</span>:  <span style="color:#ae81ff">/etc/puppetlabs/puppet/eyaml/public_key.pkcs7.pem</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Per-OS defaults&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;os/%{facts.os.family}.yaml&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Common data&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;common.yaml&#34;</span>
</span></span></code></pre></div><p>This configuration demonstrates several key strengths of Hiera:</p>
<ol>
<li>
<p><strong>Multiple layers of specificity</strong>: The hierarchy starts with node-specific data (<code>nodes/%{trusted.certname}.yaml</code>), then moves through various levels of abstraction (datacenter + business group, business group alone, datacenter-specific secrets), down to OS-specific settings, and finally to common data that applies to all systems.</p>
</li>
<li>
<p><strong>Dynamic path generation</strong>: Notice how paths include variables like <code>%{trusted.certname}</code>, <code>%{facts.whereami}</code>, and <code>%{facts.os.family}</code>. These are interpolated with actual system facts at runtime, creating a dynamic configuration system that adapts to each node.</p>
</li>
<li>
<p><strong>Mixed backend support</strong>: While most data comes from YAML files, the configuration demonstrates how you can use different backends (like the encrypted <code>eyaml_lookup_key</code>) for sensitive data, with backend-specific options.</p>
</li>
<li>
<p><strong>Clear progression</strong>: When Hiera looks up a value, it starts at the top of this hierarchy and works its way down. Once it finds a value, it stops looking (unless you&rsquo;re using a merge strategy). This means more specific configurations naturally override more general ones.</p>
</li>
</ol>
<p>In Ansible, achieving this level of configuration flexibility would require complex variable precedence rules and possibly custom plugins. Hiera makes it declarative and straightforward.</p>
<h2 id="puppet-bolt-a-fresh-approach-to-remote-execution">Puppet Bolt: A Fresh Approach to Remote Execution</h2>
<p><a href="https://www.puppet.com/docs/bolt/latest/bolt">Puppet Bolt</a> is another gem in the Puppet ecosystem that offers an interesting alternative to Ansible for ad-hoc tasks and orchestration.</p>
<h3 id="speed-and-flexibility">Speed and Flexibility</h3>
<p>Bolt takes a different approach to remote execution that brings both advantages and limitations:</p>
<ul>
<li>
<p><strong>Script-based execution</strong>: While Ansible relies heavily on Python modules, Bolt embraces a more universal approach by simply executing scripts on remote systems. This might seem less sophisticated at first, but it brings remarkable flexibility.</p>
</li>
<li>
<p><strong>Language agnosticism</strong>: Bolt doesn&rsquo;t care what language your script is written in. Bash, Python, PowerShell, Ruby—it&rsquo;s all the same to Bolt. This removes the dependency on specific Python versions that can sometimes complicate Ansible deployments.</p>
</li>
<li>
<p><strong>Simplicity in execution</strong>: Sometimes a simple bash script is the easiest solution to a problem. Bolt acknowledges this reality and makes it trivial to run whatever makes the most sense for a given task.</p>
</li>
</ul>
<h3 id="limitations-and-considerations">Limitations and Considerations</h3>
<p>Despite its strengths, Bolt isn&rsquo;t without challenges:</p>
<ul>
<li>
<p><strong>JSON string conversion</strong>: One frustration I&rsquo;ve encountered is that converting JSON strings to objects isn&rsquo;t natively supported. You either need to use modules or extend Bolt with custom functions. If you&rsquo;re not interested in diving into Ruby or managing additional module dependencies, this can be a drawback.</p>
</li>
<li>
<p><strong>Less granular modules</strong>: We do lose some of the fine-grained task-level module support that Ansible provides. This is the trade-off for Bolt&rsquo;s flexibility.</p>
</li>
</ul>
<h3 id="interactive-use-and-documentation">Interactive Use and Documentation</h3>
<p>Where Bolt really shines is in its usability:</p>
<ul>
<li>
<p><strong>Standalone operation</strong>: Using Bolt without the broader Puppet ecosystem works perfectly fine, making it accessible even if you&rsquo;re not fully committed to Puppet.</p>
</li>
<li>
<p><strong>Interactive experience</strong>: Bolt feels designed for more interactive use than Ansible, with features like the <code>prompt::menu</code> function that provides interactive dropdown options, allowing users to make selections directly in the terminal during plan execution.</p>
</li>
<li>
<p><strong>Superior plan discovery</strong>: The <code>bolt plan show</code> command provides a clean, organized list of all available plans. When viewing a specific plan, Bolt displays detailed documentation that&rsquo;s easily added through comments in the code.</p>
</li>
<li>
<p><strong>Task targeting flexibility</strong>: Plans in Bolt are conceptually similar to Ansible playbooks, but with a key difference in execution model. In Bolt, each task within a plan can target a different set of hosts, giving you greater flexibility. This contrasts with Ansible, where a play defines a list of tasks that all run against the same list of hosts. This distinction makes Bolt plans more adaptable when you need different actions on different host groups within the same workflow.</p>
<p>Here&rsquo;s a simple example showing how Bolt plans can target different sets of hosts for different tasks:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-puppet" data-lang="puppet"><span style="display:flex;"><span><span style="color:#a6e22e">plan</span> <span style="color:#a6e22e">my_module</span>::<span style="color:#a6e22e">my_deployment</span>(<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#a6e22e">TargetSpec</span> $web_servers,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#a6e22e">TargetSpec</span> $db_servers,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#a6e22e">String</span> $version<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>) {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # First, update the database servers
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # Using catch_errors to prevent plan failure on error</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  $db_results <span style="color:#f92672">=</span> <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;database::update&#39;</span>, $db_servers, { <span style="color:#e6db74">&#39;version&#39;</span> <span style="color:#f92672">=&gt;</span> $version }, <span style="color:#a6e22e">_catch_errors</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">  # Check if database update was successful</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  <span style="color:#66d9ef">if</span> $db_results.ok {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Run a simple command on the web servers to verify they&#39;re online</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    $web_status <span style="color:#f92672">=</span> <span style="color:#a6e22e">run_command</span>(<span style="color:#e6db74">&#39;systemctl status nginx&#39;</span>, $web_servers)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Deploy the new application version to web servers</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;webapp::deploy&#39;</span>, $web_servers, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;version&#39;</span> <span style="color:#f92672">=&gt;</span> $version,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;db_endpoint&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">get_db_endpoint</span>($db_results)<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    })<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Send success notification</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;notification::send&#39;</span>, <span style="color:#e6db74">&#39;monitoring.example.com&#39;</span>, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;message&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;Deployment of version ${version} completed successfully&#34;</span>,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;status&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;success&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    })<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  } <span style="color:#66d9ef">else</span> {<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Send failure notification with details</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    $error_msg <span style="color:#f92672">=</span> $db_results.error_set.first.error.message<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#a6e22e">run_task</span>(<span style="color:#e6db74">&#39;notification::send&#39;</span>, <span style="color:#e6db74">&#39;monitoring.example.com&#39;</span>, {<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;message&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#34;Deployment of version ${version} failed during database update: ${error_msg}&#34;</span>,<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>      <span style="color:#e6db74">&#39;status&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;failure&#39;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    })<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Log details for investigation</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#a6e22e">out</span>::<span style="color:#a6e22e">message</span>(<span style="color:#e6db74">&#34;Database update failed: ${error_msg}&#34;</span>)<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    # Raise an exception that the plan failed</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    <span style="color:#a6e22e">fail_plan</span>(<span style="color:#e6db74">&#34;Deployment failed at database stage&#34;</span>)<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>  }<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>}<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>Notice how <code>run_task()</code> and <code>run_command()</code> take a target parameter (<code>$db_servers</code>, <code>$web_servers</code>, or even a specific hostname), allowing each operation to be directed at precisely the right group of hosts. This syntax <code>run_task($task_name, $targets, $args)</code> makes the targeting explicit and flexible.</p>
</li>
</ul>
<h3 id="plan-functions-and-built-in-capabilities">Plan Functions and Built-in Capabilities</h3>
<p>Bolt comes with a rich set of built-in plan functions that extend its capabilities. The <a href="https://www.puppet.com/docs/bolt/latest/plan_functions">Bolt Plan Functions documentation</a> provides a comprehensive reference of these functions, including file manipulation, error handling, and interactive prompts like <code>prompt::menu</code>. These functions make Bolt plans powerful and flexible without requiring external dependencies.</p>
<h3 id="parameter-handling-and-variable-scope">Parameter Handling and Variable Scope</h3>
<p>The way Bolt handles parameters and variables feels more like proper programming:</p>
<ul>
<li>
<p><strong>Function-like parameters</strong>: Plans have proper parameters, similar to functions in programming languages, with validation checks at the beginning of execution.</p>
</li>
<li>
<p><strong>Cleaner than Ansible&rsquo;s <code>-e</code></strong>: Compared to the common practice of injecting variables with <code>-e</code> in Ansible, Bolt&rsquo;s approach feels more correct and maintainable. It asserts that all arguments are defined and used properly.</p>
</li>
<li>
<p><strong>Sensible variable scoping</strong>: Ansible&rsquo;s variable precedence can sometimes lead to confusion, with <code>-e</code> variables overriding <code>set_fact</code> values in ways that might be unexpected. Bolt follows traditional programming scopes, where variables have clear context and shouldn&rsquo;t be redeclared.</p>
</li>
</ul>
<h3 id="inventory-handling">Inventory Handling</h3>
<p>Like Ansible, Bolt supports dynamic inventories:</p>
<ul>
<li>
<p><strong>Language flexibility</strong>: Again embracing its language-agnostic approach, Bolt allows you to write dynamic inventories in any programming language. The only requirement is that they return properly formatted JSON.</p>
</li>
<li>
<p><strong>Task-based inventory sources</strong>: Bolt makes it incredibly simple to use a task as an inventory source. As documented in the <a href="https://www.puppet.com/docs/bolt/latest/bolt_inventory_reference#example-file">Bolt inventory reference</a>, you can create a dynamic inventory by simply adding a <code>_plugin: task</code> key at the root level of your inventory file, and then specifying a <code>task</code> key with the name of a module::task to execute. This task returns the inventory data, making it easy to generate target lists from external sources like cloud providers, CMDBs, or custom databases.</p>
<p>Here&rsquo;s a minimal example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># inventory.yaml</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">_plugin</span>: <span style="color:#ae81ff">task</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">task</span>: <span style="color:#ae81ff">my_module::get_targets</span>
</span></span></code></pre></div><p>This simple configuration tells Bolt to execute the <code>my_module::get_targets</code> task and use its output as the inventory for the current run.</p>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>While Ansible remains a popular and powerful tool, my experience with Puppet&rsquo;s Hiera and Bolt has revealed significant advantages in certain scenarios. Hiera&rsquo;s sophisticated data hierarchy provides exceptional flexibility for configuration management, while Bolt offers a refreshing approach to remote execution that balances simplicity with power.</p>
<p><img alt="conclusion" loading="lazy" src="/post/puppet-automation-insights/conclusion.png"></p>
<p>If you&rsquo;re deep in the Ansible ecosystem, it&rsquo;s still valuable to understand what benefits other tools can bring. Staying open-minded about different approaches helps broaden your technical perspective, can lead to fresh ideas for solving problems, and ultimately makes you a more versatile infrastructure developer. Even if you don&rsquo;t switch tools, exploring alternative design philosophies can inspire creative solutions and innovations in your own workflows and automation strategies.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
