I have an Ansible role that deploys microservices, of whom I have a list of. The microservices are called foo, bar and baz. I also have a list of stages, called DEV, QA and PROD where the microservices get deployed to.

Of course, every microservice needs to connect to a database using credentials. These credentials are different for every microservice in every stage. This means I have nine different password-variables stored in an Ansible-vault encrypted file. I named them {{ stage }}_{{ microservice }}_password, e.g. prod_foo_password.

The aforementioned role contains a task that creates the credentials for the database-connection and stores them in a secret (to be later used by the microservice). How do I pass the correct password for the stage-microservice-combination to this credential-creation task?

I could just write nine tasks, one for every combination of microservice and stage. That’s probably not even a bad idea to keep the code simple and easy to understand. That will look like this (illustrated with debug-tasks and shortened):

- hosts: localhost
  vars:
    # these are the secrets that are in my vault-file
    foo_QA_password: foo
    bar_QA_password: bar
    baz_QA_password: baz

    foo_DEV_password: foo
    bar_DEV_password: bar
    baz_DEV_password: baz
  tasks:
    - name: "create secret for foo in stage DEV"
      debug:
        msg: "{{ foo_DEV_password }}"

    - name: "create secret for bar in stage QA"
      debug:
        msg: "{{ bar_QA_password }}"
    ....

However, in my case, it’s not practical as the microservices and stages will change over time, meaning I’d have to add or delete tasks all the time.

What I did instead was to dynamically construct a variable from other variables, additionally using a loop:

- hosts: localhost
  vars_prompt:
    - name: "stage"
      prompt: "Stage of the vhost (DEV, QA, PROD)"
      private: no
  vars:
    # these are the secrets that are in my vault-file
    foo_QA_password: foo
    bar_QA_password: bar
    baz_QA_password: baz

    # list of microservices
    microservices:
      - "foo"
      - "bar"
      - "baz"

  tasks:
    - name: "create secret for {{ item }} in stage {{ stage }}"
      debug:
        msg: "{{ lookup('vars', item + '_' + stage + '_password') }}"
      loop: "{{ microservices }}"

The interesting part is this:

    data: "{{ lookup('vars', item + '_' + stage + '_password') }}"
loop: "{{ microservices }}"

This means that I first loop over all microservices (stored in the item-variable). I then create a lookup. In this lookup I look for vars that contain my item following the _-string, then the content of the variable stage (notice the missing curly braces here), finally the _password-string. The result is lookup('vars', item + '_' + stage + '_password') - this in turn gets interpolated to the actual password from my vault-file!

The magic here is done by the vars-lookup plugin. This lookup retrieves the value of an Ansible variable. And this variable can be made of other variables or strings. Definitely an advanced feature of Ansible, but still good to know that it exists.



Related posts: