For manually creating containers on a specific host I wrote an Ansible playbook prompting the user for some container information when the playbook is run.
Here you can see a snippet from it, prompting for the container_name and container_distro input:
vars_prompt:
- name: "container_name"
private: no
prompt: "Enter name of container"
- name: "container_distro"
private: no
prompt: "Which Linux distro to use? default: debian"
The user has the option to enter the Linux distribution to use. If the user leaves that option empty (which results in an empty string), the value for container_distro should be set to "debian". There are several ways how to achieve this.
Note: I added this way one day after I initially wrote the article. I got that hint after sharing my blog post on Mastodon. Interestingly this is by far the easiest and fastest method. I can't believe I missed that from the documentation.
The vars_prompt also allow to set a default value, in case the user does not enter a value:
vars_prompt:
- name: "container_name"
private: no
prompt: "Enter name of container"
- name: "container_distro"
private: no
prompt: "Which Linux distro to use? default: debian"
default: debian
This is obviously the most straight forward and fastest solution.
To verify this works, I added a debug output right below it:
- name: Debug - show settings
debug:
msg: "Container Name: {{ container_name }} /// Distro: {{ container_distro }}"
When running the playbook this results in:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: rafiki
Which Linux distro to use? default: debian [debian]: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: rafiki /// Distro: debian"
}
The disadvantage with this method is that using inventory variables as default value does not seem to work. For example if you want to set a default value retrieved from the hostvars, you will run into an error:
vars_prompt:
- name: "container_name"
private: no
prompt: "Enter name of container"
- name: "container_distro"
private: no
prompt: "Which Linux distro to use? default: debian"
default: "{{ hostvars[container_name]['os']['distro'] }}"
... the playbook will return an error:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
ERROR! 'hostvars' is undefined
If I understand correctly this is because the playbook runs vars_prompt before gathering facts.
A set_fact can be used in combination with a when condition:
- name: FACT SET - Default container_distro
set_fact:
container_distro: "debian"
when: container_distro is defined and container_distro == ""
The set_fact task is executed if the condition is matched. The condition in this situation checks whether the variable "container_distro" is defined (as this variable is part of the prompt, it is defined) and is an empty string. If the user does not input anything in the prompt, the variable's value will be an empty string ("").
And by running the playbook, the default can be verified:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: simba
Which Linux distro to use? default: debian: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: simba /// Distro: debian"
}
As an alternative I was also looking at using the jinja2 default() function instead of using a when condition:
- name: FACT SET - Default container_distro
set_fact:
container_distro: "{{ container_distro | default('debian') }}"
However this did not work as container_distro is a defined variable and the default() function would only work if the container_distro variable would not exist.
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: nala
Which Linux distro to use? default: debian: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: nala /// Distro: "
}
To proof that I used a non-existant variable name "distro":
- name: FACT SET - Default container_distro
set_fact:
container_distro: "{{ distro | default('debian') }}"
And in this case the default() function is used:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: pumbaa
Which Linux distro to use? default: debian: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: pumbaa /// Distro: debian"
}
As seen above, the problem is that Ansible considers the empty string "" as a valid value. However we can also use jinja2 if/else conditions inside a playbook.
The following set_fact task uses such a condition.
- name: FACT SET - Default container_distro
set_fact:
container_distro: "{% if not container_distro %}debian{%else %}{{ container_distro }}{% endif %}"
Note the difference of curly brackets:
Let's try this:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: mufasa
Which Linux distro to use? default: debian: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: mufasa /// Distro: debian"
}
It worked! Interestingly the jinja2 if condition handles the empty string differently than Ansible's when condition; the variable is considered empty and therefore not-existant. Meaning:
Jinja2 if condition != Ansible when condition
{% if not container_distro %} != when: container_distro is defined
With the knowledge from above's example (jinja2 if condition), is there even a need to use a set_fact task? Let's put it to the test and completely remove the set_fact task from the playbook and instead use the jinja2 if condition when the variable is used in the task:
- name: Debug - show settings
debug:
msg: "Container Name: {{ container_name }} /// Distro: {% if not container_distro %}debian{% else %}{{ container_distro }}{% endif %}"
The debug task was adjusted accordingly and now contains the jinja2 if condition. Running the playbook reveals:
ck@ansible:~$ ansible-playbook /tmp/containersetup.yaml
Enter name of container: scar
Which Linux distro to use? default: debian: [Enter]
TASK [Debug - show settings] ****************************************
ok: [target] => {
"msg": "Container Name: scar /// Distro: debian"
}
This works, too! Awesome!
While working on this playbook, I learned how to use jinja2 conditions directly in the playbook. In the past I've always dealt with set_fact and when conditions - but now I'd write a lot of playbooks differently.
The Jinja2 live parser was a great help to test conditions and I can greatly recommend it before wasting time running a playbook and potentially run into a syntax error.
Here two screenshots of the same logic I used in the playbook.
The first example shows an empty string for the container_distro variable:
And in the second example a value "ubuntu" was set:
No comments yet.
AWS Android Ansible Apache Apple Atlassian BSD Backup Bash Bluecoat CMS Chef Cloud Coding Consul Containers CouchDB DB DNS Database Databases Docker ELK Elasticsearch Filebeat FreeBSD Galera Git GlusterFS Grafana Graphics HAProxy HTML Hacks Hardware Icinga Influx Internet Java KVM Kibana Kodi Kubernetes LVM LXC Linux Logstash Mac Macintosh Mail MariaDB Minio MongoDB Monitoring Multimedia MySQL NFS Nagios Network Nginx OSSEC OTRS Office OpenSearch PGSQL PHP Perl Personal PostgreSQL Postgres PowerDNS Proxmox Proxy Python Rancher Rant Redis Roundcube SSL Samba Seafile Security Shell SmartOS Solaris Surveillance Systemd TLS Tomcat Ubuntu Unix VMWare VMware Varnish Virtualization Windows Wireless Wordpress Wyse ZFS Zoneminder