16

Is it possible to change a role variable default value according to some condition (i.e. the value of another variable)?

Details

I have two related variables for a command, env and composer_opts.

If both are left at default (env = "prod" and composer_opts = "--no-dev") everything is ok.

If I change env to dev, the default for the other one will break my command, so I always need to set both. Would it be possible to avoid this by setting a conditional default value with a custom script / if?

Important: I don't want to always set the composer_opts value according to the env value. I want to set it only if it's not already set (i.e. a dynamic default value).

Pseudocode

I would like to do something like this (following code is not valid, just pseudocode to express my need)

---
# defaults/main.yml

env: prod
composer_opts: 
    when: "{{env}}" = 'prod'
        '--no-dev --optimize-autoloader --no-interaction'
    when: "{{env}}" = 'dev'
        '' 
12

I suggest this solution:

---
 - set_fact:
     composer_opts: ""
   when: "{{env}}" == 'dev'

It will set composer_opts variable to string "" when variable env is equal to 'dev'.

Here is example of playbook based on updated question:

$ cat test.yml

---
- hosts: 127.0.0.1
  connection: local
  tasks:
  - set_fact:
      composer_opts: "{% if env == 'prod' %} '--no-dev --optimize-autoloader --no-interaction' {% else %} '' {% endif %}"

  - debug: var=composer_opts

Sample output:

sudo ansible-playbook test.yml -e env=dev

PLAY [127.0.0.1] ************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [127.0.0.1]

TASK: [set_fact ] ************************************************************* 
ok: [127.0.0.1]

TASK: [debug var="{{composer_opts}}"] ***************************************** 
ok: [127.0.0.1] => {
    "var": {
        " '' ": " '' "
    }
}

PLAY RECAP ******************************************************************** 
127.0.0.1                  : ok=3    changed=0    unreachable=0    failed=0   


sudo ansible-playbook test.yml -e env=prod

PLAY [127.0.0.1] ************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [127.0.0.1]

TASK: [set_fact ] ************************************************************* 
ok: [127.0.0.1]

TASK: [debug var="{{composer_opts}}"] ***************************************** 
ok: [127.0.0.1] => {
    "var": {
        " '--no-dev --optimize-autoloader --no-interaction' ": " '--no-dev --optimize-autoloader --no-interaction' "
    }
}

PLAY RECAP ******************************************************************** 
127.0.0.1                  : ok=3    changed=0    unreachable=0    failed=0   
  • 1
    This is a part of the solution. It will always set composer_opts to empty string when env is "dev", overwriting any actual value set. I think the conditional should be extended like this: when: "{{env}}" == 'dev' and "{{composer_opts}}" is undefined. Does it looks good? Can you udpate your question accordingly? – Francesco Abeni Aug 21 '15 at 11:05
  • composer_opts will be defined because it have it's default value. You need another expression to solve your task. For example variable custom_composer_opts. – Navern Aug 21 '15 at 12:00
  • Please elaborate with pseudocode what do you want to do. I will update my answer accordingly. – Navern Aug 21 '15 at 12:01
  • I have updated my question with additional explanation and pseudocode sample. Thank you. – Francesco Abeni Aug 21 '15 at 12:14
  • I have updated my answer. Check it. I believe i understood what you need. – Navern Aug 21 '15 at 12:37
4

While @Navern's answer does work, I found the embedded Jinja2 notation ("{% if env == 'prod' %} ...) to be extremely susceptible to notation and thus rather fragile. For example, when wrapping the line in question for better readability such as in this untested code:

composer_opts: >
               "{% if env == 'prod' %}
                   '--no-dev --optimize-autoloader --no-interaction'
                {% else %}
                   ''
                {% endif %}"

I ended up with unexpected results, such as additional whitespace or \n in composer_opts.

The approach I use is much dumber, but also more stable:

- name: set composer_opts for dev env
  set_fact:
     composer_opts: ''
     when: "{{env}}" == 'dev'

- name: set composer_opts for prod env
  set_fact:
     composer_opts: '--no-dev --optimize-autoloader --no-interaction'
     when: "{{env}}" == 'prod'

I also found this blog post to be useful which essentially follows the same approach.

  • @sec if you use | instead of > you might not have the whitespace problem. (or you'll get more of it LOL) – Michael May 17 '17 at 8:40
  • @sec Use '>-' and check out the ansible spec. It has many options for correctly manipulating multiline strings. yaml-multiline.info Note, in particular, the block chomping indicator. – DylanYoung Oct 10 at 13:23
  • Note that this solution also had precedence issues. A fact is not a default. – DylanYoung Oct 10 at 13:24
2

Ansible set_fact based on condition in one liner :

- name: "set composer_opts based on environment"
  set_fact:
     composer_opts:  "{{ '--no-dev --optimize-autoloader --no-interaction' if (env == 'prod') else '' }}"
  • Same precedence issues as the other solutions ( a fact is not a default), however, if you put this condiional right in your defaults.yml file, this solution will work. It gets real ugly real fast if you have a number of defaults dependent on the condition – DylanYoung Oct 10 at 13:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.