Category Archives: CI/CD Tools

A Better ansible_managed Tag

If you’re using Ansible to manage configuration files on your servers, you’re probably familiar with the {{ ansible_managed }} tag .

This tag is well behaved from an idempotentcy perspective, but in the nitty-gritty of managing servers where there’s a chance of interaction by humans, this built-in tag leaves a lot to be desired.

Enter Ansible filter plugins, and a little bit of magic in group_vars.

This filter plugin will obtain (according to git pretty-formats) the commit hash, author, and date of the last commit to the file provided by the filter plugin structure:

#!/usr/bin/python
# this should be saved to ./filter_plugins/get_last_commit_to_file.py

class FilterModule(object):
  def filters(self):
    return {
      'get_last_commit_to_file': self.get_last_commit_to_file
    }

  def get_last_commit_to_file(self, file_path):
    import os
    try:  # py3
      from shlex import quote
    except ImportError:  # py2
      from pipes import quote
    stream = os.popen('git log -n 1 --pretty=format:\'%h by %an at %aD\' -- ' + quote(file_path))
    output = stream.read()
    return output

So, now we can define two new variables in our group_vars/all.yml file: ansible_workspace_git_url and ansible_managed_custom.

Notice the use of our custom filter plugin – we can pipe the variable template_path variable (which Ansible gives to us for free) into our filter plugin to obtain a string about the git history of our file!

ansible_workspace_git_url: "https://github.com/myrepo"

ansible_managed_custom: "
Ansible Managed for {{ inventory_hostname }} ({{ansible_system_vendor}}::{{ansible_product_name}}::{{ansible_product_serial}}) \n
Ansible Git Repository URL: {{ ansible_workspace_git_url }}\n
Inventory File: {{ inventory_file|replace(inventory_dir,'') }}@{{ inventory_file | get_last_commit_to_file }}\n
Template file: {{ template_path|replace(inventory_dir,'') }}@{{ template_path | get_last_commit_to_file }}\n
Role Context: {{ role_name | default('None') }}\n
Rendered Template Target: {{ template_destpath }}"

We now have available for use inside any template a nicely formatted (but not necessarily idempotent) header which gives any readers DETAILED information including:

  • The hostname and BIOS serial number of the host to which this file was rendered
  • The git repository of the playbook responsible for this template
  • The filename of the template used to render this file
  • The hash of the last commit which affected the template used to render this file
  • The filename of the inventory supplying variables to render this template
  • The hash of the last commit which affected the inventory supplying variables to render this template
  • The target path on the target server to where this file was rendered

Now, anytime I’m templating out a file (well, at least any file that supports multiline comments), I can just include our new ansible_managed_custom variable:

<!--
  {{ ansible_managed_custom }}
--> 

By switching to this header format, I was able to better assist my teammates with identifying why a template was rendered in a particular fashion, as well as provide them details about how to fix any problems they may find. This template format also helps answer questions about whether the rendered file was _copied_ from one server to another (possibly in a migration) and whether the file is likely to still be under Ansible management.

TIL: Java code in Jenkins pipelines run on the Master

I was trying to read a file with Java.io.File in a Jenkins Groovy Scripted Pipeline on a non-master node. I kept getting an exception that the file was not found (java.io.FileNotFoundException)

Turns out that Java code written in scripted pipelines (Groovy) runs on the master node: https://issues.jenkins-ci.org/browse/JENKINS-37577. This is as-designed behavior, and accessing files in the workspace on a non-master node should use the readFile function in the Pipeline Basic Steps DSL https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#pwd-determine-current-directory

I’m thoroughly embarrassed at how many failed Jenkins jobs and alerts I’ve triggered while discovering this.