Terraform test improvements in 1.10 for everyday modules
Terraform 1.10 continued the evolution of terraform test, making module testing more practical for daily development. The last six months have seen more teams adopt it for pre‑merge validation instead of relying only on plan/apply checks.
What changed
Tests fit into normal workflows
terraform test now works as a first‑class workflow for module authors. Combined with config‑driven import and more predictable state behavior, it’s a real alternative to ad‑hoc shell scripts.
Faster iteration in CI
The test framework is designed to reduce the need for full environment provisioning when validating modules, which speeds up the feedback loop for contributors.
Why it helps fellow developers
Fewer regressions in shared modules
A clear contract for module behavior
CI runs that are faster and more reliable
Practical action list
Add terraform test to CI for shared modules.
Start with unit‑style validations before full integration tests.
Keep test configs small and focused on outcomes, not implementation.
The result: safer module changes and faster feedback for everyone.
Terraform 1.10 config-driven import and why it matters
Terraform 1.10 introduced config‑driven import, and in the past six months it has matured into a practical workflow for teams managing brownfield infrastructure. Instead of writing ad-hoc CLI scripts, you can declare import blocks in configuration and let Terraform reconcile resources in a repeatable way.
What changed
Import is now part of configuration
You can define import blocks in .tf files, commit them, and run terraform apply to bring existing infrastructure under management. This makes imports reviewable and repeatable.
Safer migration workflows
Because the import intent lives in version control, code review becomes the safety net. Teams can see exactly what is being imported and why.
Why it helps fellow developers
This shifts import from “hero debugging” to a team‑friendly workflow:
Less tribal knowledge about IDs and one-off commands
Easier rollback and review
Better alignment with IaC best practices
Practical action list
Use import blocks for any existing infra being adopted into Terraform.
Keep import blocks alongside the resources they target.
Remove import blocks after successful state adoption.
It’s a small feature that makes the “from legacy to IaC” path way smoother.
Molecule 5.0 and the driver future
Molecule 5.0 has been a meaningful release for role maintainers in the last six months. It continues the shift toward cleaner, more modular driver behavior and leans further into consistent test pipelines across different backends.
What changed
Driver focus is clearer
Molecule 5.0 emphasizes driver‑specific behavior so that Docker, Podman, and cloud drivers behave consistently with fewer surprises. This means less time fighting driver quirks and more time validating role logic.
More predictable testing pipelines
The workflow is more streamlined, which makes it easier to standardize Molecule pipelines across multiple roles and contributors.
Why it helps fellow developers
Cleaner driver boundaries reduce “works on my laptop” issues
Standardized pipelines make contributor onboarding easier
More consistent results across CI environments
Practical action list
Pin to Molecule 5.x in role requirements.
Align driver configs across roles so contributors don’t have to relearn every repo.
Keep scenario definitions minimal and focused on behavior, not environment quirks.
If your team maintains many roles, Molecule 5.0’s driver focus will save hours of debugging.
Molecule 5.0 makes CI more stable for roles
If you run Molecule in CI, the last six months have brought improvements that make pipelines more stable. Molecule 5.0 reduces surprises around driver behavior and keeps scenarios more consistent across environments.
What changed
Cleaner defaults
Molecule 5.0 is more opinionated about defaults, which reduces the need for custom glue in every repo. That makes CI definitions smaller and easier to maintain.
Better multi‑driver reliability
For teams that test on more than one platform, consistency across drivers is the most noticeable improvement. Fewer driver‑specific hacks are needed.
Why it helps fellow developers
Less CI flakiness
Faster contributor onboarding
More confidence in release pipelines
Practical action list
Standardize Molecule configs across repos.
Keep driver configs explicit but minimal.
Run Molecule locally with the same driver versions as CI.
Molecule 5.0 reduces friction in the feedback loop, which is exactly what teams want from testing.
Galaxy Collections in 2026 - What Changed
The last six months brought a quieter but important shift in Ansible Galaxy workflows: the older v2 Galaxy server API is gone in ansible-galaxy. If you had custom tooling that still talked to v2, it will now fail and needs to move to the supported API.
What changed
v2 API removed
The ansible-galaxy CLI no longer supports the v2 server API. That’s a hard break for legacy automation that scraped or pushed data using v2 endpoints.
Cleaner, more predictable CLI behavior
Alongside API cleanup, a few legacy compat shims were removed. The net effect is fewer “it used to work” surprises and more deterministic behavior for automation pipelines.
Why it helps other developers
When teams standardize on the supported Galaxy API, the pipeline becomes:
More stable across Ansible versions.
Easier to maintain because fewer undocumented behaviors remain.
More secure because old endpoints are a common attack surface and often lack modern auth expectations.
Quick migration checklist
Inventory internal scripts that call Galaxy directly.
Check for v2 endpoints and update to the supported API.
Re-run collection publish workflows in CI to confirm behavior.
This is a small change on paper, but it eliminates a class of flaky Galaxy automation that’s been around for years.
ansible-core 2.20 porting changes that matter in daily playbooks
In the last six months (Aug 2025–Feb 2026), ansible-core 2.20 landed with a set of behavioral changes that are easy to miss but can impact real-world automation. The porting guide is the fastest way to spot those changes before they bite you in CI.
Changes you’ll actually feel
PowerShell path handling on Windows
Quote stripping in PowerShell operations has been removed. If you had playbooks that relied on Ansible “fixing” over‑quoted paths, you now need to tighten those paths yourself. This mostly affects Windows copy/fetch workflows.
Deprecated features that are now gone
Several deprecated behaviors have been removed, including:
The smart value for DEFAULT_TRANSPORT
The v2 Galaxy server API in ansible-galaxy
Deprecated options like install_repoquery in dnf/dnf5 and keepcache in yum_repository
Old Paramiko config keys and a few utility/compat shims
Deprecation signal you should act on
INJECT_FACTS_AS_VARS is now deprecated, which is a strong hint to avoid depending on injected facts as top-level vars and instead use ansible_facts directly.
Why it helps fellow developers
These changes make playbooks and plugins more explicit and less magical:
Better Windows path hygiene improves cross‑shell reliability.
Removing old APIs reduces “it works on my box” surprises across teams.
Cleaner fact usage makes roles easier to reason about and test.
Practical action list
Run the porting guide checklist against your Windows-heavy roles.
Search for deprecated options (DEFAULT_TRANSPORT=smart, vaultid, install_repoquery, keepcache) and replace or remove them.
Refactor fact usage to rely on ansible_facts instead of injected vars.
If you’re maintaining shared roles, updating now saves you a lot of churn once collections start tightening compatibility on top of 2.20.
Terraform loops
There are a couple of ways to loop in Terraform. Let’s have a look.
As you can see, the resource some_resource is being created 3 (count = 3) times.
The name should be unique, so the count.index variable is used. This variable is available when using count.
The variable count.index has these values:
itteration
count.index value
first
0
second
1
third
2
And so on.
Counting and looking up values
The parameter count sounds simple, but is actually quite powerful. Lets have a look at this example.
count is calculated by the length function. It basically counts how many maps there are in the list virtual_machines.
name is looked-up in the variable var.virtual_machines. Pick the first (0) entry from var.virtual_machines in the first itteration.
Similarly size is looked up.
This results in two resources:
resource"fake_virtual_machine""default"{name="first"size=16}# NOTE: This code does not work; `default` may not be repeated. It's just to explain what happens.resource"fake_virtual_machine""default"{name="second"size=32}
For each
The looping mechanism for_each can also be used. Similar to the count example, let’s think of a data structure to make virtual machines:
The dynamic "disk" { starts a dynamic block. The name (“disk”) must reflect the parameter in the resource, not juts any name. Now a new object is available; disk.
The for_each = each.value.disks loops the dynamic block. The loop uses disks from an already looped value var.virtual_machines.
The content { block will be rendered by Terraform.
The name = disk.value.name uses the disk variable (created by the block iteratordisk) to find the value from the disks map.
Hope that helps a bit when writing loops in Terraform!
Looping in Terraform, Ansible and Bash
Looping is quite an important mechanism in coding. (Thanks @Andreas for the word coding, a mix of scripting and programming.)
Looping allows you to write a piece of logic once, and reuse it as many times are required.
Looping is difficult to understand for new people new to coding. It’s sometimes difficult for me to. This article will probably help me a bit too!
A sequence
The simplest loop I know is repeating a piece of logic for a set of number or letters, like this:
1 2 3
First off, here is how to generate a sequence:
bash
ansible
terraform
seq 1 3
with_sequence: start=1 end=3
range(1, 3)
{1..3}
For all languages, this returns a list of numbers.
bash
ansible
terraform
1 2 3
item: 1, item: 2, item: 3
[ 1, 2, 3, ]
So, a more complete example for the three languages:
Bash
for number in{1..3};do
echo"number: ${number}
done
If you’re used to shells and their commands like bash, sed and grep, here are a few alternatives for Ansible.
Using these native alternatives has an advantage, developers maintain the Ansible modules for you, they are idempotent and likely work on more distributions or platforms.
Grep
Imagine you need to know if a certain patter is in a file. With a shell script you would use something like this:
With Ansible you can achieve a similar result like this:
----hosts:allgather_facts:notasks:-name:check if pattern if foundlineinfile:path:file.txtregexp:'.*pattern.*'line:'whatever'register:check_if_pattern_is_foundcheck_mode:yesnotify:do somethinghandlers:-name:do somethingcommand:do-something-if-pattern-is-found.sh
Hm, much longer than the bash example, but native Ansible!
Sed
So you like sed? So do I. It’s one of the most powerful tools I’ve used.
Lets replace some pattern in a file:
sed's/pattern_a/pattern_b/g' file.txt
This would repace all occurences of pattern_a for pattern_b. Lets see what Ansible looks like.
The find (UNIX) tools is really powerful too, imagine this command:
find / -name some_file.txt -execrm{}\;
That command would find all files (and directories) named some_file.txt and remove them. A bit dangerous, but powerful.
With Ansible:
-name:find and removegather_facts:notasks:-name:find filesfind:paths:/patterns:some_file.txtregister:found_files-name:remove filesfile:path:"{{item.path}}"state:absentloop:"{{found_files.results}}"
Conclusion
Well, have fun with all non-shell solutions. You hardly needs the shell or command modules when you get the hang of it.
Debugging services in Ansible
Sometimes services don’t start and give an error like:
Unable to start service my_service: A dependency job for my_service.service failed. See 'journalctl -xe' for details.
Well, if you’re testing in CI, you can’t really access the instance that has an issue. So how to you troubleshoot this?
The rescue runs when the initial task (start my_service) fails. It has two tasks.
The task collect information runs a few commands, add extra is you know more and saves the results in my_service_collect_information.
The task show information displays the saved result. Because collect information has a loop, the variable has .results appended, which is a list to needs to be looped over.
Hope this helps you troubleshoot services in Travis of Google Actions.
5 times why
Why do I write all this code? What’s the purpose and were does it stop?
To answer this question, there is a method you can use: 5 times why. Answer the initial question and pose a new question: “Why?”. Repeat the process a couple of times until you get to the core of the reason. Let’s go.
1. Why do I write all this code?
Because it’s a great way to learn a language.
2. Why do I want to learn a new language?
Technology changes, this allows me to keep up to date.
3. Why do I need to be up to date?
Being up to date allows me to be relevant.
4. Why do I need to be relevant.
Being relevant allows me to steer my career.
5. Why do I need to steer my career?
I don’t want to depend on 1 single employer and have many options when choosing a job.
Conclusion
So, I write all this code to not depend on any one employer. Interesting conclusion, I tend to agree.
GitHub is offering 20 concurrent builds which is quite a lot, more than Travis’s limit of 5. The build could be 4 times faster. Faster CI, happier developers. ;-)
Here are a few examples. First; just release to Galaxy, no testing includes. (Not a smart idea)
As you can see, 2 actions are used, checkout which gets the code and galaxy-action to push the role to Galaxy. Galaxy does lint-testing, but not functional testing. You can use the molecule-action to do that.
The build is split in 2 parts now; test and release and release needs test to be done.
You can also see that checkout is now called with a path which allows Molecule to find itself. (ANSIBLE_ROLES_PATH: $ephemeral_directory/roles/:$project_directory/../)
Finally you can include a matrix to build with a matrix of variables set.
Now your role is tested on the list of images specified.
Hope these actions make it easier to develop, test and release your roles, if you find problems, please make an issue for either the molecule or galaxy action.
The benefit is that you (or others) can reuse the action. Have fun making GitHub actions!
And, or and not
Today I spent a couple of hours on a condition that contained a mistake. Let me try to help myself and describe a few situations.
Condition?
A condition in Ansible can be described in a when statement. This is a simple example:
-name:do something only to virtual instancesdebug:msg:"Hereisamessagefromaguest"when:ansible_virtualization_role == "guest"
And
It’s possible to describe multiple conditions. In Ansible, the when statement can be a string (see above) or a list:
-name:do something only to Red Hat virtual instancesdebug:msg:"HereisamessagefromaRedHatguest"when:-ansible_virtualization_role == "guest"-ansible_os_family == "RedHat"
The above example will run when it’s both a virtual instance and it’s a Red Hat-like system.
Or
Instead of combining (‘and’) conditions, you can also allow multiple condition where either is true:
-name:do something to either Red Hat or virtual instancesdebug:msg:"HereisamessagefromaRedHatsystemoraguest"when:-ansible_virtualization_role == "guest" or ansible_os_family == "RedHat"
I like to keep lines short to increase readability:
when:
- ansible_virtualization_role == "guest" or
ansible_os_family == "RedHat"
And & or
You can also combine and and or:
-name:do something to a Debian or Red Hat, if it's a virtual instancesdebug:msg:"HereisamessagefromaRedHatorDebianguest"when:-ansible_virtualization_role == "guest"-ansible_os_family == "RedHat" or ansible_os_family == "Debian"
In
It’s also possible to check if some pattern is in a list:
-name:make some listset_fact:allergies:-apples-bananas-name:Test for allergiesdebug:msg:"Amatchwasfound:"when:item in allergiesloop:-pears-milk-nuts-apples
You can have multiple lists and check multiple times:
-name:make some listset_fact:fruit:-apples-bananasdairy:-milk-eggs-name:Test for allergiesdebug:msg:"Amatchwasfound:"when:-item in fruit oritem in dairyloop:-pears-milk-nuts-apples
Negate
It’s also possible to have search in a list negatively. This is where it gets difficult: (for me!)
-name:make some listset_fact:fruit:-apples-bananasdairy:-milk-eggs-name:Test for allergiesdebug:msg:"Nomatchwasfound:"when:-item not in fruit-item not in dairyloop:-pears-milk-nuts-apples
The twist here is that both conditions (and) should not be true.
Well, I’ll certainly run into some issue again in the future, hope this helps you (and me) if you ever need a complex condition in Ansible.
Relations between containernames, setup and Galaxy
It’s not easy to find the relation between container names, facts returned from setup (or gather_facts) and Ansible Galaxy platform names.
Why would you write Ansible roles for multiple distributions?
During some disucussion with the audience at DevOps Amsterdam, I got some feedback;
My statement are:
“Keep your code as simple as possible”
“Write roles for multiple distributions” (To improve logic.)
These two contradict each other: simplicity would mean 1 role for 1 (only my) distribution.
Hm, that’s a very fair point. Still I think writing for multiple operating systems is a good thing, for these reasons:
You get a better understanding of all the operating systems. For example Ubuntu is (nearly) identical to Debian, SUSE is very similar to Red Hat.
By writing for multiple distributions, the logic (in tasks/main.yml) becomes more stable.
It’s just very useful to be able to switch distributions without switching roles.
Super important Ansible facts
There are some facts that I use very frequently, they are super important to me.
This is more a therapeutic post for me, than it’s a great read to you. ;-)
Sometimes, actually most of the times, each operating system or distribution needs something specific. For example Apache httpd has different package named for mostly every distribution. This mapping (distro:packagename) can be done using this variable: ansible_os_family.
Try to select packages/service-names/directories/files/etc based on the most general level and work your way down to more specific when required. This results in a sort of priority list:
General variable, not related to a distribution. For example: postfix_package.
ansible_os_family variable, related to the type of distribution, for example: httpd_package which differs for Alpine, Archlinux, Debian, Suse and RedHat. (But is’t the same for docker images debian and ubuntu.)
ansible_distribution variable when each distribution has differences. For example: reboot_requirements. CentOS needs yum-utils, but Fedora needs dnf-utils.
ansible_distribution and ansible_distribution_major_version when there are differences per distribution release. For example firewall_packages. CentOS 6 and CentOS 7 need to have a different package.
Here is a list of containers and their ansible_os_family.
That structure would ensure that all modules required to run the role are going to be installed.
From an Ansible playbook repository
Identical to the role setup, I could imagine a requirements.yml that basically prepares the environment with all required dependencies, either roles or collections.
Loop dependencies
Ansible Collections can depend on other Ansible Collections.
Imagine my_collection’s requirements.yml:
----src:buluma.ytype:collection
The Ansible Collection y could refer to my_colletion.
my_collection ---> y
^ |
| |
+-----------+
I’m not sure how that can be resolved or prevented.
How many modules do you need?
Ansible Collections are coming. A big change in Ansible, so a stable version will likely be a good moment to go to Ansible 3. Listening to the developers, I think we can expect Ansible 3 in the spring of 2020.
Anyway, let’s get some stats:
How many modules am I using?
(Content needs verification)
That was not so difficult to estimate: 97 modules.
What ‘weird’ modules?
A bit more difficult to answer, I’ve taken two approaches:
Take the bottom of the list of “most used modules”.
Walked through the 97 modules and discover odd once.
bigip_*: I’ve written a role for a workshop.
gem: Don’t know, weird.
debug: What, get it out of there!
include_vars: Why would that be?
fail: Let’s check that later.
set_fact: I’m now a big fan of set_fact, most facts can be “rendered” in vars/main.yml.
How many ‘vendor’ modules?
I expect some Ansible Collections will be maintained by the vendors; Google (GCP), Microsoft (Azure), F5 (BigIP), yum (RedHat), etc. That’s why knowing this upfront is likely smart.
Module
Times used
Potential maintainer
pip
17
PyPi
apt
16
Canonical
yum
9
Red Hat
apt_key
6
Canonical
apt_repository
5
Canonical
rpm_key
4
Red Hat
zypper
3
SUSE
yum_repository
3
Red Hat
dnf
3
Red Hat/Fedora
zypper_repository
2
SUSE
zabbix_host
2
Zabbix
zabbix_group
2
Zabbix
apk
2
Alpine
tower_*
7 (combined)
RedHat
redhat_subscription
1
RedHat
pacman
1
ArchLinux
bigip_*
6 (combined)
F5
How often do I use what modules?
Place
Module
Times used
1
package
138
2
service
137
3
command
73
4
template
64
5
file
62
6
meta
27
7
assert
26
8
unarchive
24
9
lineinfile
21
10
copy
20
Wow, I’m especially surprised by two modules:
command - I’m going to review if there are modules that I can use instead of command. I know very well that command should be used as a last resort, not 73 times… Painful.
assert - Most roles used to see of variable met the criteria. (If a variable is defined and the type is correct.) Rather wait for role spec.
Ansible if full of modules, “batteries included” is a common expression. This reduces velocity in adding modules, fixing issues with modules or adding features to modules. Ansible Collections is there to solve this issue.
Ansible will (in a couple of releases) only be the framework, without modules or plugins. Modules will have to be installed seprarately.
There are a few unknowns:
How to manage dependencies between collections and Ansible. For example, what collections work on which Ansible version.
The documentation of Ansible is very important, but how to keep the same central point of documentation while spreading all these collections.
How to deal with colliding module names? Imaging the file module is included in more than 1 collection, which one takes precedence?
Anyway, the big take-away: Start to learn to develop or use Ansible Collections, it’s going to be important.
AWX is refactoring components to improve development velocity and the performance of the product itself.
New UI, based on React and Pattern-Fly.
tower-cli will be replaced by awx, which exposed the availabe commands based on the capabilities of the AWX API. The version of awx will be the same as the AWX web/api-tool.
Data analysis
There are a few applications to analyse data and give insights on development and usage of Ansible:
Roles analysis
Modules details
Pull requests per country
There are many more perspectives.
Next Ansible Fest not in Europe
Spain seems to be the largest contributor of Ansible, but next Ansible Fest will be in San Diego.
The Contributors Summit will be in Europe though.
Why “hardening” is not a role
I see manydeveloperswriting an Ansible role for hardening. Although these roles can absolutely be useful, here is why I think there is a better way.
Roles are (not always, but frequently) product centric. Think of role names like:
auditd
sshd
users
A role for hardening you system has the potential to cover all kinds of topics that are covered in the product specific roles.
Besides that, in my opinion a role should be:
Small
Cover on function
A good indicator of a role that’s too big is having multiple task files in tasks.
So my suggestion to not use a harden role, but rather have each role that you compose a system out of, use secure defaults.
namespace: "buluma"
name: "development_environment"
description: Install everything you need to develop Ansible roles.
version: "1.0.4"
readme: "README.md"
authors:
- "Michael Buluma"
dependencies:
license:
- "Apache-2.0"
tags:
- development
- molecule
- ara
repository: "https://github.com/buluma/ansible-development-environment"
documentation: "https://github.com/buluma/ansible-development-environment/blob/master/README.md"
homepage: "https://buluma.github.io"
issues: "https://github.com/buluma/ansible-development-environment/issues"
2. Enable Travis for the repository
Go to Travis and click Sync account. Wait a minute or so and enable the repository containing your collection.
3. Save a hidden variable in Travis
Under settings for a repository you can find Environment Variables. Add one, I called it galaxy_api_key. You’ll refer to this variable in .travis.yml later.
Bonus hint: Normally you don’t save roles, so you add something like roles/* to .gitignore, but in this case it is a part of the collection. So if you have requirements.yml, download all the roles locally using ansible-galaxy install -r roles/requirements.yml -f and include them in the commit.
Fedora 30 and above use python-3
Fedora 30 (and above) uses python 3 and starts to deprecate python 2 package like python2-dnf.
So for a while, you have to tell Ansible to use python 3. This can be done by setting the ansible_python_interpreter somewhere. Here are a few locations you could use:
1. inventory
This is quite a good location, because you could decide to give a single node this variable:
inventory/host_vars/my_host.yml:
---ansible_python_interpreter:/usr/bin/python3
Or you could group hosts and apply a variable to it:
inventory/hosts:
[python3]my_host
inventory/group_vars/python3.yml
---ansible_python_interpreter:/usr/bin/python3
2. extra vars
You could start a playbook and set the ansible_python_interpreter once:
You could save the variable in your playbook or role, but this makes re-using code more difficult; it will only work on machines with /usr/bin/python3:
----name:do somethinghosts:allvars:ansible_python_interpreter:/usr/bin/python3tasks:-name:do some taskdebug:msg:"Yes,itworks."
4. molecule
Last case I can think of it to let Molecule set ansible_python_interpreter.
molecule/default/molecule.yml:
---# Many parameters omitted.provisioner:name:ansibleinventory:group_vars:all:ansible_python_interpreter:/usr/bin/python3# More parameters omitted.
Why you should use the Ansible set_fact module
So far it seems that the Ansible set_fact module is not required very often. I found 2 cases in the roles I write:
-name:find version of zabbix-server-mysqlset_fact:zabbix_server_version:""
In both cases a “complex” variable strucure is saved into a simpler to call variable name.
Variables that are constructed of other variables can be set in vars/main.yml. For example the kernel role needs a version of the kernel in defaults/main.yml:
So sometimes set_fact can be used to keep code simple, other (most) times vars/main.yml can help.
For a moral compass Southpark uses Brian Boitano, where my moral coding compass uses Jeff Geerling who would say something like: “If your code is complex, it’s probably not good.”
Different methods to include roles
There are several ways to include roles from playbooks or roles.
and the variable are set in [vars/main.yml](https://github.com/buluma/ansible-role-artifactory/blob/master/vars/main.yml) contains:
service_list:-name:artifactorydescription:Start script for Artifactorystart_command:"/bin/artifactory.shstart"stop_command:"/bin/artifactory.shstop"type:forkingstatus_pattern:artifactory
How to write and maintain many Ansible roles
It’s great to have many code nuggets around to help you setup an environment rapidly. Ansible roles are perfect to describe what you want to do on systems.
As soon as you start to write more roles, you start to develop a style and way of working. Here are the tings I’ve learned managing many roles.
Use a skeleton for stating a new role
When you start to write a new role, you can start with pre-populated code:
ansible-galaxy is a command. This may change to molecule in the future.
init tells ansible-galaxy to initialize a new role.
--role-skeleton=ansible-role-skeleton refers to a skeleton ansible role. I use his repository.
role_name is the name of your new role. I use short names here, like nginx or postfix.
Use ansible-lint for quick feedback
Andrew has written a tool including many rules that help you write readable and consistent code.
There are times where I don’t agree to the rules, but the feedback is quickly processed.A
There are also times where I initially think rules are useless, but after a while I’m convinced about the intent and change my code.
You can also describe your preferences and use ansible-lint to verify you code. Great for teams that need to agree on a style.
Use molecule on Travis to test
In my opinion the most important part of writing code is testing. I spend a lot of time on writing and executing tests. It helps yourself to prove that certain scenarios work as intended.
Travis can help test your software. A typical commit takes some 30 to 45 minutes to test, but after that I know:
When I write some new functionality, I typically need a few iterations to make it work. Using GitHub releases helps me to capture (and release) a working version of a role.
You can play as much as you want in between releases, but when a release is done, the role should work.
Go forth and develop!
You can setup a machine yourself for developing Ansible roles. I’ve prepared a repository that may help.
----name:setup an ansible development environmenthosts:allbecome:yesgather_facts:noroles:-buluma.bootstrap-buluma.update-buluma.fail2ban-buluma.openssh-buluma.digitalocean_agent-buluma.common-buluma.users-buluma.postfix-buluma.docker-buluma.investigate-buluma.ansible-buluma.ansible_lint-buluma.buildtools-buluma.molecule-buluma.ara-buluma.ruby-buluma.travistasks:-name:copy private keycopy:src:id_rsadest:/home/robertdb/.ssh/id_rsamode:"0400"owner:robertdbgroup:robertdb-name:copy git configurationcopy:src:gitconfigdest:/home/robertdb/.gitconfig-name:create repository_destinationfile:path:""state:directoryowner:robertdbgroup:robertdb-name:clone all rolesgit:repo:"/.git"dest:"/"accept_hostkey:yeskey_file:/home/robertdb/.ssh/id_rsawith_items:""become_user:robertdb
When is a role a role
Sometimes it’s not easy to see when Ansible code should be captured in an Ansible role, or when tasks can be used.
Here are some guidelines that help me decide when to choose for writing an Ansible role:
Don’t repeat yourself
When you start to see that your repeating blocks of code, it’s probably time to move those tasks into an Ansible role.
Whenever I open up somebody else’ Ansible role and the code keeps on scrolling, I tend to get demotivated:
Where can you find the error/issue/bug?
How can this be maintained?
There is probaly no easy way to test this.
The code does many things and misses focus.
Cleanup your playbook
Another reason to put code in Ansible roles, is to keep your playbook easy to read. A long list of tasks is harder to read than a list of roles.
Take a look at this example:
-name:build the backend serverhosts:backendbecome:yesgather_facts:noroles:-buluma.bootstrap-buluma.update-buluma.common-buluma.python_pip-buluma.php-buluma.mysql-buluma.phpmyadmin
This code is simple to read, anybody could have an understanding what it does.
When input is required
Some roles can have variables to change the installation, imagine this set of variables:
The Ansible module dnf tries to install python2-dnf if it running on a python2 environment. It took me quite some time to figure out why this error appeared:
fatal: [bootstrap-fedora-rawhide]: FAILED! => {"attempts": 10, "changed": true, "msg": "non-zero return code", "rc": 1, "stderr": "Error: Unable to find a match\n", "stderr_lines": ["Error: Unable to find a match"], "stdout": "Last metadata expiration check: 0:01:33 ago on Thu Nov 29 20:16:32 2018.\nNo match for argument: python2-dnf\n", "stdout_lines": ["Last metadata expiration check: 0:01:33 ago on Thu Nov 29 20:16:32 2018.", "No match for argument: python2-dnf"]}
(I was not trying to install python2-dnf, so confusion…)
Hm; so I’ve tried these options to work around the problem:
Use alternatives to set /usr/bin/python to /usr/bin/python3. Does not work, the Ansible module dnf will still try to install python2-dnf.
Set ansible_python_interpreter for Fedora-30 hosts. Does not work, my bootstrap role does not have any facts, it does not know about ansible_distribution (Fedora), nor ansible_distribution_major_version (30).
so far the only reasonable option is to set ansible_python_interpreter as documented by Ansible.
TL;DR On Fedora 30 (and higher) you have to set ansible_python_interpreter to /usr/bin/python3.
Ansible Molecule testing on EC2
Molecule is great to test Ansible roles, but testing locally with has it’s limitations:
Docker - Not everything is possible in Docker, like starting services, rebooting of working with block devices.
Vagrant - Nearly everything is possible, but it’s resource intensive, making testing slow.
I use my bus-ride time to develop Ansible Roles and the internet connection is limited, which means a lot of waiting. Using AWS EC2 would solve a lot of problems for me.
Here is how to add an EC2 scenario to an existing role.
Save AWS credentials
Edit ~/.aws/credentials using information downloaded from [AWS Console].
It feels as if the ec2 driver has had a little less attention as for example the vagrant or docker driver. Here are some strange things:
The region needs to be set using an environment variable, the credentials from a file. This may be my mistake, but now it’s a little strange. It would feel more logical to add region: to the platform section.
The vpc_subnet_id should be found by the user and put into molecule.yml.
Molecule and ARA
To test playbooks, molecule is really great. And since Ansible Fest 2018 (Austin, Texas) clearly communicated that Molecule will be a part of Ansible, I guess it’s safe to say that retr0h’s tool will be here to stay.
When testing, it’s even nicer to have great reports. That’s where ARA comes in. ARA collects job output as a callback_plugin, saves it and is able to display it.
Now point your browser to http://localhost:9191/ and run a molecule test:
molecule test
[204] Lines should be no longer than 120 chars
It seems Galaxy is going to use galaxy-lint-rules to star roles.
One of the controls tests the length of the lines. Here are a few way so pass those rules.
Spread over lines
In YAML you can use multi line to spread long lines.
Without new lines
The > character replaces newlines by spaces.
- name: demostrate something
debug:
msg: >
This will just
be a single long
line.
With new lines
The | character keeps newlines.
- name: demostrate something
debug:
msg: |
The following lines
will be spread over
multiple lines.
Move long lines to vars
Sometimes variables can get very long. You can save a longer variable in a shorter one.
For example, too long would be this task in main.yml:
Yes it’s annoying to have a limitation like this, but it does make the code more readable and it’s not difficult to change your roles to get 5 stars.
Ansible roles for clusters
Ansible can be used to configure clusters. It’s actually quite easy!
Typically a cluster has some master/primary/active node, where stuff needs to be done and other stuff needs to be done on the rest of the nodes.
Ansible can use run_once: yes on a task, which “automatically” selects a primary node. Take this example:
inventory:
host1
host2
host3
tasks/main.yml:
- name: do something on all nodes
package:
name: screen
state: present
- name: select the master/primary/active node
set_fact:
master: ""
run_once: yes
- name: do something to the master only
command: id
when:
- inventory_hostname == master
- name: do something on the rest of the nodes
command: id
when:
- inventory_hostname != master
It’s a simple and understandable solution. You can even tell Ansible that you would like to pin a master:
- name: select the master/primary/active node
set_fact:
master: ""
run_once: yes
when:
- master is not defined
In the example above, if you set “master” somewhere, a user can choose to set a master instead of “random” selection.
Hope it helps you!
Ansible Galaxy Lint
Galaxy currently is a dumping place for Ansible roles, anybody can submit any quality role there and it’s kept indefinitely.