Whitespace Control with Jinja Templating in Ansible <= 2.2

The Situation

Using Ansible 2.2 or older, I need to create a bash script using the template module[1] that will modify a CentOS image. Specifically, I need to conditionally create a new user on the image before injecting ssh keys if we will not be using the root user on that image. This condition will be determined by the value associated with a supplemental_user variable.

In the following examples, assume that supplemental_user is stack.

Template One

virt-customize -a ~{{ supplemental_user }}/ipa.qcow2 \
    --root-password password:test1234 \
    --install openssh-server \
    --run-command "xfs_growfs /" \
    --run-command "echo 'GRUB_CMDLINE_LINUX=\"console=tty0 crashkernel=auto no_timer_check net.ifnames=0 console=ttyS0,115200n8\"' >> /etc/default/grub" \
    --run-command "grubby --update-kernel=ALL --args=net.ifnames=0" \
    --run-command "systemctl enable sshd" \
    {% if supplemental_user != 'root' %}--run-command "useradd {{ supplemental_user }} -m -p ''" \{% endif %}  # <-- Simple, right?
    --mkdir /root/.ssh \
    --copy-in ifcfg-eth0:/etc/sysconfig/network-scripts/ \
    --copy-in ifcfg-eth1:/etc/sysconfig/network-scripts/ \
    --ssh-inject {{ supplemental_user }}:file:$VMSSHKEY \
    --selinux-relabel

Output From Template One:

virt-customize -a ~stack/ipa.qcow2 \
    --root-password password:test1234 \
    --install openssh-server \
    --run-command "xfs_growfs /" \
    --run-command "echo 'GRUB_CMDLINE_LINUX=\"console=tty0 crashkernel=auto no_timer_check net.ifnames=0 console=ttyS0,115200n8\"' >> /etc/default/grub" \
    --run-command "grubby --update-kernel=ALL --args=net.ifnames=0" \
    --run-command "systemctl enable sshd" \
    --run-command "useradd stack -m -p ''" \    --mkdir /root/.ssh \
    --copy-in ifcfg-eth0:/etc/sysconfig/network-scripts/ \
    --copy-in ifcfg-eth1:/etc/sysconfig/network-scripts/ \
    --ssh-inject stack:file:$VMSSHKEY \
    --selinux-relabel

The Problem:

As you may have noticed above, the newline trailing our conditional is being absorbed when the template is rendered. A quick review of Jinja template docs[1]  tells us that this is expected behavior, ‘If an application configures Jinja to trim_blocks, the first newline after a template tag is removed automatically (like in PHP).’

Template Two

The trim_blocks issue is easily resolved with Jinja by prepending the conditinoal, ‘You can manually disable the lstrip_blocks behavior by putting a plus sign (+) at the start of a block.’

virt-customize -a ~{{ supplemental_user }}/ipa.qcow2 \
    --root-password password:test1234 \
    --install openssh-server \
    --run-command "xfs_growfs /" \
    --run-command "echo 'GRUB_CMDLINE_LINUX=\"console=tty0 crashkernel=auto no_timer_check net.ifnames=0 console=ttyS0,115200n8\"' >> /etc/default/grub" \
    --run-command "grubby --update-kernel=ALL --args=net.ifnames=0" \
    --run-command "systemctl enable sshd" \
    {%+ if supplemental_user != 'root' %}--run-command "useradd {{ supplemental_user }} -m -p ''" \{% endif %}  # <-- Note the "{%+ .."
    --mkdir /root/.ssh \
    --copy-in ifcfg-eth0:/etc/sysconfig/network-scripts/ \
    --copy-in ifcfg-eth1:/etc/sysconfig/network-scripts/ \
    --ssh-inject {{ supplemental_user }}:file:$VMSSHKEY \
    --selinux-relabel

Output From Template Two:

FAILED! => {"changed": false, "failed": true, "msg": "AnsibleError: template error while templating string: tag name expected. String: <snip>

The Next Problem:

This is unexpected, we followed Jinja’s docs, right? A quick look at the docs for Ansible’s template module[2] tell us that we need to set the `trim_blocks` parameter to false when calling the template module. Unfortunately, this feature was added in Ansible 2.3.

Template Three

What do you do if your library is pinned to an earlier release of Ansible? You use an expression to interpret python directly. However if you have lots of special characters, like we do here in this complicated bash template, you will have to be very careful when you form your expression.

# NOTE(hrybacki): The ugly is formed as such because Ansible <2.3 lacks the ability to control
#                 whitespace trimming in Jinja templates. This results in the inability to form
#                 a proper newline. http://docs.ansible.com/ansible/template_module.html#options
virt-customize -a ~{{ supplemental_user }}/ipa.qcow2 \
    --root-password password:test1234 \
    --install openssh-server \
    --run-command "xfs_growfs /" \
    --run-command "echo 'GRUB_CMDLINE_LINUX=\"console=tty0 crashkernel=auto no_timer_check net.ifnames=0 console=ttyS0,115200n8\"' >> /etc/default/grub" \
    --run-command "grubby --update-kernel=ALL --args=net.ifnames=0" \
    --run-command "systemctl enable sshd" \
    {{ "--run-command \"useradd " + supplemental_user + " -m -p ''\" \\" if supplemental_user != 'root' else "\\" }}
    --mkdir /root/.ssh \
    --copy-in ifcfg-eth0:/etc/sysconfig/network-scripts/ \
    --copy-in ifcfg-eth1:/etc/sysconfig/network-scripts/ \
    --ssh-inject {{ supplemental_user }}:file:$VMSSHKEY \
    --selinux-relabel

Output From Template Three:

# NOTE(hrybacki): The odd conditional is formed as such because ansible 2.2 does not support
#                 trim_blocks option for Jinja templates preventing us from being able to
#                 form a proper newline.
virt-customize -a ~stack/ipa.qcow2 \
    --root-password password:test1234 \
    --install openssh-server \
    --run-command "xfs_growfs /" \
    --run-command "echo 'GRUB_CMDLINE_LINUX=\"console=tty0 crashkernel=auto no_timer_check net.ifnames=0 console=ttyS0,115200n8\"' >> /etc/default/grub" \
    --run-command "grubby --update-kernel=ALL --args=net.ifnames=0" \
    --run-command "systemctl enable sshd" \
    --run-command "useradd stack -m -p ''" \
    --mkdir /root/.ssh \
    --copy-in ifcfg-eth0:/etc/sysconfig/network-scripts/ \
    --copy-in ifcfg-eth1:/etc/sysconfig/network-scripts/ \
    --ssh-inject stack:file:$VMSSHKEY \
    --selinux-relabel

Relevant Links:

[1] – http://jinja.pocoo.org/docs/2.9/templates/#whitespace-control
[2] – http://docs.ansible.com/ansible/template_module.html#options

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: