My first experience with Ansible
Images by Christina Morillo from pexels.com |
Recently, I did some Ansible scripting, and there are some initial struggles to develop good Ansible scripts.
Firstly, Ansible scripts are implemented in YAML format. Hence these scripts can be unmaintainable very quickly if
- We do not document each step;
- We do not recreate reusable module libraries and/or ansible role. That's we copy and paste code
- We do not maintain small Ansible .yml files.
Next, I found that list and object manipulation in Ansible are not natural to me. Here are some of the things that I have learned.
1. Combining dictionaries
I need to set default values to an object (dictionary) so that I can simplify my implementation. Here is the demo script.
--- - name: hosts: localhost vars: data: { "config1": true, "config2": { "enabled": true } } tasks: - name: create default values set_fact: default_vals: { "config1": false, "config2": {}, "dataset": [], "extra": "hello-world" } - name: combine data and default values set_fact: data: "{{ default_vals | combine(data) }}"
and the result is
ok: [localhost] => { "ansible_facts": { "data": { "config1": true, "config2": { "enabled": true }, "dataset": [], "extra": "hello-world" } }, "changed": false }
the missing attributes, dataset
, and extra
are added accordingly.
2. Filtering and Mapping
These are very common operations on a list of objects (dictionaries).
--- - name: hosts: localhost vars: data: [{ "hostname": "example.com", "ip_address": "10.51.1.4" }, { "hostname": "sample.com", "ip_address": "10.52.1.4" }, { "hostname": "helloworld.net", "ip_address": "10.51.1.51" }] tasks: - name: filter on data set_fact: filtered_data: "{{ data | selectattr('hostname', 'search', '\\.com$') \ | map(attribute='ip_address') | list }}"
Here, we filter by hostname ending with .com
and get the ip_address
of filtered list.
ok: [localhost] => { "ansible_facts": { "filtered_data": [ "10.51.1.4", "10.52.1.4" ] }
3. Filtering (in list)
The other common operation is to filter objects when attribute of object is in a list.
--- - name: hosts: localhost vars: data: [{ "hostname": "example.com", "ip_address": "10.51.1.4" }, { "hostname": "sample.com", "ip_address": "10.52.1.4" }, { "hostname": "helloworld.com", "ip_address": "10.51.1.5" }] selected_ip_addrs: [ "10.51.1.4", "10.51.1.5" ] tasks: - name: filter on data set_fact: filtered_data: "{{ data | selectattr(\ 'ip_address', 'in', selected_ip_addrs) | list }}"
Here, we filter by ip_address
in selected_ip_addrs
list.
ok: [localhost] => { "ansible_facts": { "filtered_data": [ { "hostname": "example.com", "ip_address": "10.51.1.4" }, { "hostname": "helloworld.com", "ip_address": "10.51.1.5" } ] }, "changed": false }
4. Concatenating List
In Ansible, it is very easy to concatenate list and get unique values.
--- - name: hosts: localhost vars: data1: ["abc", "xyz"] data2: ["123", "abc"] tasks: - name: concatentate list set_fact: data: "{{ (data1 + data2) | unique }}"
The duplicate value here is abc
.
ok: [localhost] => { "ansible_facts": { "data": [ "abc", "xyz", "123" ] }, "changed": false }
5. Split string by delimiter
I have also encountered situations where I need to split strings. Here is just a simple example.
--- - name: hosts: localhost vars: ip_address: "10.51.1.5" tasks: - name: split ip_address set_fact: parts: "{{ ip_address.split('.') }}" - name: get parts set_fact: result: "{{ parts[0] + '.' + parts[1] }}"
Here, we split IP Address into parts and get only the first and second parts.
ok: [localhost] => { "ansible_facts": { "result": "10.51" }, "changed": false }
6. Loops
Looping through items in list is also also common operation.
--- - name: hosts: localhost vars: input: [ { name: "hello", value" "world" }, { name: "welcome", value" "world" }, { name: "hi", value" "world" }, { name: "hello", value" "world 2" }, ] tasks: - name: concatentate list with_items: "{{ input }}" set_fact: data: "{{ ((data | default([])) + [item.name]) | unique }}" - name: print result debug: msg: "{{ data }}"
Here we use default() to set the initial value for data, and then append name of each item to it.
ok: [localhost] => { "msg": [ "hello", "welcome", "hi" ] }
My Observations
After working with Ansible for about 6 weeks here are my observations
- I found myself duplicating Ansible script initially because I do not fully understand the concept of library and roles. After sometime, I started to remove duplicated code and move them to roles or libraries to facilitate code reuse.
- I could not understand how set_fact works. There are times when I could not figure out why facts did not in different hosts. After some readings, I figured that facts are tied to hostname. That's a fact that is set in one hostname is not visible to another hostname. Reference:
Variables are set on a host-by-host basis just like facts discovered by the setup module.
- It is easier to test how things work by creating a small Ansible yml; and run it separately. This helps me to isolate other things and focus on the pieces that I am interested in.
- I recommend that you pause development after 3-4 weeks, and set away time to refactor your Ansible scripts. This gives you the opportunity to create reusable roles/libraries, and also improve your scripts.
- Use linters for static code checking.
- yamllint
- ansible-playbook <your.xml> --syntax-check
- ansible-lint <your.xml>
Comments
Post a Comment