20

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'
        '' 
chicks
  • 3,793
  • 10
  • 27
  • 36
Francesco Abeni
  • 575
  • 1
  • 4
  • 14

4 Answers4

14

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   
Francesco Abeni
  • 575
  • 1
  • 4
  • 14
Navern
  • 1,619
  • 1
  • 10
  • 14
  • 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
  • Excellent! Apart from a minor syntax issue on the debug statement, it does exactly what I need. Thank you! – Francesco Abeni Aug 21 '15 at 14:30
  • This solution has precedence issues. A fact is not a default. – DylanYoung Oct 10 '19 at 13:21
7

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.

ssc
  • 1,159
  • 3
  • 17
  • 30
  • @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 08:40
  • @sec Use '>-' and check out the ansible spec. It has many options for correctly manipulating multiline strings. https://yaml-multiline.info/ Note, in particular, the block chomping indicator. – DylanYoung Oct 10 '19 at 13:23
  • Note that this solution also had precedence issues. A fact is not a default. – DylanYoung Oct 10 '19 at 13:24
3

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 '' }}"
S.K. Venkat
  • 131
  • 4
  • 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 '19 at 13:25
  • IMO, we can have the variable in global vars file so that it could be imported wherever it is required. – S.K. Venkat Apr 08 '20 at 15:41
  • 1
    Imported how? That doesn't change the precedence. In particular, this fact will override host vars, unlike a default. You could add a condition though: `when: composer_opts is not defined` or something to that effect. – DylanYoung Apr 08 '20 at 20:28
0
- name: 

set_fact: path_install: | {% if reportviewer_state == 'absent' and reportviewer_handler[reportviewer_package].name == 'ReportViewer_2010.exe' %} C:\Windows\System32\msiexec.exe {% else %} {{ url_base_repository }}{{ reportviewer_handler[reportviewer_package].name }} {% endif %}

  • name: "install {{ reportviewer_handler[reportviewer_package].name }}" win_package: path: "{{ path_install | replace('\n', '') }}" state: "{{ reportviewer_state }}" product_id: "{{ reportviewer_handler[reportviewer_package].product_id }}" arguments: "{{ reportviewer_handler[reportviewer_package].arguments }}"