Search code examples
jenkinsansibleidempotentansible-template

How to make this ansible jenkins script idempotent, when jenkins is rewriting its configuration?


I've a got an ansible playbook to deploy jenkins, where the jenkins config.xml jinja2 template file contains this snippet for AD authentication:

<securityRealm class="hudson.plugins.active_directory.ActiveDirectorySecurityRealm" plugin="[email protected]">
    <domain>{{ ldap_hostname }}/domain>
    <bindName>{{ ldap_bind_user }}</bindName>
    <bindPassword>{{ ldap_password }}</bindPassword>
    <server>{{ ldap_hostname }}:{{ ldap_port }}</server>
    <groupLookupStrategy>RECURSIVE</groupLookupStrategy>
    <removeIrrelevantGroups>false</removeIrrelevantGroups>
</securityRealm>

{{ ldap_password }} is a clear text password coming from the vault.

The problem is that when jenkins starts after the config.xml is deployed, it rewrites it by replacing the clear text password with a password hash. (The hash seems to be dependant on the target host, as we get different hashes on different virtual machines). While this is a good thing in general, it makes every execution of the playbook mark the template operation as changed.

How can I make this play script idempotent?


Solution

  • I really can't think of a nice clean way to do this so this might come across as slightly tortured.

    You've got a few options here depending on how "correct" you want to be with this.

    First off, you can simply tell Ansible that you don't care about whether you are making any changes to this file and so always mark is unchanged (green). You can do this with changed_when: False on the task.

    Alternatively you can say that you only want to template this file on the first run and after that it's out of Ansible's hands. This kind of approach is useful for bootstrapping installations of things that then want to manage their own files and you would never consider changing them again. For this you could get away with something like:

    - name: check if jenkins config.xml file is there
      stat:
        path: path/to/config.xml
      register: config-xml_stat
    
    - name: template jenkins config.xml if not already there
      template:
        src: path/to/config.xml.j2
        dest: path/to/config.xml
      when: config-xml_stat.exists == false
    

    Thinking about the problem you are templating something and then actively expecting a specific line to change from outside of Ansible's control. Another option here then would be to template the file to the box to a different file path than Jenkins is expecting (which can then be idempotent as normal), diff this with the file Jenkins is using and then if anything other than that line is different copy over the top of the Jenkins file. You could use something like this to do that:

    - name: template jenkins config.xml.template
      template:
        src: path/to/config.xml.j2
        dest: path/to/config.xml.template
    
    # We are expecting bindPassword to have changed so we can exclude this from the diff line count
    - name: diff jenkins config.xml and config.xml.template
      shell: diff path/to/config.xml.template path/to/config.xml | grep -v bindPassword | wc -l
      register: config-xml_diff
    
    # Diff will also output 2 extra lines that we don't care about. If there's more lines than this then we need to retemplate the config.xml
    - name: template jenkins config.xml
      template:
        src: path/to/config.xml.j2
        dest: path/to/config.xml
      when: config-xml_diff.stdout != 2
    

    The last one is a bit more awkward and unwieldy but is "more correct" as it is actively checking if the file has changed more than we are expecting for and if so re-templating it.

    If you decide to go down the third route then I would suggest combining the check to see if it exists from the second option to make it a little more obvious in what things are doing. Without it, if the config.xml file doesn't exist then your diff task will output a single line (diff: config.xml: No such file or directory) which will then still mean that your third task's conditional evaluates okay but it's a bit misleading.