Short article description
Short description of where I got the book from and how I got interested in it, as well as when I read it. Reference other book reviews if applicable.
- Explaining the current state / problem we are going to solve
- What is ansible anyway?
- Installing ansible
- Solving Real Problems with ansible
- Parameters for Tasks
- Conclusion
Explaining the current state / problem we are going to solve
My main operating system is NixOS. Both my primary laptop as well as my desktop
computer run on NixOS. With nix, deterministic configuration management is
easy. Then I have my working computer, and a couple servers. These are all on
different versions of ubuntu. I could explain how I got to NixOS, but that’s
for another time.
I’d like to have well synced configuration files, between the two laptops and the desktop computer. It was a git repository collecting files and references for the longest time, manually copying some new version and making local adjustments (do not show a battery or wifi in the status bar at the desktop).
Setting up a new computer and configuring the configs did not take much time, and the steps were obvious, but it still regularly took half an hour until my workflows worked again (Oh, why is that script failing? Ah, there is a symlink missing here. Ah, and it’s not in the PATH yet).
Sound familiar? Let’s get down to business.
What is ansible anyway?
Ansible is going to magically solve all our problems. At least I thought so when hearing about it. So what is ansible? Wikipedia:
Ansible is an open-source software provisioning, configuration management, and application-deployment tool. It runs on many Unix-like systems, and can configure both Unix-like systems as well as Microsoft Windows. It includes its own declarative language to describe system configuration.
Even though it’s certainly not wrong, it does not feel exactly ‘right’ either. Let’s try the top answer from quora to ‘What is ansible?’:
Ansible is an open source IT configuration management (CM) and automation platform, provided by Red Hat. It uses human-readable YAML templates so that users can program repetitive tasks to occur automatically, without learning an advanced language.
In both answers, ‘configuration management’ was one of the first descriptions, around automation platform and software provisioning, whatever that is. Instead of trying to look at definitions and words, let’s take a look at what ansible can actually do.
Installing ansible
For nixos, it’s simply the ansible package. For those without nixos, you
might want to install it with pip (I suggest python3 in a virtual environment).
Again, the package itself is called ansible.
After installing the package, verify installation with ansible --version. The
next steps will be to actually solve some basic problems of dotfile deployment.
I’m going to push you head first into the cold water, so brace for impact. Don’t worry, I’ll explain what’s going on.
Solving Real Problems with ansible
Copying files
Let’s say you have your repo with your configs on your new machine already. Some files need to be placed at certain places. How could we do this? The following ansible playbook should give you an idea:
--- # file: main.yml
- hosts: locahost
connection: local
tasks:
- name: Move File from A to B
template:
src: /path/to/A
dest: /path/to/B
# - name: Next task
# actually no, this is just a comment.
We only want to run the following tasks on localhost, this is unusual for ansible. Also we want the connection to be local, since otherwise ansible would try to connect with ssh which would most likely fail.
The task is quite simple: take a file from path A, and copy it to path B. You
might be wondering why it is called template then, this is because template
can do so much more than just that, we’ll get to that in a minute.
So how to execute this thing? It’s simple, really: ansible-playbook main.yml.
Idempotence
When you run this playbook for the first time, you will see something like
changed: [localhost] after execution. Every time you run it after that
(without removing or modifying the file at src or dest), you will see
ok: [localhost] instead. This means, that the task did not do anything in
this run.
This is important, because it means that if some later step fails, you can modify and run it again without worrying about the earlier steps interfering in any way, since they notice that nothing needs to be done for them, so they won’t do anything. Repeated execution without the fear of accidentially destroying something by an earlier step.
Linking files and folders
So let’s say you have deployed the configuration files for ~/.config/sth, but
you’d like to make changes to them just once and see the effects without having
to re-run the ansible playbook. Which is to say, you’d like to create a symlink
to your configuration file, instead of copying it. Nothing easier than that:
# part of main.yml
- name: Link File A to B
file:
src: /path/to/A
dest: /path/to/B
state: link
Symlinking folders works the same way, just make A a folder instead of a file.
A hardlink is also possible by setting state: hard.
More information on the file module can be found here.
Installing Packages with APT
Assuming you are a fan of viewing stars, and your favourite piece of software
is stellarium. When being on a ubuntu-based distribution, you would install
it with sudo apt install stellarium. Let’s include that in our script.
# part of main.yml, v1
- name: Install stellarium
shell: sudo apt install stellarium
This is a bad idea on multiple counts, so let’s dissect it. When trying to execute it, it is unlikely to terminate. Unless you don’t need to put in a password when accessing root priviliges. So using the shell is not a good idea, especially if you need to input some additional data. Actually, there is an apt module.
# part of main.yml, v2
- name: Install stellarium
apt:
name: stellarium
That’s it? Not yet, I’m afraid.If you try, it will fail because a normal user cannot install packages. There is two ways to deal with it, ignoring it, or escalating to root priviliges. How would I ignore it?
All available parameters for the apt module can be found here.
Parameters for Tasks
You see, the general syntax for a task looks like this:
- name: Optional name
mod: # actual task to execute
par: parameter for task
modifier: modify execution of the task in a certain way
Both ‘ignoring’ as well as ‘privilige escalation’ are modifiers applicable to every task you might encounter. Though you might to avoid using them.
Ignoring failure
Ignoring an actually failing task is considerably easy, you only need to add
ignore_errors: true to the module. So the fully non-working but also
error-ignoring installation of stellarium using apt looks like this:
# part of main.yml, v3
- name: Install stellarium
apt:
name: stellarium
ignore_errors: true
‘But that’s barely useful’ you say. While this is true for this scenario, we are going to need it later for cloning / updating the configs-repository, since it fails if the repository is already present but with local modification (due to which the module was unable to ‘simply’ update, thus failing).
Privilige escalation
This is a bit more tricky, since we actually need to slightly change the way we
call the playbook (it needs to request the sudo password, after all).
Privilige escalation in ansible (to sudo/root) works in most cases with a
simple become: true.
# part of main.yml, v3
- name: Install stellarium
apt:
name: stellarium
become: true
However, this will fail if privilige escalation requires a password on your
computer (as it should!). This can be mitigated by calling
ansible-playbook main.yml -K instead of the previous without -K. This will
prompt you for a password that will be tried whenever a privilige escalation
is supposed to happen and a password is required.
Note: -K also asks for a password even if no sudo rights are required
for executing the playbook, and will notice a wrong password only when ‘using’
it.
More detailed information on privilige escalation can be found here.
This now finally works! But what if I don’t just have an ubuntu, but a
NixOS/CentOS/Arch/… as well, where apt is not available?
Conditionals
So we might want to ensure that our task is only run when apt is actually
available. Loosely speaking, this restricts us to debian and ubuntu.
# part of main.yml, v4
- name: Install stellarium
apt:
name: stellarium
become: true
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
Whoa, what does when do? And where does ansible_distribution come from? As
we might guess, when is a modifier ensuring that this task is only executed
on debian or ubuntu, where apt is bound to be available.
ansible_distribution is one of the ansible facts that are collected even
prior to task execution.
You can see all available facts with the setup module, as described
here, by running ansible -m setup localhost. You might want to add
--connection local, since ansible will try to connect via ssh otherwise.
In a playbook, we can build a task to do the same:
# part of main.yml
- debug: var=ansible_facts
Yeah, that’s it. Based on these, based on the environment of the target for the ansible playbook, we can decide to execute some tasks instead of others. Conditionals become really useful when conditioning based on Variables that receive values based on the success of a prior task
Variables
(mainly registering)
Handlers
Iterating multiple items
Including other files
Customizing configuration files
Slowly figuring out what to do, step by step. Including the stepwise ‘escalation’ to the more formally correct methods.