If you’re an Ansible convert, you want every part of your deployment to be managed by the tasks in your roles. I recently wanted to manage my Elastic Watcher config with Ansible. It was enough of a struggle (“learning process”) that I wanted to document it here.
While I was developing my first few watches, I made stand-alone shell scripts that could be run to create/update them. This worked fine, and even handled the basic auth configuration in front of the master nodes. Basically it was:
curl -u username -XPUT 'http://hostname/_watcher/watch/watchname' -d '{ ... }'
It would prompt for the user’s password and send the information along.
Ansible can run local commands, but doesn’t like interactive commands. As such, the password prompt would block the task from completing. Looking around for other solutions led me to the ‘uri’ module. By default, it won’t prompt for the password either, but you can use the prompts system. I chose to use the Ansible vault to store the password to avoid external sharing/management of the password.
The final config looks like this:
- set_fact: watch: "{{ lookup('file','watcher.json') }}" - name: Install watcher for cluster health uri: delegate_to: 127.0.0.1 url: "http://hostname//_watcher/watch/watchname" method: PUT user: "ansible" password: "{{ ansible_password }}" force_basic_auth: yes body: "{{ watch }}" run_once: true
The set_fact loads the contents of the file into a variable (otherwise, you’d have to list the entire json in uri’s body section).
By using delegate_to, the PUT will be run on the local machine. Coupled with run_once, it contacts the cluster and sets the config one time.
On my desktop mac, it originally gave me errors about httplib2, which I had to install:
sudo pip install httplib2
As mentioned before, the variable that contains the basic auth password comes from an ansible vault.
One final – yet important – gotcha. Ansible seemingly wants to process all data as templates. Unfortunately, watcher uses the same variable syntax as Ansible, so your watcher definition may include lines with watcher variables:
{{ ctx.payload.status }}
Ansible will think these are its own variables, and you’ll get errors when running the playbook:
fatal: [localhost] => One or more undefined variables: 'ctx' is undefined
The solution is to tell Ansible to ignore that section:
{% raw %}{{ ctx.payload.status }}{% endraw %}
This can be a little tedious, but I didn’t find a way to prevent Ansible from interpreting the string.