<?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>Ansible on TechBlog about OpenShift/Ansible/Satellite and much more</title><link>https://blog.stderr.at/categories/ansible/</link><description>TechBlog about OpenShift/Ansible/Satellite and much more</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Toni Schmidbauer &amp; Thomas Jungbauer</copyright><lastBuildDate>Tue, 09 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.stderr.at/categories/ansible/index.xml" rel="self" type="application/rss+xml"/><item><title>Creating an auditd rule to monitor Ansible Automation Platform static secrets</title><link>https://blog.stderr.at/ansible/2026/06/creating-an-auditd-rule-to-monitor-ansible-automation-platform-static-secrets/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2026/06/creating-an-auditd-rule-to-monitor-ansible-automation-platform-static-secrets/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible Automation Platform (AAP) comes with an elaborate credential
management solution. All credentials within AAP are stored encrypted
in a PostgreSQL database. But the keys used for encrypting database
fields are currently stored on the machine(s) running AAP. In this
blog post, we show we created an auditd rule to get at least some kind
of monitoring if those keys are accessed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ansible_automation_platform_local_secret_key"&gt;Ansible Automation Platform local SECRET_KEY&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;AAP comes with a
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.7/secure-con_controller_how_credentials_work"&gt;credential
management solution&lt;/a&gt;. Credentials are used to&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;access remote machines&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;provide configuration data for automation, like API keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;connect AAP to source code repositories and inventories&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;and much more. You can even integrate external credential management
solutions like &lt;a href="https://www.hashicorp.com/products/vault"&gt;HashiCorp
Vault&lt;/a&gt; or &lt;a href="https://openbao.org/"&gt;OpenBao&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All credentials stored within AAP are saved as encrypted database
fields in a PostgreSQL database. There is a main key and AAP creates
for every encrypted database field a derived AES-256
encryption key. For more details see the
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.7/secure-con_controller_secret_handling#controller-secret-handling-operational-use"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But there is one caveat: the main secret key is stored on the local
file system. There is currently no supported option to integrate
something like a HSM (High Security Module) for storing the main
secret securely.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To mitigate this issue we deployed a Linux audit rule that monitors
read/write and attribute changes to the main secret.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuring_linux_auditd"&gt;Configuring Linux auditd&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We use the containerized version of AAP 2.6. In the containerized
version of AAP those secrets are stored as &lt;em&gt;podman&lt;/em&gt; secrets as the
user running the AAP containers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To list AAP podman secrets execute&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;$ podman secret list
ID NAME DRIVER CREATED UPDATED
02a2eb397dc5cedefde6ae411 hub_resource_server file 2 hours ago 2 hours ago
0dd84dc7b808917c4efb6ea9f controller_channels file 2 hours ago 2 hours ago
85fb73ff57729250c019976ed hub_database_fields file 2 hours ago 2 hours ago
c482ff7c07f908e443d327293 controller_postgres file 2 hours ago 2 hours ago
e6750121a8997cce8185072bf gateway_admin_password file 2 hours ago 2 hours ago
2731b2d03806f88f35809de66 eda_admin_password file 2 hours ago 2 hours ago
31ca44577b4c5b61dd3f0a0b6 eda_secret_key file 2 hours ago 2 hours ago
856473626bad9ff37b1b979d4 eda_resource_server file 2 hours ago 2 hours ago
b20ec0897fc2b6533544c4441 gateway_redis_url file 2 hours ago 2 hours ago
1dfdd812847ed4c1c01b194e2 gateway_db_password file 2 hours ago 2 hours ago
32d04575881dd781be192ef49 eda_db_password file 2 hours ago 2 hours ago
5102f69350717c15177b80821 hub_secret_key file 2 hours ago 2 hours ago
d1faf92922182604cfa746318 controller_resource_server file 2 hours ago 2 hours ago
d29f0d8c2fc709655dd918f2c postgresql_admin_password file 2 hours ago 2 hours ago
1b3ee24261bdff50d2c70e3f0 gateway_secret_key file 2 hours ago 2 hours ago
ae6c9e78e91dea180d22db6ee hub_settings file 2 hours ago 2 hours ago
b348d89bfb7d3b18f33a67d07 controller_secret_key file 2 hours ago 2 hours ago &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;the controller secret key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If we want to know where those secrets are stored we can execute&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;$ podman secret inspect controller_secret_key
[
{
&amp;#34;ID&amp;#34;: &amp;#34;b348d89bfb7d3b18f33a67d07&amp;#34;,
&amp;#34;CreatedAt&amp;#34;: &amp;#34;2026-06-09T10:57:28.278138715Z&amp;#34;,
&amp;#34;UpdatedAt&amp;#34;: &amp;#34;2026-06-09T10:57:28.278138715Z&amp;#34;,
&amp;#34;Spec&amp;#34;: {
&amp;#34;Name&amp;#34;: &amp;#34;controller_secret_key&amp;#34;,
&amp;#34;Driver&amp;#34;: {
&amp;#34;Name&amp;#34;: &amp;#34;file&amp;#34;,
&amp;#34;Options&amp;#34;: {
&amp;#34;path&amp;#34;: &amp;#34;/home/admin/.local/share/containers/storage/secrets/filedriver&amp;#34;
}
},
&amp;#34;Labels&amp;#34;: {}
}
}
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s see what’s in the filedriver directory:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;$ ls /home/admin/.local/share/containers/storage/secrets/filedriver
secretsdata.json secretsdata.lock&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;we are mainly interested in the &lt;em&gt;secretsdata.json&lt;/em&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So the &lt;em&gt;secretsdata.json&lt;/em&gt; file stores all podman secrets that are
using the &lt;em&gt;filedriver&lt;/em&gt;. For more information see the section
&lt;a href="https://docs.podman.io/en/latest/markdown/podman-secret-create.1.html#secret-drivers"&gt;Secret
Drivers&lt;/a&gt; in the podman-secrets-create(1) manpage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s create a Linux auditd rule to monitor access to this file. As we
are using Red Hat Enterprise Linux 10 a more detailed introduction to
the Linux audit system is available
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/risk_reduction_and_recovery_operations/auditing-the-system"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We created the file &lt;em&gt;/etc/audit/rules.d/aap_secrets.rules&lt;/em&gt; with the following content:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;-w /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json -p rwa -k aap_secrets &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;-p rwa means monitor read/write/attribute changes, -k tells the audit system log events with an additional key &amp;#34;aap_secrets&amp;#34;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To activate the rule we need to load the audit rule via:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;# auditctl -R /etc/audit/rules.d/aap_secrets.rules&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can list loaded audit rules via&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;# auditctl -l
-w /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json -p rwa -k aap_secrets&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a test we can cat the secrets file and check if auditd logged an event:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;# cat /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json
...
# grep aap_secrets /var/log/audit/audit.log
type=SYSCALL msg=audit(1781009319.993:204): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7ffc5276b65c a2=0 a3=0 items=1 ppid=6186 pid=6695 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts2 ses=3 comm=&amp;#34;cat&amp;#34; exe=&amp;#34;/usr/bin/cat&amp;#34; subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=&amp;#34;aap_secrets&amp;#34;ARCH=x86_64 SYSCALL=openat AUID=&amp;#34;admin&amp;#34; UID=&amp;#34;root&amp;#34; GID=&amp;#34;root&amp;#34; EUID=&amp;#34;root&amp;#34; SUID=&amp;#34;root&amp;#34; FSUID=&amp;#34;root&amp;#34; EGID=&amp;#34;root&amp;#34; SGID=&amp;#34;root&amp;#34; FSGID=&amp;#34;root&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We can see that the &lt;em&gt;cat&lt;/em&gt; command (comm=cat) was used by the root user to display the file contents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this blog post we demonstrated how to configure the Linux audit
system to log events if the AAP database secret key is accessed. The
audit subsystem can also monitor system calls, for a detailed
introduction see
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/risk_reduction_and_recovery_operations/auditing-the-system#audit-system-architecture"&gt;Audit system architecture&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Using a local Renovate bot to manage Ansible collection dependencies</title><link>https://blog.stderr.at/ansible/2026/05/using-a-local-renovate-bot-to-manage-ansible-collection-dependencies/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2026/05/using-a-local-renovate-bot-to-manage-ansible-collection-dependencies/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;We wanted to use &lt;a href="https://github.com/renovatebot/renovate"&gt;renovate&lt;/a&gt; bot
to automatically update collection dependencies for one of our
Ansible playbook repositories. This blog post describes the basics of
renovate, how we configured it and how we actually run renovate
locally.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_understanding_renovate"&gt;Understanding renovate&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As this was our first try of using renovate for dependency updates, we
struggled to understand how renovate works and how it is configured.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Renovate is a bot that is able to scan multiple repositories for
dependencies and create pull/merge requests with updated dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Configuration is split between a configuration for the bot and a local
one for each repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So you basically have&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;One or more global configuration files that configure the bot
(&lt;em&gt;config.js&lt;/em&gt;) written in JavaScript or TypeScript&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One configuration file per repository (renovate.json) written in
JSON that contains local configuration options just for this
repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Renovate is very flexible on how to structure the configuration. For
more information see
&lt;a href="https://docs.renovatebot.com/config-overview/"&gt;config-overview&lt;/a&gt; in the
docs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Renovate also supports multiple platforms, like GitHub, GitLab or
Forgejo. For a complete list see
&lt;a href="https://docs.renovatebot.com/modules/platform/"&gt;renovate platforms&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The idea is that you run the bot once, it scans all configured
repositories and creates pull requests in every repository with
dependency updates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If your projects are hosted on GitHub and you would like to use GitHub
Actions for running renovate the straight forward solution is to
follow &lt;a href="https://github.com/marketplace/renovate"&gt;Mend Renovate on GitHub
Marketplace&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But we wanted more control and tried to&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;run renovate on a local machine. This should be migrated to a Job Template or
Kubernetes CronJob later (might be worth another post)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;use a private Automation Hub for hosting dependencies. So renovate
should query collections on our Private Automation Hub for
dependency updates&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;credentials should be made available via environment variables to the bot&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuring_and_running_renovate"&gt;Configuring and running renovate&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a first test we created a local bot configuration file &lt;em&gt;config.js&lt;/em&gt;
in our home directory. The GIT repository we used for testing is
&lt;a href="https://github.com/tosmi-ansible/aap-setup" class="bare"&gt;https://github.com/tosmi-ansible/aap-setup&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to be aware of is that the renovate bot by default
&lt;strong&gt;always&lt;/strong&gt; searches the repository configuration file (&lt;em&gt;renovate.json&lt;/em&gt;)
in the &lt;strong&gt;upstream&lt;/strong&gt; repository. Changes to local files are ignored.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One way to avoid this is using &lt;em&gt;--platform=local&lt;/em&gt;. This actually uses
the local &lt;em&gt;renovate.json&lt;/em&gt; file, but has the caveat that not all
renovate functionality is available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But in our case this was good enough as we just wanted to test AAP Hub
authentication.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It is also helpful to run renovate with debugging enabled, this dumps
the configuration currently used to STDOUT.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example we had the following &lt;em&gt;renovate.json&lt;/em&gt; config for testing:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [
&amp;#34;config:best-practices&amp;#34;
],
&amp;#34;timezone&amp;#34;: &amp;#34;Europe/London&amp;#34;,
&amp;#34;schedule&amp;#34;: [
&amp;#34;at any time&amp;#34;
],
&amp;#34;packageRules&amp;#34;: [
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;galaxy-collection&amp;#34;]
},
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;github-tags&amp;#34;],
&amp;#34;enabled&amp;#34;: &amp;#34;false&amp;#34;
}
],
&amp;#34;hostRules&amp;#34;: [
{
&amp;#34;matchHost&amp;#34;: &amp;#34;aap.apps.hub.aws.tntinfra.net&amp;#34;,
&amp;#34;token&amp;#34;: &amp;#34;Token &amp;lt;the token&amp;gt;&amp;#34;,
&amp;#34;authType&amp;#34;: &amp;#34;Token-Only&amp;#34;
}
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But running renovate with &lt;em&gt;LOG_LEVEL=debug&lt;/em&gt; set in the environment
revealed the following settings:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;DEBUG: Repository config (repository=tosmi-ansible/aap-setup)
&amp;#34;fileName&amp;#34;: &amp;#34;renovate.json&amp;#34;,
&amp;#34;config&amp;#34;: {
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [&amp;#34;config:recommended&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration above is the configuration available in the remote
GIT repository and not our local configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Renovate does not use a local &lt;em&gt;renovate.json&lt;/em&gt; repository config
by default. It always pulls the configuration from the remote
repository except when you use _—​platform=local.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Furthermore renovate heavily relies on local caching. We wanted to test
if renovate is using our hub token for connecting to the Hub, but we
could not see any log entry in the AAP Hub container.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Renovate creates its cache files in a directory called &lt;em&gt;baseDir&lt;/em&gt;. You
can specify the location (and later wipe the cache) with the
&lt;em&gt;--base-dir&lt;/em&gt; option.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As we are using a self signed certificate in our AAP Hub test
installation we had to specify &lt;em&gt;NODE_TLS_REJECT_UNAUTHORIZED=0&lt;/em&gt; to
disable TLS verification.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To summarize our test configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We used the following &lt;em&gt;config.js&lt;/em&gt; global renovate configuration file:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-javascript hljs" data-lang="javascript"&gt;module.exports = {
token: &amp;#39;&amp;lt;github token&amp;gt;&amp;#39;,
platform: &amp;#39;github&amp;#39;,
onboardingConfig: {
extends: [&amp;#39;config:recommended&amp;#39;],
},
//repositories: [&amp;#39;tosmi-ansible/aap-setup&amp;#39;], &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Repository listing is not allowed when using &lt;em&gt;--platform=local&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the repository where we want to test automatic dependency updates
we used the following &lt;em&gt;renovate.json&lt;/em&gt; configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [
&amp;#34;config:best-practices&amp;#34;
],
&amp;#34;timezone&amp;#34;: &amp;#34;Europe/London&amp;#34;,
&amp;#34;schedule&amp;#34;: [
&amp;#34;at any time&amp;#34;
],
&amp;#34;packageRules&amp;#34;: [
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;galaxy-collection&amp;#34;]
},
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;github-tags&amp;#34;],
&amp;#34;enabled&amp;#34;: &amp;#34;false&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
}
],
&amp;#34;hostRules&amp;#34;: [
{
&amp;#34;matchHost&amp;#34;: &amp;#34;aap.apps.hub.aws.tntinfra.net&amp;#34;,
&amp;#34;token&amp;#34;: &amp;#34;Token &amp;lt;the token&amp;gt;&amp;#34;,
&amp;#34;authType&amp;#34;: &amp;#34;Token-Only&amp;#34;
}
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We disabled GitHub tag lookup explicitly as renovate would find some of our dependencies on GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is our &lt;em&gt;collections/requirements.yaml&lt;/em&gt; file with a test dependency:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;roles:
collections:
- name: kubernetes.core
version: 6.3.0
source: https://aap.apps.hub.aws.tntinfra.net/api/galaxy/content/rh-certified/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On our AAP Private Automation Hub we had &lt;em&gt;kubernetes.core&lt;/em&gt; version
6.4.0 available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So to run and test renovate locally we used the following command line:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;NODE_TLS_REJECT_UNAUTHORIZED=0 LOG_LEVEL=debug RENOVATE_CONFIG_FILE=~/config.js renovate --platform=github --repository-cache=disabled --base-dir=/tmp/cache&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_secret_management_and_configuration_cleanup"&gt;Secret management and configuration cleanup&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The next step is to remove the hard-coded tokens from our
configuration. We wanted to use environment variables, as this allows
us to run renovate as:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A Kubernetes &lt;em&gt;CronJob&lt;/em&gt; with a &lt;em&gt;Pod&lt;/em&gt;. We could inject the token via a &lt;em&gt;Secret&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As a job template within AAP with tokens stored as AAP credentials or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A normal Unix cron job with environment variables set&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As the global renovate configuration &lt;em&gt;config.js&lt;/em&gt; is written in
JavaScript we could leverage &lt;em&gt;process.env&lt;/em&gt; to retrieve required tokens
from environment variables.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here is our modified &lt;em&gt;config.js&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-javascript hljs" data-lang="javascript"&gt;module.exports = {
platform: &amp;#39;github&amp;#39;,
onboardingConfig: {
extends: [&amp;#39;config:recommended&amp;#39;],
},
// repositories: [&amp;#39;tosmi-ansible/aap-setup&amp;#39;], &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
secrets: {
AAP_HUB_TOKEN: process.env.AAP_HUB_TOKEN, &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
},
hostRules: [
{
matchHost: &amp;#34;aap.apps.hub.aws.tntinfra.net&amp;#34;,
token: &amp;#34;{{ secrets.AAP_HUB_TOKEN }}&amp;#34;, &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
authType: &amp;#34;Token&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
},
],
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We disable repository listing for &lt;em&gt;--platform=local&lt;/em&gt;. Otherwise renovate will raise an error.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We define a new secret AAP_HUB_TOKEN and read the value from the environment variable AAP_HUB_TOKEN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We use the secret as our token for authenticating to AAP Hub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;authType&lt;/em&gt; setting is a little bit special, see the following section&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For GitHub authentication renovate expects a GitHub token in the
environment variable
&lt;a href="https://docs.renovatebot.com/self-hosted-configuration/#token"&gt;RENOVATE_TOKEN&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A few words about the &lt;em&gt;authType&lt;/em&gt; setting. AAP Hub requires the
following authorization header in the HTTP request for authentication:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;Authorization: Token &amp;lt;the token&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With renovate we have two options to achieve this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Either set &lt;em&gt;authType&lt;/em&gt; to &amp;#34;Token-Only&amp;#34; and specify the token as
&amp;#34;Token &amp;lt;the token&amp;gt;&amp;#34;. So with token-only renovate takes the token
string verbatim&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or set the &lt;em&gt;authType&lt;/em&gt; to the string we would like to prepend to the
token as we did&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We had to dig into the
&lt;a href="https://github.com/renovatebot/renovate/blob/6c6a6b793d6f339a18222e6aec851df77ceaa8f4/lib/util/http/auth.ts#L33"&gt;source code&lt;/a&gt;
of renovate to understand how it functions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;And here is the &lt;em&gt;renovate.json&lt;/em&gt; repository configuration that we used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [
&amp;#34;config:best-practices&amp;#34;
],
&amp;#34;timezone&amp;#34;: &amp;#34;Europe/London&amp;#34;,
&amp;#34;schedule&amp;#34;: [
&amp;#34;at any time&amp;#34;
],
&amp;#34;packageRules&amp;#34;: [
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;galaxy-collection&amp;#34;]
},
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;github-tags&amp;#34;],
&amp;#34;enabled&amp;#34;: &amp;#34;false&amp;#34;
}
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You could also move the &lt;em&gt;hostRules&lt;/em&gt; to the repository local
&lt;em&gt;renovate.json&lt;/em&gt; file and still use &lt;em&gt;secrets.AAP_HUB_TOKEN&lt;/em&gt;, but as
this is going to be our global configuration for Ansible code base
covering multiple repositories our current idea is to store the
authentication in the global configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To set the required environment variables containing our secrets we
execute&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;$ export AAP_HUB_TOKEN=$(pass show other/ansible/aws-aap-tokens | awk &amp;#39;/hub/ {print $2}&amp;#39;) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
$ export RENOVATE_TOKEN=$(pass show other/github/tosmi-rw-token | awk &amp;#39;/token/ {print $2}&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We use the &lt;a href="https://www.passwordstore.org/"&gt;pass utility&lt;/a&gt; for secret management.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_integration_with_github"&gt;Integration with GitHub&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So the last step is to store everything (the global and local
configuration) on GitHub.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For the global repository we created the
&lt;a href="https://github.com/tosmi-ansible/renovate-config"&gt;tosmi-ansible/renovate-config&lt;/a&gt;
repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here is our final &lt;em&gt;config.js&lt;/em&gt; global configuration file in this repository:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-javascript hljs" data-lang="javascript"&gt;module.exports = {
platform: &amp;#39;github&amp;#39;,
gitAuthor: &amp;#34;Renovate Bot &amp;lt;renovate-bot@stderr.at&amp;gt;&amp;#34;,
dryRun: null,
onboardingConfig: {
extends: [&amp;#39;config:recommended&amp;#39;],
},
repositories: [&amp;#39;tosmi-ansible/aap-setup&amp;#39;], &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
secrets: {
AAP_HUB_TOKEN: process.env.AAP_HUB_TOKEN,
},
hostRules: [
{
matchHost: &amp;#34;aap.apps.hub.aws.tntinfra.net&amp;#34;,
token: &amp;#34;{{ secrets.AAP_HUB_TOKEN }}&amp;#34;,
authType: &amp;#34;Token&amp;#34;
},
],
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;This time we tell renovate which repositories it should scan.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The default configuration &lt;em&gt;default.json&lt;/em&gt;, also stored in the global
repository:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [
&amp;#34;config:best-practices&amp;#34;
],
&amp;#34;timezone&amp;#34;: &amp;#34;Europe/London&amp;#34;,
&amp;#34;schedule&amp;#34;: [
&amp;#34;at any time&amp;#34;
],
&amp;#34;enabledManagers&amp;#34;: [&amp;#34;ansible&amp;#34;, &amp;#34;ansible-galaxy&amp;#34;], &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
&amp;#34;packageRules&amp;#34;: [
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;galaxy-collection&amp;#34;]
},
{
&amp;#34;matchDatasources&amp;#34;: [&amp;#34;github-tags&amp;#34;],
&amp;#34;enabled&amp;#34;: &amp;#34;false&amp;#34;
}
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We only enable ansible and ansible-galaxy managers. Otherwise renovate would search for various other sources for dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://github.com/tosmi-ansible/aap-setup"&gt;tosmi-ansible/aap-setup&lt;/a&gt; contains our local configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://docs.renovatebot.com/renovate-schema.json&amp;#34;,
&amp;#34;extends&amp;#34;: [
&amp;#34;tosmi-ansible/renovate-config&amp;#34;,
&amp;#34;config:best-practices&amp;#34;
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This only contains a reference to our global configuration and we
would like to use the
&lt;a href="https://docs.renovatebot.com/presets-config/#configbest-practices"&gt;best-practices&lt;/a&gt;
preset.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can now run renovate from the &lt;em&gt;renovate-config&lt;/em&gt; repository and it
will scan the configured repository and create a pull request with updated
dependencies if required.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;$ git clone git@github.com:tosmi-ansible/renovate-config.git
$ cd renovate-config
$ NODE_TLS_REJECT_UNAUTHORIZED=0 renovate --platform=github --base-dir=/tmp/renovate-cache &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We still need to set &lt;em&gt;NODE_TLS_REJECT_UNAUTHORIZED&lt;/em&gt; to 0, because our AAP Hub instance uses a private certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One minor issue that we discovered is that if a pull request for an
update already existed (even a closed one) Renovate would not open a
new one. If you run renovate with &lt;em&gt;LOG_LEVEL=debug&lt;/em&gt; you will see the
following message:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;DEBUG: Closed PR #19 already exists. Skipping branch. (repository=tosmi-ansible/aap-setup, branch=renovate/kubernetes.core-6.x)
&amp;#34;prTitle&amp;#34;: &amp;#34;Update dependency kubernetes.core to v6.4.0&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To convince Renovate to create a new pull request just rename the
closed pull request on GitHub. We just added the prefix
&amp;#34;RENAMED&amp;#34;. After renaming the pull request, renovate will happily
create a new one.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_tips_and_tricks"&gt;Tips and tricks&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This section list various things we learning while running renovate locally&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_renovate_does_not_honor_new_dependencies_in_requirements_yaml"&gt;Renovate does not honor new dependencies in &lt;em&gt;requirements.yaml&lt;/em&gt;&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We had an issues with new dependencies in &lt;em&gt;requirements.yaml&lt;/em&gt;. Wiping
the renovate cache (&lt;em&gt;/tmp/renovate_cache&lt;/em&gt;) in our case fixed this and
renovate created new pull requests for the added dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_renovate_does_not_open_new_pr_pull_requests_for_updated_dependencies"&gt;Renovate does not open new PR (pull requests) for updated dependencies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We double checked AAP and our renovate configuration and there was
clearly a newer version of a collection but renovate just did not open
a new PR.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Running renovate with &lt;em&gt;LOG_LEVEL=debug&lt;/em&gt; revealed the following message
(we tried to update the redhat.openshift_virtualization collection):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt; {
&amp;#34;branchName&amp;#34;: &amp;#34;renovate/redhat.openshift_virtualization-2.x&amp;#34;,
&amp;#34;prNo&amp;#34;: null,
&amp;#34;prTitle&amp;#34;: &amp;#34;Update dependency redhat.openshift_virtualization to v2.2.4&amp;#34;,
&amp;#34;result&amp;#34;: &amp;#34;branch-limit-reached&amp;#34;, &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;renovate does not open a new branch because of &amp;#34;branch-limit-reached&amp;#34;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We check our upstream repository on GitHub and there was just the main
branch, hm…​&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Looking further up in the log we found:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;DEBUG: prHourlyLimit of the upgrades present in this branch (repository=tosmi-ansible/aap-setup, branch=renovate/infra.aap_utilities-3.x)
&amp;#34;limits&amp;#34;: [{&amp;#34;depName&amp;#34;: &amp;#34;infra.aap_utilities&amp;#34;, &amp;#34;prHourlyLimit&amp;#34;: 2}]
DEBUG: Calculated lowest prHourlyLimit among the upgrades present in this branch is 2. (repository=tosmi-ansible/aap-setup, branch=renovate/infra.aap_utilities-3.x)
DEBUG: Hourly PRs limit reached (repository=tosmi-ansible/aap-setup, branch=renovate/infra.aap_utilities-3.x) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
&amp;#34;hourlyPrCount&amp;#34;: 2,
&amp;#34;hourlyPrLimit&amp;#34;: 2
DEBUG: Reached branch limit - skipping branch creation (repository=tosmi-ansible/aap-setup, branch=renovate/infra.aap_utilities-3.x)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;There is a hourly branch limit of two in place&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As we just ran renovate to update another dependency, we seem to be
hitting this hourly limit. The
&lt;a href="https://docs.renovatebot.com/configuration-options/#prhourlylimit"&gt;docs&lt;/a&gt;
mention a default of 2.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The solution was to run renovate with the option &lt;em&gt;--pr-hourly-limit=0&lt;/em&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-terminal hljs" data-lang="terminal"&gt;NODE_TLS_REJECT_UNAUTHORIZED=0 renovate --platform=github --base-dir=/tmp/renovate-cache --pr-hourly-limit=0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Onboarding to Ansible Automation Platform with Configuration as Code</title><link>https://blog.stderr.at/ansible/2026/03/onboarding-to-ansible-automation-platform-with-configuration-as-code/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2026/03/onboarding-to-ansible-automation-platform-with-configuration-as-code/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;We had the honor of presenting at the 2nd Ansible Anwendertreffen in
Austria. The topic was the onboarding of application teams to the
Ansible Automation Platform.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We created an extensive demo that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Onboards a new tenant into an AAP organization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each tenant gets a configuration as code repository, &lt;a href="https://github.com/tosmi-ansible/template-org-config" target="_blank" rel="noopener"&gt;cloned from a template&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A push event to the config-as-code repository triggers an update of AAP objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides an &lt;a href="https://github.com/tosmi-ansible/template-example-project" target="_blank" rel="noopener"&gt;example repository&lt;/a&gt; for each tenant to get started.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The example repository provides a &lt;a href="https://github.com/tosmi-ansible/template-org-config/blob/main/playbooks/devenv.yaml" target="_blank" rel="noopener"&gt;webhook&lt;/a&gt; that creates an OpenShift Virtualization VM for testing code changes when a feature branch is created.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Contains a &lt;a href="https://github.com/tosmi-ansible/template-org-config/blob/main/playbooks/cac-diff.yaml" target="_blank" rel="noopener"&gt;playbook&lt;/a&gt; to display objects that are not currently managed by the tenant’s configuration-as-code repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The source code for the demo is available on &lt;a href="https://github.com/tosmi-ansible/aap-onboarding" target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://github.com/tosmi-ansible/aap-onboarding/blob/main/README.md"&gt;README&lt;/a&gt; contains more details about the implementation, and the slide deck is also &lt;a href="https://github.com/tosmi-ansible/aap-onboarding/blob/main/docs/Ansible%20Anwender%20Treffen%20202602%20-%20Slides.pdf" target="_blank" rel="noopener"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Ansible Style Guide</title><link>https://blog.stderr.at/ansible/2021/11/ansible-style-guide/</link><pubDate>Tue, 30 Nov 2021 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2021/11/ansible-style-guide/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;You should always follow the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html" target="_blank" rel="noopener"&gt;Best Practices&lt;/a&gt; and &lt;a href="https://ansible-lint.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Ansible Lint&lt;/a&gt; rules defined by the Ansible documentation when developing playbooks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Although very basic, the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html" target="_blank" rel="noopener"&gt;Best Practices&lt;/a&gt; document gives a few guidelines to be able to carry out well-structured playbooks and roles, it contains recommendations that evolve with the project, so it is recommended to review it regularly. It is advisable to review the organization of content in Ansible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://ansible-lint.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Ansible Lint&lt;/a&gt; documentation shows us through this tool the syntax rules that will be checked in the testing of roles and playbooks, the rules that will be checked are indicated in this document in their respective section.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This style guide is meant as a base of playbook development. It is not the ultimate truth. Always verify and apply appropriate company rules. There are many additional references available, such as: &lt;a href="https://github.com/whitecloud/ansible-styleguide" class="bare"&gt;https://github.com/whitecloud/ansible-styleguide&lt;/a&gt; or &lt;a href="https://github.com/redhat-cop/automation-good-practices" class="bare"&gt;https://github.com/redhat-cop/automation-good-practices&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_general_recommendations"&gt;General Recommendations&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Treat your Ansible content like code: Any playbook, role, inventory or collection should be versionized using Git or a similar tool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Iterate: start with basic playbook and refactor later&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use multiple Git repositories: on per role/collection, separate inventory from other repositories&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use human-meaningful names in inventory files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;group hosts in inventory files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;use a consistent &lt;a href="#_directory_structure"&gt;Directory Structure&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_optimize_playbook_execution"&gt;Optimize Playbook Execution&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Disable facts gathering if it is not required.&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;Try not to use: &lt;code&gt;ansible_facts[‘hostname’]&lt;/code&gt; (or ‘nodename’)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try to use &lt;code&gt;inventory_hostname&lt;/code&gt; and &lt;code&gt;inventory_hostname_short&lt;/code&gt; instead&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It is possible to selectively gather facts (&lt;code&gt;gather_subnet&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider caching facts&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Increase Parallelism&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;Ansible runs 1st task on every host, then 2nd task on every host …&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use “forks” (default is 5) to control how many connections can be active (load will increase, try first with conservative values)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While testing forks analize &lt;a href="#_enable_cpu_and_memory_profiling"&gt;Enable CPU and Memory Profiling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you use the module &amp;#34;copy&amp;#34; for large files: do not use “copy” module. Use “synchronize” module instead (based on rsync)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use lists when ever a modules support it (i.e. yum)
Instead of&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;tasks:
- name: Ensure the packages are installed
yum:
name: &amp;#34;{{ item }}&amp;#34;
state: present
loop:
- httpd
- mod_ssl
- httpd-tools
- mariadb-server
- mariadb
- php
- php-mysqlnd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;use&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;tasks:
- name: Ensure the packages are installed
yum:
state: present
name:
- httpd
- mod_ssl
- httpd-tools
- mariadb-server
- mariadb
- php
- php-mysqlnd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="5"&gt;
&lt;li&gt;
&lt;p&gt;Optimize SSH Connections&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;in &lt;em&gt;ansible.cfg&lt;/em&gt; set&lt;/p&gt;
&lt;div class="olist lowerroman"&gt;
&lt;ol class="lowerroman" type="i"&gt;
&lt;li&gt;
&lt;p&gt;ControlMaster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ControlPersist&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PreferredAuthentications&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabling &lt;strong&gt;Pipelining&lt;/strong&gt;&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;Not enabled by default&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;reduces number of SSH operations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;requires to disable &lt;strong&gt;requiertty&lt;/strong&gt; in sudo options&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_name_tasks_and_plays"&gt;Name Tasks and Plays&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Every task or play should be explicitly named in a way that the purpose is easily understood and can be referred to if the playbook has to be restarted from a certain task.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example the following is hard to read and debug:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;PLAY [localhost]
********************************
TASK [include_vars]
********************************
ok: [localhost]
TASK [yum]
********************************
ok: [localhost]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While with naming it is easier to follow what is happening:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;PLAY [Create a new virtual machine]
********************************
TASK [Include vmware-credentials]
********************************
ok: [localhost]
TASK [Install required packages with yum]
********************************
ok: [localhost]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
# set another variable
- set_fact:
my_second_var: &amp;#34;{{ my_var }}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: set another variable
set_fact:
my_second_var: &amp;#34;{{ my_var }}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Better understanding what is currently happening in a play and better possibility to debug.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_variables_in_task_names"&gt;Variables in Task Names&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Include as much information as necessary to explain the purpose of a task. Make usage of variables inside a task name to create dynamic output messages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#bad
- name: &amp;#39;Change status&amp;#39;
service:
enabled: true
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;{{ state }}&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#good
- name: &amp;#39;Change status of httpd to {{ state }}&amp;#39;
service:
enabled: true
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;{{ state }}&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
This will help to easily understand log outputs of playbooks.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_omitting_unnecessary_information"&gt;Omitting Unnecessary Information&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While name tasks in a playbook, &lt;strong&gt;do not&lt;/strong&gt; include the name of the role which is currently executed, since Ansible will do this automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Avoiding the same output twice on the console will prevent confusions.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_names"&gt;Names&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All the newly created Ansible roles should follow the name convention using dashes if necessary:
&lt;code&gt;[company]-[action]-[function/technology]&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
lvm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
mycompany-setup-lvm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
If using roles from Ansible Galaxy, it will keep consistency about which roles are created internally.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_use_modules_instead_of_command_or_shell"&gt;Use Modules instead of command or shell&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before using the &lt;code&gt;command&lt;/code&gt; or &lt;code&gt;shell&lt;/code&gt; module, verify if there is already a module available which can avoid the usage of raw shell command.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: install httpd
tasks:
- command: &amp;#34;yum install httpd&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: install packages
tasks:
- name: &amp;#39;install httpd&amp;#39;
yum:
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;present&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_documenting_a_taskplay"&gt;Documenting a Task/Play&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Every playbook, role or task should start with a documentation &lt;em&gt;why&lt;/em&gt; this code has been written and what it does and should include an example usage if applicable. The comment should be followed by &lt;code&gt;---`&lt;/code&gt; with no blank lines around it, to indicate the actual start of the yaml definition&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#bad
- name: &amp;#39;Change httpd status&amp;#39;
service:
enabled: true
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;{{ state }}&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#good
# Example usage: ansible-playbook -e state=started playbook.yml
# This playbook changes the state of the httpd daemon
---
- name: &amp;#39;Change httpd status&amp;#39;
service:
enabled: true
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;{{ state }}&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
This common programmatic practice helps to quickly understand the purpose and usage of a playbook or role.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.document_start" target="_blank" rel="noopener"&gt;document-start rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_end_a_file"&gt;End a File&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Files should be ended with a newline.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
This is common Unix best practice which avoids any prompt misalignment when printing files in a terminal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.new_line_at_end_of_file" target="_blank" rel="noopener"&gt;new-line-at-end-of-file rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_quotes"&gt;Quotes&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Strings should be quoted, while quotes for booleans (e.g. true/false) or integers (i.g. 42) should be avoided.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Do &lt;strong&gt;NOT&lt;/strong&gt; quote:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;hosts: targets (e.g. hosts: databases rather than hosts: ‘databases’)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;include_tasks: and include_roles: target file names&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task and role names&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;registered variables&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;number values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;boolean values&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
It is possible to use single or double quotes. In this document single quotes are used, as it seems to be more common. Double quotes are often seen in European countries, especially German speaking countries. The most important thing is to stick to one style.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: &amp;#39;start robot named my-robot&amp;#39;
service:
name: my-robot
state: started
enabled: true
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;start robot named my-robot&amp;#39;
service:
name: &amp;#39;my-robot&amp;#39;
state: &amp;#39;started&amp;#39;
enabled: true
become: true
# double quotes w/ nested single quotes
- name: &amp;#39;start all robots&amp;#39;
service:
name: &amp;#39;{{ item[&amp;#34;robot_name&amp;#34;] }}&amp;#39;
state: &amp;#39;started&amp;#39;
enabled: true
with_items: &amp;#39;{{ robots }}&amp;#39;
become: true
# double quotes to escape characters
- name &amp;#39;print some text on two lines&amp;#39;
debug:
msg: &amp;#34;This text is on\ntwo lines&amp;#34;
# folded scalar style
- name: &amp;#39;robot infos&amp;#39;
debug:
msg: &amp;gt;
Robot {{ item[&amp;#39;robot_name&amp;#39;] }} is {{ item[&amp;#39;status&amp;#39;] }} and in {{ item[&amp;#39;az&amp;#39;] }}
availability zone with a {{ item[&amp;#39;curiosity_quotient&amp;#39;] }} curiosity quotient.
with_items: robots
# folded scalar when the string has nested quotes already
- name: &amp;#39;print some text&amp;#39;
debug:
msg: &amp;gt;
&amp;#34;I haven&amp;#39;t the slightest idea,&amp;#34; said the Hatter.
# do not quote booleans/numbers
- name: &amp;#39;download google homepage&amp;#39;
get_url:
dest: &amp;#39;/tmp&amp;#39;
timeout: 60
url: &amp;#39;https://google.com&amp;#39;
validate_certs: true
# variables example 1
- name: &amp;#39;set a variable&amp;#39;
set_fact:
my_var: &amp;#39;test&amp;#39;
# variables example 2
- name: &amp;#39;print my_var&amp;#39;
debug:
var: my_var
when: ansible_os_family == &amp;#39;Darwin&amp;#39;
# variables example 3
- name: &amp;#39;set another variable&amp;#39;
set_fact:
my_second_var: &amp;#39;{{ my_var }}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.quoted_strings" target="_blank" rel="noopener"&gt;quoted-strings rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_sudo"&gt;Sudo&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use the new become syntax when designating that a task needs to be run with sudo privileges&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#bad
- name: &amp;#39;template client.json to /etc/sensu/conf.d/&amp;#39;
template:
dest: &amp;#39;/etc/sensu/conf.d/client.json&amp;#39;
src: &amp;#39;client.json.j2&amp;#39;
sudo: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;template client.json to /etc/sensu/conf.d/&amp;#39;
template:
dest: &amp;#39;/etc/sensu/conf.d/client.json&amp;#39;
src: &amp;#39;client.json.j2&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Using sudo was deprecated at &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/become.html"&gt;Ansible version 1.9.1&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Ansible-lint &lt;a href="https://docs.ansible.com/ansible-lint/rules/default_rules.html" target="_blank" rel="noopener"&gt;E103 rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_hosts_declarations"&gt;Hosts Declarations&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Host sections be defined in such a way that it follows this general order:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;host declaration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;host options in alphabetical order&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pre_tasks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;roles&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tasks&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# example
- hosts: &amp;#39;webservers&amp;#39;
remote_user: &amp;#39;centos&amp;#39;
vars:
tomcat_state: &amp;#39;started&amp;#39;
pre_tasks:
- name: &amp;#39;set the timezone to America/Boise&amp;#39;
lineinfile:
dest: &amp;#39;/etc/environment&amp;#39;
line: &amp;#39;TZ=America/Boise&amp;#39;
state: &amp;#39;present&amp;#39;
become: true
roles:
- { role: &amp;#39;tomcat&amp;#39;, tags: &amp;#39;tomcat&amp;#39; }
tasks:
- name: &amp;#39;start the tomcat service&amp;#39;
service:
name: &amp;#39;tomcat&amp;#39;
state: &amp;#39;{{ tomcat_state }}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
A global definition about the order of these items, will create an easy and consistent human readable code.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_tasks_declarations"&gt;Tasks Declarations&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A task should be defined in such a way that it follows this general order:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;task name&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tags&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task map declaration (e.g. service:)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task parameters in alphabetical order (remember to always use multi-line map syntax)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;loop operators (e.g. with_items)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task options in alphabetical order (e.g. become, ignore_errors, register)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# example
- name: &amp;#39;create some ec2 instances&amp;#39;
tags: &amp;#39;ec2&amp;#39;
ec2:
assign_public_ip: true
image: &amp;#39;ami-c7d092f7&amp;#39;
instance_tags:
Name: &amp;#39;{{ item }}&amp;#39;
key_name: &amp;#39;my_key&amp;#39;
with_items: &amp;#39;{{ instance_names }}&amp;#39;
ignore_errors: true
register: ec2_output
when: ansible_os_family == &amp;#39;Darwin&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
A global definition about the order of these items, will create an easy and consistent human readable code.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_include_declaration"&gt;Include Declaration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For include statements, make sure to quote filenames and only use blank lines between include statements if they are multi-line (e.g. they have tags).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- include: other_file.yml
- include: &amp;#39;second_file.yml&amp;#39;
- include: third_file.yml tags=third&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- include: &amp;#39;other_file.yml&amp;#39;
- include: &amp;#39;second_file.yml&amp;#39;
- include: &amp;#39;third_file.yml&amp;#39;
tags: &amp;#39;third&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Using such syntax, will create an easy and consistent human readable code.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_booleans"&gt;Booleans&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use true/false instead of yes/no (or 1/0).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: &amp;#39;start httpd&amp;#39;
service:
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;restarted&amp;#39;
enabled: 1
become: &amp;#39;yes&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;start httpd&amp;#39;
service:
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;restarted&amp;#39;
enabled: true
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Ansible can read boolean values in many different ways, like: True/False, true/false, yes/no, 1/0. It makes sense to stick to one option, which is then equally used in any playbook or role. It is recommended to use true/false since Java and Javascript are using the same values for boolean values.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.truthy" target="_blank" rel="noopener"&gt;truthy rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Required config: &lt;code&gt;truthy: {allowed-values: [&amp;#34;true&amp;#34;, &amp;#34;false&amp;#34;]}`&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_key_value_pairs"&gt;Key Value Pairs&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Only one space should be used after the colon, when defining key/value pairs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name : &amp;#39;start httpd&amp;#39;
service:
name : &amp;#39;httpd&amp;#39;
state : &amp;#39;restarted&amp;#39;
enabled : true
become : true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;start httpd&amp;#39;
service:
name: &amp;#39;httpd&amp;#39;
state: &amp;#39;restarted&amp;#39;
enabled: true
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
It increases human readability and reduces changeset collisions for version control.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.colons" target="_blank" rel="noopener"&gt;colons rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Required config: &lt;code&gt;colons: {max-spaces-before: 0, max-spaces-after: 1}&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_using_map_syntax"&gt;Using Map Syntax&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Always use the map syntax&lt;/strong&gt; for better readability.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: &amp;#39;create conf.d directory&amp;#39;
file: &amp;#39;path=/etc/httpd/conf.d/ state=directory mode=0755 owner=httpd group=httpd&amp;#39;
become: true
- name: &amp;#39;copy mod_ssl.conf to /etc/httpd/conf.d&amp;#39;
copy: &amp;#39;dest=/etc/httpd/conf.d/ src=mod_ssl.conf&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;create conf.d directory&amp;#39;
file:
group: &amp;#39;httpd&amp;#39;
mode: &amp;#39;0755&amp;#39;
owner: &amp;#39;httpd&amp;#39;
path: &amp;#39;/etc/httpd/conf.d&amp;#39;
state: &amp;#39;directory&amp;#39;
become: true
- name: &amp;#39;copy mod_ssl.conf to /etc/httpd/conf.d&amp;#39;
copy:
dest: &amp;#39;/etc/httpd/conf.d/&amp;#39;
src: &amp;#39;mod_ssl.conf&amp;#39;
become: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
It increases human readability and reduces changeset collisions for version control.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_spacing"&gt;Spacing&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You should have blank lines between two host blocks, between two task blocks, and between host and include blocks. When indenting, you should use 2 spaces to represent sub-maps, and multi-line maps should start with a &lt;code&gt;-&lt;/code&gt;. For a more in-depth example of how spacing (and other things) please take a look at the example below&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Example: ansible-playbook --ask-become-pass --ask-vault-pass style.yml
#
# This is a sample Ansible script to showcase all of our style decisions.
# Pay close attention to things like spacing, where we use quotes, etc.
# The only thing you can ignore is where comments are, except this first comment:
# It&amp;#39;s generally a good idea to include some good information like sample usage
# at the beginning of your file, so that someone can run head on the script
# to see what they should do.
#
# A good rule of thumb on quoting is to quote anything that represents a value
# that does not represent either a primitive type, or something within the
# playbook; e.g. do not quote integers, booleans, variable names, boolean logic
# Variable names still need to be quoted when they are module parameters for
# Ansible to properly resolve them.
# You should also always have single quotes around the outer string, and
# double quotes on the inside.
# If for some reason this is not possible or it would require escaping quotes
# (which you should avoid if you can), use the scalar string operator (shown
# in this playbook).
#
# Directory structure style:
# Your directory structure should match the structure described by the Ansible
# developers: http://docs.ansible.com/ansible/playbooks_best_practices.html
#
# ---
#
# - include: &amp;#39;role_name.yml&amp;#39;
# become: true # only if every task in the role requires super user
#
# The self-named yml file contains all of the actual role tasks.
#
# Header comments are followed by blank line, then --- to signify start of YAML,
# then another blank line, then the script.
---
- hosts: &amp;#39;localhost&amp;#39;
tasks:
- name: &amp;#39;fail if someone tries to run this&amp;#39;
fail:
msg: &amp;#39;this playbook was not meant to actually be ran. just inspect the source!&amp;#39;
- include: &amp;#39;first_include.yml&amp;#39; # quote filenames
- include: &amp;#39;second_include.yml&amp;#39; # no blank line needed between includes without tags
- include: &amp;#39;third_include.yml&amp;#39; # includes with tags should have blank lines between
tags: &amp;#39;third_include&amp;#39;
- include: &amp;#39;fourth_include.yml&amp;#39;
tags: &amp;#39;fourth_include&amp;#39;
- hosts: &amp;#39;tag_environment_samplefruit&amp;#39;
remote_user: &amp;#39;centos&amp;#39; # options in alphabetical order
vars:
sample_str: &amp;#39;dood&amp;#39; # use snake_case for variable names
sample_bool: true # do not quote booleans or integers
sample_int: 42
vars_files:
- &amp;#39;group_vars/secrets.yml&amp;#39;
pre_tasks: # then pre_tasks, roles, tasks
- name: &amp;#39;this runs a command that involves both single and double quotes&amp;#39;
command: &amp;gt;
echo &amp;#34;I can&amp;#39;t even&amp;#34;
args:
chdir: &amp;#39;/tmp&amp;#39;
- name: &amp;#39;this command just involves double quotes&amp;#39;
command: &amp;#39;echo &amp;#34;Hey man&amp;#34;&amp;#39;
roles:
- { role: &amp;#39;sample_role&amp;#39;, tags: &amp;#39;sample_role&amp;#39; } # use this format for role listing
tasks:
- name: &amp;#39;get list of directory permissions in /tmp&amp;#39;
command: &amp;#39;ls -l /tmp&amp;#39;
register: tmp_listing # do not quote variable names when registering
# A task should be defined in the following order:
# name
# tags
# module
# module arguments, alphabetical
# loop operator (e.g. with_items, with_fileglob)
# other options, alphabetical (e.g. become, ignore_errors, when)
- name: &amp;#39;a more complicated task to show where everything goes: touch all items from /tmp&amp;#39;
tags: &amp;#39;debug&amp;#39; # tags go immediately after name
file:
path: &amp;#39;{{ item }}&amp;#39; # use path for single file actions, dest/src for multi file actions
state: &amp;#39;touch&amp;#39; # arguments go in alphabetical order
with_items: tmp_listing.stdout_lines # loop things go immediately after module
# the rest of the task options are in alphabetical order
become: true # try to keep become only on the tasks that need it. If every task in a host uses become, then move it up to the host options
ignore_errors: true
when: ansible_os_family == &amp;#39;Darwin&amp;#39; and tmp_listing.stdout_lines | length &amp;gt; 1
- name: &amp;#39;some modules can have maps in their maps (woah man)&amp;#39;
ec2:
assign_public_ip: true
group: [&amp;#39;wca_ssh&amp;#39;, &amp;#39;wca_tomcat&amp;#39;]
image: &amp;#39;ami-c7d092f7&amp;#39;
instance_tags:
Name: &amp;#39;instance&amp;#39;
service_tomcat: &amp;#39;&amp;#39;
key_name: &amp;#39;ops&amp;#39;
- hosts: &amp;#39;tag_environment_secondfruit&amp;#39;
tasks:
- name: &amp;#39;this task has multiple tags&amp;#39;
tags: [&amp;#39;tagme&amp;#39;, &amp;#39;tagmetoo&amp;#39;]
set_fact:
mr_fact: &amp;#39;w&amp;#39;
- name: &amp;#39;perform an action&amp;#39;
action: ec2_facts
delegate_to: &amp;#39;localhost&amp;#39;
# newline at end of file&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
This produces nice looking code that is easy to read.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Yamllint &lt;a href="https://yamllint.readthedocs.io/en/stable/rules.html#module-yamllint.rules.empty_lines" target="_blank" rel="noopener"&gt;empty-lines rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_variable_names"&gt;Variable Names&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Names of variables should be as expressive as possible. &lt;a href="https://en.wikipedia.org/wiki/Snake_case" target="_blank" rel="noopener"&gt;&lt;strong&gt;snake_case&lt;/strong&gt;&lt;/a&gt; for names will help to make the code human readable. The prefix should contain the name of the role.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: &amp;#39;set some facts&amp;#39;
set_fact:
myBoolean: true
int: 20
MY_STRING: &amp;#39;test&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: &amp;#39;set some facts&amp;#39;
set_fact:
rolename_my_boolean: true
rolename_my_int: 20
rolename_my_string: &amp;#39;test&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Ansible uses snake_case for module names so it makes sense to extend this convention to variable names. Perfixing the variable with the role name, makes it immediately obvious where it is used.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_jinja_variables"&gt;Jinja Variables&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use spaces around Jinja variable names to increase readability.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: set some facts
set_fact:
my_new_var: &amp;#34;{{my_old_var}}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: set some facts
set_fact:
my_new_var: &amp;#34;{{ my_old_var }}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
A proper definition for how to create Jinja variables produces consistent and easily readable code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Ansible-lint &lt;a href="https://ansible-lint.readthedocs.io/en/latest/default_rules.html#variables-should-have-spaces-before-and-after-var-name" target="_blank" rel="noopener"&gt;E206 rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_comparing"&gt;Comparing&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Do not compare to literal True/False.&lt;br/&gt;
Use &lt;code&gt;when: var&lt;/code&gt; rather than &lt;code&gt;when: var == True&lt;/code&gt; (or conversely &lt;code&gt;when: not var&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Do not compare it to empty strings.&lt;br/&gt;
Use &lt;code&gt;when: var&lt;/code&gt; rather than &lt;code&gt;when: var != &amp;#34;&amp;#34;&lt;/code&gt; (or conversely &lt;code&gt;when: not var&lt;/code&gt; rather than &lt;code&gt;when: var == &amp;#34;&amp;#34;&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: validate required variables
fail:
msg: &amp;#34;No value specified for &amp;#39;{{ item }}&amp;#39;&amp;#34;
when: (vars[item] is undefined) or (vars[item] is defined and vars[item] | trim == &amp;#34;&amp;#34;)
with_items: &amp;#34;{{ appd_required_variables }}&amp;#34;
- name: Create an user and add to the global group
include_tasks: user.yml
when:
- username is defined
- username != &amp;#34;&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: Validate required variables
fail:
msg: &amp;#34;No value specified for &amp;#39;{{ item }}&amp;#39;&amp;#34;
when: (vars[item] is undefined) or (vars[item] is defined and not vars[item] | trim == &amp;#34;&amp;#34;)
with_items: &amp;#34;{{ appd_required_variables }}&amp;#34;
- name: Create an user and add to the global group
include_tasks: user.yml
when:
- username is defined
- username&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Avoid code complexity using quotes and standardize the way literals and empty strings are used&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Ansible-lint &lt;a href="https://ansible-lint.readthedocs.io/en/latest/default_rules.html#don-t-compare-to-literal-true-false" target="_blank" rel="noopener"&gt;E601 rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_delegation"&gt;Delegation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Do not use local_action, use delegate_to: localhost&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: Send summary mail
local_action:
module: mail
subject: &amp;#34;Summary Mail&amp;#34;
to: &amp;#34;{{ mail_recipient }}&amp;#34;
body: &amp;#34;{{ mail_body }}&amp;#34;
run_once: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: Send summary mail
mail:
subject: &amp;#34;Summary Mail&amp;#34;
to: &amp;#34;{{ mail_recipient }}&amp;#34;
body: &amp;#34;{{ mail_body }}&amp;#34;
delegate_to: localhost
run_once: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Avoid complexity, standardization, flexibility and code readability. The module and its parameters are easy to read and can be delegated even to a third party server.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Ansible-lint &lt;a href="https://ansible-lint.readthedocs.io/en/latest/default_rules.html#do-not-use-local-action-use-delegate-to-localhost" target="_blank" rel="noopener"&gt;E504 rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_playbook_file_extension"&gt;Playbook File Extension&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All Ansible Yaml files should have a .yml extension (and NOT .YML, .yaml etc).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
~/tasks.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
~/tasks.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Ansible tooling (like ansible-galaxy init) creates files with a .yml extension. Also, the Ansible documentation website references files with a .yml extension several times. Because of this, it is normal in the Ansible community to use a .yml extension for all Ansible YAML files.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Lint rule&lt;/strong&gt;&lt;br/&gt;
Ansible-lint &lt;a href="https://ansible-lint.readthedocs.io/en/latest/default_rules.html#use-yml-or-yaml-playbook-extension" target="_blank" rel="noopener"&gt;E205 rule&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_template_file_extension"&gt;Template File Extension&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All Ansible Template files should have a .j2 extension.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
~/template.conf&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
~/template.conf.j2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Ansible Template files will usually have the .j2 extension, which denotes the Jinja2 templating engine used.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_vaults"&gt;Vaults&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All Ansible Vault files should have a .vault extension (and NOT .yml, .YML, .yaml etc).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
~/secrets.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
~/secrets.vault&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
It is easier to control unencrypted files automatically for the specific .vault extension.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_debug_and_comments"&gt;Debug and Comments&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Do not overuse debug and comments in final code as much as possible. Use task and role names to explain what the task or role does. Use the verbose option under ansible for debugging purposes. If debug is used, assign a verbosity option. This will display the message only on certain debugging levels.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# bad
- name: print my_var
debug:
var: my_var
when: ansible_os_family == &amp;#34;Darwin&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# good
- name: print my_var
debug:
var: my_var
verbosity: 2
when: ansible_os_family == &amp;#34;Darwin&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
It will keep clean code and consistency avoiding extra debug and comments. Extra debug will spend extra time when running the playbook or role.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_use_modules_copy_or_templates_instead_of_linefile_or_blockfile"&gt;Use Modules copy or templates instead of linefile or blockfile&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of using the modules &lt;em&gt;linefile&lt;/em&gt; and &lt;em&gt;blockfile&lt;/em&gt;, which manage changes inside a file, it should be tried to use the modules &lt;code&gt;copy&lt;/code&gt; or &lt;code&gt;template&lt;/code&gt; instead, which will manage the whole file. For better future proof template should be preferred over copy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
When using linefile/blockfile only a single line or a part of a file is managed. It is not easy to remember which part exactly is managed and which is not. Using the template module the whole file is managed by Ansible and there is no confusion about different parts of a file.
Moreover, regular expressions can be avoided, which are often used using linefile.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_use_module_synchronize_instead_of_copy_for_large_files"&gt;Use Module synchronize Instead of copy for Large Files&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Copying large files takes significantly longer than syncing it. The &lt;code&gt;synchronize&lt;/code&gt; modules which are based on rsync can increase the time moving large files from one node to another (or even on the same node).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_do_not_show_sensitive_data_in_ansible_output"&gt;Do not Show Sensitive Data in Ansible Output&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When using the template module and there are passwords or other sensitive data in the file, use the &lt;code&gt;no_log&lt;/code&gt; option to hide this information.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
For obvious reasons the output of sensitive data on the screen (and logfile) should be prohibited.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_use_block_module"&gt;Use Block-Module&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Block can help to organize the code and can enable rollbacks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;- block:
copy:
src: critical.conf
dest: /etc/critical/crit.conf
service:
name: critical
state: restarted
rescue:
command: shutdown -h now&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong class="big"&gt;Reason&lt;/strong&gt;&lt;br/&gt;
Using blocks groups critical tasks together and allows better management when a single task of this block fails.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_enable_cpu_and_memory_profiling"&gt;Enable CPU and Memory Profiling&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Enabling profiling is extremely useful when testing forks or to analyse memory and cpu consumption in general. It will present a summary of used CPU/memory for the whole play and per task.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To enable it:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a new &lt;strong&gt;control group&lt;/strong&gt; - which is required for CPU and memory profiling&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;cgcreate -a root:root -t root:root -g cpuacct,memory,pids:ansible_profile&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;em&gt;ansible.cfg&lt;/em&gt;&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-ini hljs" data-lang="ini"&gt;callback_whitelist=cgroup_perf_recap
[callback_cgroup_perf_recap]
control_group=ansible_profile&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute the playbook using &lt;em&gt;cgexec&lt;/em&gt;&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;cgexec -g cpuacct,memory,pids:ansible_profile ansible-playbook x.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Analyse the usage&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Memory Execution Maximum: 11146.29MB
cpu Execution Maximum: 112.08%
pids Execution Maximum: 35.00
memory:
lab : creating network (b42e9945-0dc7-20b1-09d1-00000000000a): 11097.35MB …..&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_enable_task_and_role_profiling"&gt;Enable Task and Role Profiling&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following can be enabled as well, to help debugging playbooks and roles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-ini hljs" data-lang="ini"&gt; [defaults]
callback_whitelist = profile_tasks, profile_role, timer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Timer: duration of playbook execution (activated by default)
profile_tasks/role: displays execution time per tasks/role&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will generate an output like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/ansible/images/profiling.png" alt="Profiling"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_directory_structure"&gt;Directory Structure&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A consistent directory structure is important to easily understand all playbooks and roles which are written. Ansible knows many different folder structures, any can be used. However, it is important to stick to one structure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is an example. Not all folders are usually used and working with collections will change such structure a little bit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;.
├── ansible.cfg
├── ansible_modules
├── group_vars
│ ├── webservers
│ └── all
├── hosts
│ ├── webserver01
│ └── webserver02
├── host_vars
├── modules
├── playbooks
│ └── ansible-cmdb.yml
└── roles
├── requirements.yml
├── galaxy
└── dev-sec.ssh-hardening
└── auditd
├── files
│ ├── auditd.conf
│ ├── audit.yml
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
└── tasks
└── main.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Automation Controller and LDAP Authentication</title><link>https://blog.stderr.at/ansible/2021/10/automation-controller-and-ldap-authentication/</link><pubDate>Mon, 25 Oct 2021 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2021/10/automation-controller-and-ldap-authentication/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;The following article shall quickly, without huge background information, deploy an Identity Management Server (based on FreeIPA) and connect this IDM to an existing Automation Controller so authentication can be tested and verified based on LDAP.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_install_freeipa"&gt;Install FreeIPA&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Run the following command to deploy and configure the IPA Server:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;yum module enable idm:DL1&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;yum distro-sync&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;yum -y module install idm:DL1/server&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the server by calling the command &lt;code&gt;ipa-server-install&lt;/code&gt;. This will start an interactive installation modus which requires the basic information about the IPA server. The following uses &lt;strong&gt;tower.local&lt;/strong&gt; as base domain&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;Do you want to configure integrated DNS (BIND)? [no]:
Server host name [node01.tower.local]:
Please confirm the domain name [tower.local]:
Please provide a realm name [TOWER.LOCAL]:
Directory Manager password: &amp;lt;enter password&amp;gt;
Password (confirm): &amp;lt;enter password&amp;gt;
IPA admin password: &amp;lt;enter password&amp;gt;
Password (confirm): &amp;lt;enter password&amp;gt;
Do you want to configure chrony with NTP server or pool address? [no]:
Continue to configure the system with these values? [no]: yes&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once all information have been provided the installation/configuration process starts. This will take a while…​&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Be sure that the hostname, here &lt;strong&gt;node01.tower.local&lt;/strong&gt;, is resolvable, at least from the Tower/Controller node and the node you are accessing the FreeIPA UI. You can use your local &lt;em&gt;hosts&lt;/em&gt; file or a real domain name for that.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_login_to_ipa_server_via_command_line"&gt;Login to IPA server via Command Line&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;For user admin use: &lt;code&gt;kinit admin&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_create_a_binduser_binddn"&gt;Create a Binduser (BindDN)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Binduser (or BindDN) will be used by the Controller to authenticate the Controller against the LDAP server.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create the actual user&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ipa user-add --first=”BindUser” --last=”None” --password binduser&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Password:
Enter Password again to verify:
------------------
Added user &amp;#34;binduser&amp;#34;
------------------
User login: binduser
First name: ”BindUser”
Last name: ”None”
Full name: ”BindUser” ”None”
Display name: ”BindUser” ”None”
Initials: ””
Home directory: /home/binduser
GECOS: ”BindUser” ”None”
Login shell: /bin/sh
Principal name: binduser@TOWER.LOCAL
Principal alias: binduser@TOWER.LOCAL
User password expiration: 20211015133112Z
Email address: binduser@tower.local
UID: 1573400003
GID: 1573400003
Password: True
Member of groups: ipausers
Kerberos keys available: True&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign the new user to the admin group&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ipa group-add-member admins --users=binduser&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt; Group Name: admins
Description: Account administrators group
GID: 1573400000
Member users: admin, binduser
-----------------------------------
Number of members added 1
-----------------------------------&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a 2nd User to test the authentication later&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;ipa user-add --first=”User” --last=”Name” --password user1&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_enable_ldap_auth_in_automation_controller"&gt;Enable LDAP Auth in Automation Controller&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Login to Automation Controller ad go to &amp;#34;Settings &amp;gt; LDAP Settings &amp;gt; Default&amp;#34;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add a new connection:&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP Service URI&lt;/strong&gt;: &lt;code&gt;ldap://node01.tower.local:389&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP Bind Password&lt;/strong&gt;: &lt;code&gt;&amp;lt;password of user binduser&amp;gt;`&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP Group Type&lt;/strong&gt;: &lt;code&gt;MemberDNGroupType&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP Bind DN&lt;/strong&gt;: &lt;code&gt;uid=binduser,cn=users,cn=accounts,dc=tower,dc=local&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP User Search&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;[
&amp;#34;cn=users,cn=accounts,dc=tower,dc=local&amp;#34;,
&amp;#34;SCOPE_SUBTREE&amp;#34;,
&amp;#34;(uid=%(user)s)&amp;#34;
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP Group Search&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;[
&amp;#34;cn=groups,cn=accounts,dc=tower,dc=local&amp;#34;,
&amp;#34;SCOPE_SUBTREE&amp;#34;,
&amp;#34;(objectClass=posixgroup)&amp;#34;
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration should look like the following image:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/ansible/images/ControllerLDAPAuth.png" alt="Automation Controller LDAP Authentication"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Automation Controller LDAP Authentication&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_verify_login_with_user1"&gt;Verify Login with user1&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can now test the login using &lt;strong&gt;user1&lt;/strong&gt;. If it does not work, check the following files for errors:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Tower Node&lt;/strong&gt;: /var/log/tower/tower.log&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;IPA Node&lt;/strong&gt;: /var/log/dirsrv/slapd-TOWER-LOCAL/access&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The login should work, but since the user1 is not assigned to any Team/Organization inside the Automation Controller, no privileges are granted. The user can do nothing.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_automatically_assign_permissions"&gt;Automatically assign permissions&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;2 roles can be automatically assigned to authenticated users:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Super User&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Auditor&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To test this, 2 groups will be created in the LDAP server and a new user will be assigned to one of the groups.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create the group for super users: &lt;code&gt;ipa group-add tower_administrators&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the group for auditors: &lt;code&gt;ipa group-add tower_auditors&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new user: &lt;code&gt;ipa user-add --first=”User” --last=”Name” --password user2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign the user to one the the groups: &lt;code&gt;ipa group-add-member tower_administrators --users=user2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modify the Controller LDAP configuration and set &lt;strong&gt;LDAP User Flags by Group&lt;/strong&gt;. This will assing any member of &lt;em&gt;tower_administrators&lt;/em&gt; to &lt;em&gt;is_superuser&lt;/em&gt; for example.&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;is_superuser&amp;#34;: [
&amp;#34;cn=tower_administrators,cn=groups,cn=accounts,dc=tower,dc=local&amp;#34;
],
&amp;#34;is_system_auditor&amp;#34;: [
&amp;#34;cn=tower_auditors,cn=groups,cn=accounts,dc=tower,dc=local&amp;#34;
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Test the authentication and authorization with the &lt;strong&gt;user2&lt;/strong&gt;. This user should now gain super admin permissions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_allow_users_from_specific_groups_only"&gt;Allow Users From Specific Groups Only&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Not all LDAP users shall be able to authenticate. Only users, which are member of a specific group, shall be able to authenticate.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a 3rd user: &lt;code&gt;ipa user-add --first=”User” --last=”Name” --password user3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modify the LDAP Configuration in Automation Controller and set &lt;strong&gt;LDAP Require Groups&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;&amp;#34;cn=towerusers,cn=groups,cn=accounts,dc=tower,dc=local&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the group &lt;em&gt;toweruser&lt;/em&gt;: &lt;code&gt;ipa group-add towerusers&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign the user &lt;strong&gt;user3&lt;/strong&gt; to that group: &lt;code&gt;ipa group-add-member towerusers --users=user3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this state only &lt;strong&gt;user3&lt;/strong&gt; will be able to login. In order to allow the other users as well, all must be assigned to the group &lt;strong&gt;towerusers&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ipa group-add-member towerusers --users=user3
ipa group-add-member towerusers --users=user1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_additional_configuration"&gt;Additional Configuration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It is possible to automatically map users to Controller Organization. I did not fully test this, but the following is an example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock json"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-none hljs"&gt; {
&amp;#34;LDAP Organization&amp;#34;: {
&amp;#34;admins&amp;#34;: &amp;#34;cn=engineering_admins,ou=groups,dc=example,dc=com&amp;#34;,
&amp;#34;remove_admins&amp;#34;: false,
&amp;#34;users&amp;#34;: [
&amp;#34;cn=engineering,ou=groups,dc=example,dc=com&amp;#34;,
&amp;#34;cn=sales,ou=groups,dc=example,dc=com&amp;#34;,
&amp;#34;cn=it,ou=groups,dc=example,dc=com&amp;#34;
],
&amp;#34;remove_users&amp;#34;: false
},
&amp;#34;LDAP Organization 2&amp;#34;: {
&amp;#34;admins&amp;#34;: [
&amp;#34;cn=Administrators,cn=Builtin,dc=example,dc=com&amp;#34;
],
&amp;#34;remove_admins&amp;#34;: false,
&amp;#34;users&amp;#34;: true,
&amp;#34;remove_users&amp;#34;: false
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Ansible Tower and downloading collections</title><link>https://blog.stderr.at/ansible/2021/07/ansible-tower-and-downloading-collections/</link><pubDate>Sat, 31 Jul 2021 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2021/07/ansible-tower-and-downloading-collections/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Every wondered why Ansible Tower does not start downloading required
collections when you synchronize a project? Here are the stumbling
blocks we discovered so far:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_wrong_name_for_requirements_yml"&gt;Wrong name for requirements.yml&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When downloading collections Ansible Tower searches for a file
&lt;code&gt;requirements.yml&lt;/code&gt; in the collections directory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Be careful with the file extension: &lt;code&gt;requirements.yml&lt;/code&gt; has to end with
the extension &lt;code&gt;.yml&lt;/code&gt; and &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_collections_download_is_disabled_in_ansible_tower"&gt;Collections download is disabled in Ansible Tower&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Within Ansible Tower there is a setting called &lt;code&gt;ENABLE COLLECTION(S)
DOWNLOAD&lt;/code&gt; under &lt;code&gt;Settings&lt;/code&gt;/&lt;code&gt;Jobs&lt;/code&gt;. This has to be set to true, which
is also the default.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_no_ansible_galaxy_credential_defined_for_the_organization"&gt;No Ansible Galaxy credential defined for the organization&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Last but not least an Ansible Galaxy credential needs to be defined
for the organization where the project is defined. With the default
installation of Ansible Tower, when the sample playbooks are installed
there is a credential called &lt;code&gt;Ansible Galaxy&lt;/code&gt; defined. You need to assign
this credential to the organization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you skip installing the sample playbooks, &lt;strong&gt;no&lt;/strong&gt; &lt;code&gt;Ansible Galaxy&lt;/code&gt;
credential will be defined for you and you have to create it manually.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_how_does_this_actually_work"&gt;How does this actually work?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible Tower uses a Python virtual environment for running
Ansible. The default environment is installed in
&lt;code&gt;/var/lib/awx/venv/awx&lt;/code&gt;. You can also create custom environments, see
&lt;a href="https://docs.ansible.com/ansible-tower/latest/html/upgrade-migration-guide/virtualenv.html"&gt;Using virtualenv with Ansible Tower&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the default setup the following files define how collections are downloaded:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib/python3.6/site-packages/awx/main/tasks.py&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib/python3.6/site-packages/awx/playbooks/project_update.yml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_task_py"&gt;task.py&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;code&gt;task.py&lt;/code&gt; defines various internal tasks Tower has to run on various
occasions. For example in line number 1930 (Ansible Tower 3.8.3) the
task &lt;code&gt;RunProjectUpdate&lt;/code&gt; gets defined. This is the task Tower
has to run whenever a project update is required.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In our case the function &lt;code&gt;build_extra_vars_file&lt;/code&gt; (line 2083 with
Ansible Tower 3.8.3) defines the variable &lt;code&gt;galaxy_creds_are_defined&lt;/code&gt;
only if the organization has a galaxy credential defined (line 2099
Ansible Tower 3.8.3).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Line 2120 (Ansible Tower 3.8.3) finally defines the Ansible extra
variable &lt;code&gt;collections_enabled&lt;/code&gt; depending on
&lt;code&gt;galaxy_creds_are_defined&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_project_update_yml"&gt;project_update.yml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So &lt;code&gt;task.py&lt;/code&gt; defines the extra variable &lt;code&gt;collections_enabled&lt;/code&gt; (see
above). Finally the playbook &lt;code&gt;project_update.yml&lt;/code&gt; consumes this extra
variable and only downloads collections if &lt;code&gt;collections_enabled&lt;/code&gt; is
set to &lt;code&gt;true&lt;/code&gt;, see the block string at line 192 (Ansible Tower 3.8.3)
in `project_update.yml.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So long and thanks for all the fish!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Ansible - Azure Resource Manager Example</title><link>https://blog.stderr.at/ansible/2020/04/ansible-azure-resource-manager-example/</link><pubDate>Mon, 27 Apr 2020 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2020/04/ansible-azure-resource-manager-example/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Using Ansible Resource Manager with an ARM template and a simple Ansible playbook to deploy a Virtual Machine with Disk, virtual network, public IP and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;Source: [&lt;a href="#source_1"&gt;1&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In order to deploy a Virtual Machine and all depended resources using the Azure Resource Manager (ARM) template with Ansible, you will need three things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The ARM Template&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The parameters you want to use&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Ansible playbook&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All can be found below.
Store them and simply call:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-playbook Azure/create_azure_deployment.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
it will take several minutes, until everything has been deployment in Azure.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Instead of using a json file locally you can upload the template file (as well as a parameters file) to a version control system and use it from there.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_additional_resources"&gt;Additional Resources&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_ansible_playbook_create_azure_deplyoment_yml"&gt;Ansible Playbook: Create-Azure-Deplyoment.yml&lt;/h3&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;---
- name: Get facts of a VM
hosts: localhost
connection: local
become: false
gather_facts: false
tasks:
#- name: Destroy Azure Deploy
# azure_rm_deployment:
# resource_group: tju-ResourceGroup
# name: tju-testDeployment
# state: absent
- name: Create Azure Resource Group deployment
azure_rm_deployment:
state: present
resource_group_name: tju-ResourceGroup
name: tju-testDeployment
#template_link: &amp;#39;&amp;lt;YOUR RAW Github template file&amp;gt;&amp;#39;
#parameters_link: &amp;#39;&amp;lt;YOUR RAW Github parameters file&amp;gt;&amp;#39;
template: &amp;#34;{{ lookup(&amp;#39;file&amp;#39;, &amp;#39;ResourceManagerTemplate.json&amp;#39;) }}&amp;#34;
parameters:
projectName:
value: tjuProject
location:
value: &amp;#34;East US&amp;#34;
adminUsername:
value: tjungbauer
adminPublicKey:
value: &amp;#34;{{ lookup(&amp;#39;file&amp;#39;, &amp;#39;/Users/tjungbauer/.ssh/id_rsa.pub&amp;#39;) }}&amp;#34;
operatingSystem:
value: CentOS
operatingSystemPublisher:
value: OpenLogic
operatingSystemSKU:
value: &amp;#39;7.1&amp;#39;
vmSize:
value: Standard_D2s_v3
register: azure
- name: Add new instance to host group
add_host:
hostname: &amp;#34;{{ item[&amp;#39;ips&amp;#39;][0].public_ip }}&amp;#34;
groupname: azure_vms
loop: &amp;#34;{{ azure.deployment.instances }}&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_resourcemanagertemplate_json"&gt;ResourceManagerTemplate.json&lt;/h3&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;$schema&amp;#34;: &amp;#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&amp;#34;,
&amp;#34;contentVersion&amp;#34;: &amp;#34;1.0.0.0&amp;#34;,
&amp;#34;parameters&amp;#34;: {
&amp;#34;projectName&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies a name for generating resource names.&amp;#34;
}
},
&amp;#34;location&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;defaultValue&amp;#34;: &amp;#34;[resourceGroup().location]&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the location for all resources.&amp;#34;
}
},
&amp;#34;adminUsername&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies a username for the Virtual Machine.&amp;#34;
}
},
&amp;#34;adminPublicKey&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the SSH rsa public key file as a string. Use \&amp;#34;ssh-keygen -t rsa -b 2048\&amp;#34; to generate your SSH key pairs.&amp;#34;
}
},
&amp;#34;operatingSystem&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the Operating System. i.e. CentOS&amp;#34;
}
},
&amp;#34;operatingSystemPublisher&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the publisher. i.e. OpenLogic&amp;#34;
}
},
&amp;#34;operatingSystemSKU&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the version of the OS. i.e. 7.1&amp;#34;
}
},
&amp;#34;vmSize&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;metadata&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Specifies the the VM size. i.e. Standard_D2s_v3&amp;#34;
}
}
},
&amp;#34;variables&amp;#34;: {
&amp;#34;vNetName&amp;#34;: &amp;#34;[concat(parameters(&amp;#39;projectName&amp;#39;), &amp;#39;-vnet&amp;#39;)]&amp;#34;,
&amp;#34;vNetAddressPrefixes&amp;#34;: &amp;#34;10.0.0.0/16&amp;#34;,
&amp;#34;vNetSubnetName&amp;#34;: &amp;#34;default&amp;#34;,
&amp;#34;vNetSubnetAddressPrefix&amp;#34;: &amp;#34;10.0.0.0/24&amp;#34;,
&amp;#34;vmName&amp;#34;: &amp;#34;[concat(parameters(&amp;#39;projectName&amp;#39;), &amp;#39;-vm&amp;#39;)]&amp;#34;,
&amp;#34;publicIPAddressName&amp;#34;: &amp;#34;[concat(parameters(&amp;#39;projectName&amp;#39;), &amp;#39;-ip&amp;#39;)]&amp;#34;,
&amp;#34;networkInterfaceName&amp;#34;: &amp;#34;[concat(parameters(&amp;#39;projectName&amp;#39;), &amp;#39;-nic&amp;#39;)]&amp;#34;,
&amp;#34;networkSecurityGroupName&amp;#34;: &amp;#34;[concat(parameters(&amp;#39;projectName&amp;#39;), &amp;#39;-nsg&amp;#39;)]&amp;#34;,
&amp;#34;networkSecurityGroupName2&amp;#34;: &amp;#34;[concat(variables(&amp;#39;vNetSubnetName&amp;#39;), &amp;#39;-nsg&amp;#39;)]&amp;#34;
},
&amp;#34;resources&amp;#34;: [
{
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Network/networkSecurityGroups&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2018-11-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;networkSecurityGroupName&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;securityRules&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;ssh_rule&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;description&amp;#34;: &amp;#34;Locks inbound down to ssh default port 22.&amp;#34;,
&amp;#34;protocol&amp;#34;: &amp;#34;Tcp&amp;#34;,
&amp;#34;sourcePortRange&amp;#34;: &amp;#34;*&amp;#34;,
&amp;#34;destinationPortRange&amp;#34;: &amp;#34;22&amp;#34;,
&amp;#34;sourceAddressPrefix&amp;#34;: &amp;#34;*&amp;#34;,
&amp;#34;destinationAddressPrefix&amp;#34;: &amp;#34;*&amp;#34;,
&amp;#34;access&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;priority&amp;#34;: 123,
&amp;#34;direction&amp;#34;: &amp;#34;Inbound&amp;#34;
}
}
]
}
},
{
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Network/publicIPAddresses&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2018-11-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;publicIPAddressName&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;publicIPAllocationMethod&amp;#34;: &amp;#34;Dynamic&amp;#34;
},
&amp;#34;sku&amp;#34;: {
&amp;#34;name&amp;#34;: &amp;#34;Basic&amp;#34;
}
},
{
&amp;#34;comments&amp;#34;: &amp;#34;Simple Network Security Group for subnet [variables(&amp;#39;vNetSubnetName&amp;#39;)]&amp;#34;,
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Network/networkSecurityGroups&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2019-08-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;networkSecurityGroupName2&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;securityRules&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;default-allow-22&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;priority&amp;#34;: 1000,
&amp;#34;access&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;direction&amp;#34;: &amp;#34;Inbound&amp;#34;,
&amp;#34;destinationPortRange&amp;#34;: &amp;#34;22&amp;#34;,
&amp;#34;protocol&amp;#34;: &amp;#34;Tcp&amp;#34;,
&amp;#34;sourceAddressPrefix&amp;#34;: &amp;#34;*&amp;#34;,
&amp;#34;sourcePortRange&amp;#34;: &amp;#34;*&amp;#34;,
&amp;#34;destinationAddressPrefix&amp;#34;: &amp;#34;*&amp;#34;
}
}
]
}
},
{
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Network/virtualNetworks&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2018-11-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;vNetName&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;dependsOn&amp;#34;: [
&amp;#34;[resourceId(&amp;#39;Microsoft.Network/networkSecurityGroups&amp;#39;, variables(&amp;#39;networkSecurityGroupName2&amp;#39;))]&amp;#34;
],
&amp;#34;properties&amp;#34;: {
&amp;#34;addressSpace&amp;#34;: {
&amp;#34;addressPrefixes&amp;#34;: [
&amp;#34;[variables(&amp;#39;vNetAddressPrefixes&amp;#39;)]&amp;#34;
]
},
&amp;#34;subnets&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;vNetSubnetName&amp;#39;)]&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;addressPrefix&amp;#34;: &amp;#34;[variables(&amp;#39;vNetSubnetAddressPrefix&amp;#39;)]&amp;#34;,
&amp;#34;networkSecurityGroup&amp;#34;: {
&amp;#34;id&amp;#34;: &amp;#34;[resourceId(&amp;#39;Microsoft.Network/networkSecurityGroups&amp;#39;, variables(&amp;#39;networkSecurityGroupName2&amp;#39;))]&amp;#34;
}
}
}
]
}
},
{
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Network/networkInterfaces&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2018-11-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;networkInterfaceName&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;dependsOn&amp;#34;: [
&amp;#34;[resourceId(&amp;#39;Microsoft.Network/publicIPAddresses&amp;#39;, variables(&amp;#39;publicIPAddressName&amp;#39;))]&amp;#34;,
&amp;#34;[resourceId(&amp;#39;Microsoft.Network/virtualNetworks&amp;#39;, variables(&amp;#39;vNetName&amp;#39;))]&amp;#34;,
&amp;#34;[resourceId(&amp;#39;Microsoft.Network/networkSecurityGroups&amp;#39;, variables(&amp;#39;networkSecurityGroupName&amp;#39;))]&amp;#34;
],
&amp;#34;properties&amp;#34;: {
&amp;#34;ipConfigurations&amp;#34;: [
{
&amp;#34;name&amp;#34;: &amp;#34;ipconfig1&amp;#34;,
&amp;#34;properties&amp;#34;: {
&amp;#34;privateIPAllocationMethod&amp;#34;: &amp;#34;Dynamic&amp;#34;,
&amp;#34;publicIPAddress&amp;#34;: {
&amp;#34;id&amp;#34;: &amp;#34;[resourceId(&amp;#39;Microsoft.Network/publicIPAddresses&amp;#39;, variables(&amp;#39;publicIPAddressName&amp;#39;))]&amp;#34;
},
&amp;#34;subnet&amp;#34;: {
&amp;#34;id&amp;#34;: &amp;#34;[resourceId(&amp;#39;Microsoft.Network/virtualNetworks/subnets&amp;#39;, variables(&amp;#39;vNetName&amp;#39;), variables(&amp;#39;vNetSubnetName&amp;#39;))]&amp;#34;
}
}
}
]
}
},
{
&amp;#34;type&amp;#34;: &amp;#34;Microsoft.Compute/virtualMachines&amp;#34;,
&amp;#34;apiVersion&amp;#34;: &amp;#34;2018-10-01&amp;#34;,
&amp;#34;name&amp;#34;: &amp;#34;[variables(&amp;#39;vmName&amp;#39;)]&amp;#34;,
&amp;#34;location&amp;#34;: &amp;#34;[parameters(&amp;#39;location&amp;#39;)]&amp;#34;,
&amp;#34;dependsOn&amp;#34;: [
&amp;#34;[resourceId(&amp;#39;Microsoft.Network/networkInterfaces&amp;#39;, variables(&amp;#39;networkInterfaceName&amp;#39;))]&amp;#34;
],
&amp;#34;properties&amp;#34;: {
&amp;#34;hardwareProfile&amp;#34;: {
&amp;#34;vmSize&amp;#34;: &amp;#34;[parameters(&amp;#39;vmSize&amp;#39;)]&amp;#34;
},
&amp;#34;osProfile&amp;#34;: {
&amp;#34;computerName&amp;#34;: &amp;#34;[variables(&amp;#39;vmName&amp;#39;)]&amp;#34;,
&amp;#34;adminUsername&amp;#34;: &amp;#34;[parameters(&amp;#39;adminUsername&amp;#39;)]&amp;#34;,
&amp;#34;linuxConfiguration&amp;#34;: {
&amp;#34;disablePasswordAuthentication&amp;#34;: true,
&amp;#34;ssh&amp;#34;: {
&amp;#34;publicKeys&amp;#34;: [
{
&amp;#34;path&amp;#34;: &amp;#34;[concat(&amp;#39;/home/&amp;#39;, parameters(&amp;#39;adminUsername&amp;#39;), &amp;#39;/.ssh/authorized_keys&amp;#39;)]&amp;#34;,
&amp;#34;keyData&amp;#34;: &amp;#34;[parameters(&amp;#39;adminPublicKey&amp;#39;)]&amp;#34;
}
]
}
}
},
&amp;#34;storageProfile&amp;#34;: {
&amp;#34;imageReference&amp;#34;: {
&amp;#34;publisher&amp;#34;: &amp;#34;[parameters(&amp;#39;operatingSystemPublisher&amp;#39;)]&amp;#34;,
&amp;#34;offer&amp;#34;: &amp;#34;[parameters(&amp;#39;operatingSystem&amp;#39;)]&amp;#34;,
&amp;#34;sku&amp;#34;: &amp;#34;[parameters(&amp;#39;operatingSystemSKU&amp;#39;)]&amp;#34;,
&amp;#34;version&amp;#34;: &amp;#34;latest&amp;#34;
},
&amp;#34;osDisk&amp;#34;: {
&amp;#34;createOption&amp;#34;: &amp;#34;fromImage&amp;#34;
}
},
&amp;#34;networkProfile&amp;#34;: {
&amp;#34;networkInterfaces&amp;#34;: [
{
&amp;#34;id&amp;#34;: &amp;#34;[resourceId(&amp;#39;Microsoft.Network/networkInterfaces&amp;#39;, variables(&amp;#39;networkInterfaceName&amp;#39;))]&amp;#34;
}
]
}
}
}
],
&amp;#34;outputs&amp;#34;: {
&amp;#34;adminUsername&amp;#34;: {
&amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;,
&amp;#34;value&amp;#34;: &amp;#34;[parameters(&amp;#39;adminUsername&amp;#39;)]&amp;#34;
}
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_sources"&gt;Sources&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a id="source_1"&gt;&lt;/a&gt;[1]: &lt;a href="https://docs.ansible.com/ansible/latest/modules/azure_rm_deployment_module.html" target="_blank" rel="noopener"&gt;azure_rm_deployment – Create or destroy Azure Resource Manager template deployments&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>DO410 Ansible and Ansible Tower training notes</title><link>https://blog.stderr.at/ansible/2020/04/do410-ansible-and-ansible-tower-training-notes/</link><pubDate>Mon, 06 Apr 2020 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2020/04/do410-ansible-and-ansible-tower-training-notes/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Notes taken during Red Hat course D410 Ansible and Ansible Tower.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ansible_installation"&gt;Ansible installation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;make sure that &lt;em&gt;libselinux-python&lt;/em&gt; is installed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ansible 2.7 requires python 2.6 or 3.5&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;yum list installed python&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;windows modules implemented in powershell&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ansible requires at least .net 4.0&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuration_files"&gt;Configuration files&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible searches for ansible.cfg in the following order:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$ANSIBLE_CFG&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ansible.cfg in the current directory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$HOME/ansible.cfg&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/etc/ansible/ansible.cfg&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;whichever it finds first will be used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;use&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible --version&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;to see which config file is currently used. you can view/dump/see what changed with&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-config [list|dump|view]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_default_modules"&gt;Default modules&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;List all available modules via&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-doc -l&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For getting help on a specific module use&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-doc ping&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ad_hoc_commmands"&gt;Ad-hoc commmands&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To display ansible output on a single line per host for easier readablility use the &lt;em&gt;-o&lt;/em&gt; option&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible all -m command -a /bin/hostname -o&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use the raw module for directly executing commands on remote systems that do not have python installed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible -m raw&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_custom_facts"&gt;Custom Facts&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible uses custom facts from &lt;em&gt;/etc/ansible/facts.d/&lt;/em&gt;. Facts can be
stored in .ini style or you can place executable scripts in this
directory. The script needs to output JSON. Custom facts are available via &lt;em&gt;ansible_facts.ansible_local&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_magic_variables_available"&gt;Magic variables available&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;hostvars: variables defined for this host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;group_names: list of groups this host is a member of&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;groups: list of all groups and hosts in the inventory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;inventory_hostname: host name of the current host as configured in the inventory&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_matching_hosts_in_the_inventory"&gt;Matching hosts in the inventory&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Some examples on how to match hosts defined in the inventory&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&amp;#39;*.lab.com&amp;#39;: match all hosts starting with lab.com&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;#39;lab,datacenter&amp;#39;: match all hosts either in lab or datacenter&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;#39;datacenter*&amp;#39;: match all host &lt;strong&gt;and host groups&lt;/strong&gt; starting with datacenter&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;#39;lab,&amp;amp;datacenter&amp;#39;: match hosts in the lab and datacenter group&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;#39;datacenter,!test.lab.com&amp;#39;: match all hosts in datacenter, except &lt;em&gt;test.lab.com&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_dynamic_inventory"&gt;Dynamic inventory&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Example scripts for dynamic inventories can be found at
&lt;a href="https://github.com/ansible/ansible/tree/devel/contrib/inventory" class="bare"&gt;https://github.com/ansible/ansible/tree/devel/contrib/inventory&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can use &lt;code&gt;ansible-inventory&lt;/code&gt; to take a look a the current inventory
as json. This also works for static inventories.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Inventories can be combined. Just create a directory containing a
static inventory and script to create a dynamic inventory, ansible
will happily execute the scripts and merge everything together.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_debugging"&gt;Debugging&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following might be useful when debugging ansible roles and playbooks&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-playbook play.yml --syntax-check
ansible-playbook play.yml --step
ansible-playbook play.yml --start-at-task=&amp;#34;start httpd service&amp;#34;
ansible-playbook --check play.yml
ansible-playbook --check --diff play.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ansible_tower"&gt;Ansible Tower&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Notes on deploying and working with ansible tower.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installation"&gt;Installation&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;System requirements:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;at least 4GB of RAM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;actual requirement depends on &lt;em&gt;forks&lt;/em&gt; variable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;recommendation is 100MB memory for each for + 2GB of memory for tower services&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;20GB of disk storage, at least 10GB in /var&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Steps for installing:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;download setup tar.gz from &lt;a href="http://releases.ansible.com/ansible-tower/setup/" class="bare"&gt;http://releases.ansible.com/ansible-tower/setup/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set passwords in &lt;em&gt;inventory&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run &lt;em&gt;./setup.sh&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_authentication"&gt;Authentication&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Authentication settings can be changed under Settings /
Authentication. E.g for configuring Azure AD authentication we are
going to need&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;an Azure AD oauth2 key and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a Azure AD oauth2 secret&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_rbac"&gt;RBAC&lt;/h3&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;separate roles for organizations and inventories&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you need to assign roles to organizations and inventories&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_the_tower_flow"&gt;The Tower Flow&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;These are the steps to run playbooks against managed nodes in Tower:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create an organization if required&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create users&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create teams and assign users&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create credentials for accessing managed nodes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign credential to organization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create credentials for accessing SCM repositories (e.g. git)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign credentials to users or teams&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a project&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assign Teams to project&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a job template for executing playbooks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_ansible_roles_support"&gt;Ansible Roles support&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If the project includes a &lt;code&gt;requirements.txt&lt;/code&gt; file in the &lt;em&gt;roles/&lt;/em&gt; folder, tower will automatically run&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;ansible-galaxy install -r roles/requirements.yml -p ./roles/ --force&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;at the end of an update. So this could be used to include external
dependencies (like SAP ansible roles).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_job_templates"&gt;Job Templates&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible playbooks are stored in GIT repositories. A job template defines&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the inventory used for this job template&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the project for executing this job&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;this connects the GIT repository used in this project with the template&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the playbook to execute&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the credentials for executing jobs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;permissions for users / teams (e.g. admin, execute)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Tower creates jobs from those templates, which are ansible runs
executed against managed nodes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_fact_caching"&gt;Fact Caching&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It might be a good idea to use the tower facts cache. To speed up
playbook runs set &lt;code&gt;gather_facts: no&lt;/code&gt; in the play. Then enable the
facts cache in tower.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In tower settings set a timeout for the cache&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In job templates enable &lt;code&gt;Use facts cache&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a playbook that runs on a regular basis to gather facts, e.g.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-ansible hljs" data-lang="ansible"&gt;- name: Refresh fact cache
hosts: all
gather_facts: yes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_inventory_options"&gt;Inventory options&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;These are the options for creating inventories in Ansible Tower&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;static inventory defined in tower&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;importing static inventories via &lt;em&gt;awx-manage&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;static inventory defined in git repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;dynamic inventory via a custom script&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;dynamic inventory provides by tower (e.g. satellite)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A special feature in Tower are so called &lt;strong&gt;smart inventories&lt;/strong&gt;. A smart
inventory combines all static and dynamic inventories and allows
filtering based on facts. Filtering requires a valid fact cache.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_troubleshooting"&gt;Troubleshooting&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Tower uses the following components:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;postgresql&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;memcached&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rabbitmq&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;supervisord&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Useful tools&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;ansible-tower-service&lt;/em&gt; (e.g. status / restart)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;supervisorctl&lt;/em&gt; (e.g. status)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;awx-manage&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Tower stores log files in&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/log/tower/&lt;/em&gt; (e.g. tower.log).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/log/supervisor/&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/log/nginx/&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Other important directories&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/lib/awx/public/static&lt;/em&gt; static files served by django&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/lib/awx/projects&lt;/em&gt; stores all project related files e.g. git checkouts)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/var/lib/awx/jobs_status&lt;/em&gt; job status output&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
by default playbook runs are confined to &lt;em&gt;/tmp&lt;/em&gt; this might lead
to problems with tasks running on the local system.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In case of a lost &lt;em&gt;admin&lt;/em&gt; password you can use &lt;em&gt;awx-manage&lt;/em&gt; to reset the password or create a new superuser:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;awx-manage changepassword admin
awx-manage createsuperuser&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_replacing_the_default_tls_certificates"&gt;Replacing the default TLS certificates&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible tower uses nginx to service it’s web interface over TLS. Nginx
uses the configuration file &lt;em&gt;/etc/nginx/nginx.conf&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To deploy custom TLS certificates used by tower replace the
certificate and private key in &lt;em&gt;/etc/tower&lt;/em&gt;. You have to replace&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/etc/tower/tower.crt&lt;/em&gt; and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;/etc/tower/tower.key&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It might be a good idea to create a backup copy before overwriting
those files.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_backup_and_restore"&gt;Backup and restore&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Of course backup and restore are done via ansible. The ansible tower
setup script &lt;code&gt;setup.sh&lt;/code&gt; provides a wrapper around these playbooks. Execute&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;setup.sh -b&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;to perform a backup. This creates a backup .tar.gz file in the current directory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To restore a backup use&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;setup.sh -r&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;this restores the latest backup per default.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_things_to_remember"&gt;Things to remember&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Workflow job templates&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add &lt;code&gt;autocmd FileType yaml setlocal ai ts=2 sw=2 et&lt;/code&gt; to .vimrc&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;use &lt;code&gt;sudo yum install python-cryptography&lt;/code&gt; if there are many vault files to speed up ansible&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item></channel></rss>