33

I'm looking for the way how to make Ansible to analyze playbooks for required and mandatory variables before run playbook's execution like:

- name: create remote app directory hierarchy
  file:
    path: "/opt/{{ app_name | required }}/"
    state: directory
    owner: "{{ app_user | required }}"
    group: "{{ app_user_group | required }}"
  ...

and rise error message if variable is undefined, like:

please set "app_name" variable before run (file XXX/main.yml:99)
Alexey Bakulin
  • 1,229
  • 2
  • 13
  • 15

6 Answers6

53

As Arbab Nazar mentioned, you can use {{ variable | mandatory }} (see Forcing variables to be defined) inside Ansible task.

But I think it looks nicer to add this as first task, it checks is app_name, app_user and app_user_group exist:

- name: 'Check mandatory variables are defined'
  assert:
    that:
      - app_name is defined
      - app_user is defined
      - app_user_group is defined
Alexey Vazhnov
  • 1,291
  • 17
  • 20
30

You can use this:

{{ variable | mandatory }}
xy2
  • 484
  • 2
  • 9
Arbab Nazar
  • 22,378
  • 10
  • 76
  • 82
  • 2
    This will not check that the variable is defined _before_ playbook execution as asked in the original question, instead it will fail when the var is evaluated at templating/runtime (which is the default behavior unless you set ` DEFAULT_UNDEFINED_VAR_BEHAVIOR = False`). https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#defining-mandatory-values – mp04 Feb 04 '21 at 20:16
  • 1
    It's not a good solution and causes to false positives when the `variable` has value, but its value is `false`. In this situation, ansible wrongly errors that the variable is not set. So, the solution provided by Alexy Vazhnov down there is correct and should be accepted. – Emran May 02 '21 at 09:32
18

Usually inside a role I perform checking input variables like in the example:

- name: "Verify that required string variables are defined"
  assert:
    that: 
      - "{{ ahs_var }} is defined"
      - "{{ ahs_var }} | length > 0"
      - "{{ ahs_var }} != None"
    fail_msg: "{{ ahs_var }} needs to be set for the role to work"
    success_msg: "Required variable {{ ahs_var }} is defined"
  loop_control:
    loop_var: ahs_var
  with_items:
    - ahs_item1
    - ahs_item2
    - ahs_item3

by the way there are some tricks:

  1. Don't use global variables inside a role.
  2. If you want use global variables define the role specific variable & set global variable to it i.e. some_role_name__db_port: "{{ db_port | default(5432) }}".
  3. It makes sense to use role name as the prefix for variable. it helps to understand the source inventory easier.
  4. Your role might be looped some how, so it makes sense to override the default item.
Sean Saleh
  • 460
  • 5
  • 17
Lev
  • 181
  • 1
  • 5
  • 7
    I like your double underscore to delimit the role name within the variable name. I have used this convention with success in other applications. – John McGehee May 07 '20 at 23:33
12

One way to check if mandatory variables are defined is:

- fail:
    msg: "Variable '{{ item }}' is not defined"
  when: item not in vars
  with_items:
    - app_nam
    - var2
René Pijl
  • 4,310
  • 1
  • 19
  • 25
4

If using a role-based architecture, check out the docs on role argument validation.

It allows for you to specify a roles/<role_name>/meta/argument_specs.yml file which allows you to make variables required. Here is an example taken from their sample specification:

# roles/myapp/meta/argument_specs.yml
---
argument_specs:
  # roles/myapp/tasks/main.yml entry point
  main:
    short_description: The main entry point for the myapp role.
    options:
      myapp_int:
        type: "int"
        required: false
        default: 42
        description: "The integer value, defaulting to 42."

      myapp_str:
        type: "str"
        required: true
        description: "The string value"

  # roles/myapp/tasks/alternate.yml entry point
  alternate:
    short_description: The alternate entry point for the myapp role.
    options:
      myapp_int:
        type: "int"
        required: false
        default: 1024
        description: "The integer value, defaulting to 1024."
papiro
  • 2,158
  • 1
  • 20
  • 29
3

There are 2 approaches:

  1. Specify |mandatory filter

    1. Beware dictionaries - if a key with mandatory value fails to eval, you may have a hard time uh understanding which is it.
  2. Use assert module

mvk_il
  • 940
  • 8
  • 11