Vlad Nikiforov
Vlad Nikiforov

Reputation: 7025

Ansible dictionary: implicit evaluation of all values?

I'm a bit puzzled about Ansible behaviour: it looks like when I request a single key's value, it evaluates all values in the dictionary.

Here's my case. I have a dictionary with two keys: dev and prod. Each key's value is defined via a specific Jinja2 expression that involves decryption using AWS KMS. Roughly it looks like this:

mydict:
  dev: '{{ "dev-ciphertext" | kms_decrypt }}'
  prod: '{{ "prod-ciphertext" | kms_decrypt }}'

kms_decrypt is a custom filter that decrypts the ciphertext. Since dev and prod environments are separated, and the playbook runs within an encryption context valid for only one of these environments, only one expression can be evaluated at a time. Attempt to retrieve a value from the other key will fail.

So let's say the playbook runs in the context of dev environment, and when I evaluate mydict['dev'], I expect it to return decrypted dev-ciphertext. However, what I get in fact is failure on decryption of prod-ciphertext, because the encryption context does not match.

I can illustrate the same behaviour in a simpler example. Instead of recreating the decryption mechanism, I just defined one of the dictionary values via an undefined variable:

- hosts: localhost
  become: no
  vars:
    dev_value: '123'
    mydict:
     dev: '{{ dev_value }}'
     prod: '{{ prod_value }}'
  tasks:
    - debug:
        msg: "{{ mydict['dev'] }}"

Regardless of the fact that mydict['prod'] is never explicitly queried, I still get an error indicating that it can't be evaluated:

TASK [debug] *********************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'prod_value' is undefined\n\nThe error appears to have been in 'ansible/test.yml': line 9, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n  tasks:\n    - debug:\n      ^ here\n"}

I realize that there are a lot of ways to work around this problem, but can anyone explain why does the whole dictionary values get evaluated when only a single key is queried? It doesn't seem to make sense to me - at least from performance point of view.

Upvotes: 1

Views: 344

Answers (1)

mdaniel
mdaniel

Reputation: 33231

Jinja templates are rendered in all circumstances -- there is no such thing as "lazy jinja," only lazy tasks via the when: or similar guards

So if you don't already have my_dict["dev"] scattered all over your code, then I would suggest using a reasonable name like my_env or something, and only declare the one value that is meaningful for the active environment:

- hosts: localhost
  become: no
  vars:
    dev_value: '123'
  tasks:
  - set_fact:
      my_env: '{{ "prod-ciphertext" | kms_decrypt }}'
    when: some_environment_variable == "prod"
  - set_fact:
      my_env: '{{ "dev-ciphertext" | kms_decrypt }}'
    when: some_environment_variable == "dev"

Otherwise, you can guard the expressions -- which for clarity are always going to be evaluated -- to return defaults if they are not applicable:

- hosts: localhost
  become: no
  vars:
    dev_value: '123'
    my_dict:
      prod: '{{ ("prod-ciphertext" | kms_decrypt)
               if the_magic_env == "prod" else {} }}'
      dev: '{{ ("dev-ciphertext" | kms_decrypt)
               if the_magic_env == "dev" else {} }}'
  tasks:
    - debug:
        msg: "{{ mydict['dev'] }}"

Upvotes: 1

Related Questions