Category Archives: Ansible

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

 

Standing up OpenStack with FreeIPA and TLS Everywhere using TripleO-Quickstart

UPDATE: 24-May-2017

The three reviews[3] mentioned in the original post have merged and this feature is now part of OOOQ/OOOQ-E. I have updated the deployment script[10] on GitHub for your convenience.

Original Post:

Over the past month, I have been working with TripleO-Quickstart (OOOQ)[1] and TripleO-Quickstart-Extras (OOOQ-E)[2] to automate the deployment of a FreeIPA server and enable TLS Everywhere. There three reviews[3] on review.openstack.org that comprise the majority of my work. All of this is coming to a head after many, many months of work from my teammates Ozz[4], Rob[5], and Ade[6] who put together all the leg work in relevant libraries and communities. Without their effort and headaches, this wouldn’t be possible.

Automating this process is a big deal. Once this it is streamlined this will allow for greatly expanding the security and compliance coverage throughout OpenStack CI. Needless to say, I am pretty excited to be working on this topic. Without any further ado, here is the present workflow to conduct a deployment:

Assumptions:

  1. One machine is set up[7] as the controller from which you will call OOOQ to conduct the deployment. I am deploying from a ThinkPad T450S running Fedora 24.
  2. Once machine, referred to as the virthost, 1 quad core CPU, 24GB of memory, and 160 GB of free space. Running with fewer resources is possible but not tested, and will likely result in difficult errors difficult to diagnose.

Preparing Our Environment To deploy:

The first thing we need to do is pull down the OOOQ and OOOQ-E repositories.

cd ~/git

git clone https://github.com/openstack/tripleo-quickstart

git clone https://github.com/openstack/tripleo-quickstart-extras

Next we have to pull down the two reviews required to run the code that hasn’t been merged yet.

cd ~/git/tripleo-quickstart

git fetch https://git.openstack.org/openstack/tripleo-quickstart refs/changes/23/453223/17 && git checkout FETCH_HEAD

cd ~/git/tripleo-quickstart-extras

git fetch https://git.openstack.org/openstack/tripleo-quickstart-extras refs/changes/98/436198/18 && git checkout FETCH_HEAD

In order to ensure that we use our local version of OOOQ-E, we must modify tripleo-quickstart/quickstart-extras-requirements.txt to the repo on our file system.

cd ~/git/tripleo-quickstart

sed -e ‘s/.*#/file:\/\/\/~\/git\/tripleo-quickstart-extras\/#/’ quickstart-extras-requirements.txt

Now, let’s go ahead and create a special working directory to house our deployment data, artifacts, and keep our modified repos clean in the process.

export WORKING_DIR=~/.quickstart-freeipa

mkdir $WORKING_DIR

cp -rf ~/git/tripleo-quickstart $WORKING_DIR

At this point we are ready to deploy. The only thing we have to do is ensure that we can ssh into our virthost from the controller as root. This is a requirement for Ansible.

Deployment:

cd ~/git/tripleo-quickstart

export VIRTHOST=<your virthost’s IP or FQDN>

bash quickstart.sh \

–bootstrap \

–ansible-debug \

–no-clone \

–playbook quickstart-extras.yml \

–working-dir $WORKING_DIR \

–release master \

–config $WORKING_DIR/config/general_config/ipa.yml \

–nodes $WORKING_DIR/config/nodes/1ctlr_1comp_1supp.yml \

–tags “all” \

$VIRTHOST

After calling quickstart.sh, you will see OOOQ take over and begin installing all the necessary libraries for deployment. After completing it’s environment preparations, it will call the Ansible playbook, quickstart-extras.yml[7]. Pay close attention to the two config files we are explicitly using, ipa.yml[8] and 1ctlr_1comp_1supp.yml[9]. These two configuration files specify how OOOQ deploys a single compute, control, and supplemental node (housing the FreeIPA server), as well as sets all of the various flags needed for the FreeIPA server and enabling TLS Everywhere.

The deployment itself should take around two hours to complete. While we are adding some additional time fetching the Centos image, provisioning the supplemental node, and deploying the FreeIPA server itself — the majority of the deployment time still lands on installing the undercloud and deploying the overcloud.

After deployment has completed — you have full access to the undercloud:

ssh -F $WORKING_DIR/ssh.config.ansible undercloud

as well as the supplemental node which is running the FreeIPA server:

ssh -F $WORKING_DIR/ssh.config.ansible supplemental

Please note that a log of the FreeIPA server deployment can be located on the supplemental node at ~/deploy_freeipa.log. The IPA server admin password can be located in either the Ansible logs (look for freeipa_admin_password) or in the deployment script ~/deploy_freeipa.sh (look for CA_ADMIN_PASSWORD).

Afterthoughts:

Once the reviews[3] have merged, this process will be come much simpler. In the meantime, I have a created a simple bash script[10] which automates all of the above steps requiring only a single parameter, the virthost.

git clone https://gist.github.com/6cf1e6bc32085dd358365f44267f7188.git

chmod +x ./6cf1e6bc32085dd358365f44267f7188/run_oooq_with_ipa.sh

./6cf1e6bc32085dd358365f44267f7188/run_oooq_with_ipa.sh <your virthost’s IP or FQDN>

Relevant Links:

[1] – https://github.com/openstack/tripleo-quickstart

[2] – https://github.com/openstack/tripleo-quickstart-extras

[3] – https://review.openstack.org/#/q/topic:bug/1662923

[4] – http://jaormx.github.io/

[5] – https://blog-rcritten.rhcloud.com/

[6] – https://vakwetu.wordpress.com/

[7] – https://github.com/openstack/tripleo-quickstart/blob/master/playbooks/quickstart-extras.yml

[8] – https://review.openstack.org/#/c/451523/27/config/general_config/ipa.yml

[9] – https://review.openstack.org/#/c/451523/27/config/nodes/1ctlr_1comp_1supp.yml

[10] – https://gist.github.com/HarryRybacki/6cf1e6bc32085dd358365f44267f7188