escaping text for inclusion in an xml template

Hello,

I’m trying to use ansible to set up a small team buildfarm with jenkins. This is my first use of ansible.

I’m successfully using ansible 2.2’s jenkins_job module as in:

tasks:

  • jenkins_job:
    name: feeds
    config: “{{ lookup(‘file’, ‘…/jenkins/jobs/feeds/config.xml’) }}”

The job is defined by the xml, which the module caries over to jenkins using jenkins’ rest API. This is great.

Now, most of my jobs are written as jenkins “pipeline” jobs. The job xml mostly holds and wraps a groovy script, like this (dumb) one:

node (‘linux’){
sh ‘test -d .qi || (rm -rf .qi; mkdir -p .qi && touch .qi/stamp)’
checkout scm sh ‘make’
}

Instead of editing the job/config.xml, I would prefer to edit the job/script.groovy, and embed it in the xml.

I tried using a role to do so:

  • { role: jenkinspipelinejob, name: ‘feeds’, script: “{{ lookup(‘file’, ‘jenkins/jobs/feeds/script.groovy’) }}” }

With

$ cat roles/jenkinspipelinejob/tasks/main.yaml

  • debug: msg=“CONFIG {{ lookup(‘template’, ‘config.xml.j2’) }}”
  • jenkins_job:
    name: “{{ name }}”
    config: “{{ lookup(‘template’, ‘config.xml.j2’) }}”

and

$ cat roles/jenkinspipelinejob/templates/config.xml.j2

<?xml version='1.0' encoding='UTF-8'?> false true

It does work, but requires me to escape my script.groovy for xml inclusion. For instance, instead of

sh 'test -d .qi || (rm -rf .qi; mkdir -p .qi **&&** touch .qi/stamp)'

I need to write

sh 'test -d .qi || (rm -rf .qi; mkdir -p .qi **&amp;&amp;** touch .qi/stamp)'

I would like this escaping to be done automatically, how should I proceed?

I guess I could write a module, but there might be a simpler way.

I’ve looked for “xml escaping jinja filter” without any luck.

For the record, the solution was to write my own xml-escaping filter plugin.

Which is easy enough:

$ cat filter_plugins/myfilters.py

Make coding more python3-ish

from future import (absolute_import, division, print_function)
metaclass = type
import xml.sax.saxutils
class FilterModule(object):
‘’’ my jinja2 filters for ansible’‘’
def filters(self):
return {‘escape_xml_attr’ : xml.sax.saxutils.quoteattr,
‘escape_xml_data’ : xml.sax.saxutils.escape}

Then, use it like this

  • { role: jenkinspipelinejob, name: ‘feeds’, script: “{{ lookup(‘file’, ‘jenkins/jobs/feeds/script.groovy’) | escape_xml_data }}” }

As far as I can see the built-in Jinja filter escape does this, so for example

  • name: escape
    command: echo “{{ ‘a&b’ | escape }}”

produces

TASK [test : escape] **************************************************
changed: [localhost] => {“changed”: true, “cmd”: [“echo”, “a&b”], “delta”: “0:00:00.018286”, “end”: “2017-04-12 11:16:17.629886”, “rc”: 0, “start”: “2017-04-12 11:16:17.611600”, “stderr”: “”, “stdout”: “a&b”, “stdout_lines”: [“a&b”], “warnings”: }

Alexey

As far as I can see the built-in Jinja filter escape does this, so for example

  • name: escape
    command: echo “{{ ‘a&b’ | escape }}”

Indeed yes, thank you Alexey!

Then, use it like this

  • { role: jenkinspipelinejob, name: ‘feeds’, script: “{{ lookup(‘file’, ‘jenkins/jobs/feeds/script.groovy’) | escape_xml_data }}” }

So this should become

  • { role: jenkinspipelinejob, name: ‘feeds’, script: “{{ lookup(‘file’, ‘jenkins/jobs/feeds/script.groovy’) | escape }}” }

but an even better way is to keep the “script” variable content unescaped, like:

  • { role: jenkinspipelinejob, name: ‘feeds’, script: “{{ lookup(‘file’, ‘jenkins/jobs/feeds/script.groovy’) }}” }

and escape it from the xml template:

$ cat roles/jenkinspipelinejob/
templates/config.xml.j2