Ansible Module - required_if

561 Views Asked by At

I am developing an Ansible module and I have a problem with the architecture of my module. I would like to have something that looks like this:

- name: test my new module
  hosts: localhost
  tasks:
  - name: run the new module
    my_module:
      instance:
        name: test
        state: present
        other: mano

My module is called my_module. The idea is to ask for required fields in my "instance" dict depending on the value of "state". I tried to do it with required_if see Ansible documentation: https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec-dependencies

Below is my module in python :

#!/usr/bin/python

# Copyright: (c) 2018, Terry Jones <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: my_test

short_description: This is my test module

# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "1.0.0"

description: This is my longer description explaining my test module.

options:
    name:
        description: This is the message to send to the test module.
        required: true
        type: str
    new:
        description:
            - Control to demo if the result of this module is changed or not.
            - Parameter description can be a list as well.
        required: false
        type: bool
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
extends_documentation_fragment:
    - my_namespace.my_collection.my_doc_fragment_name

author:
    - Your Name (@yourGitHubHandle)
'''

EXAMPLES = r'''
# Pass in a message
- name: Test with a message
  my_namespace.my_collection.my_test:
    name: hello world

# pass in a message and have changed true
- name: Test with a message and changed output
  my_namespace.my_collection.my_test:
    name: hello world
    new: true

# fail the module
- name: Test failure of the module
  my_namespace.my_collection.my_test:
    name: fail me
'''

RETURN = r'''
# These are examples of possible return values, and in general should use other names for return values.
original_message:
    description: The original name param that was passed in.
    type: str
    returned: always
    sample: 'hello world'
message:
    description: The output message that the test module generates.
    type: str
    returned: always
    sample: 'goodbye'
'''

from ansible.module_utils.basic import AnsibleModule


def run_module():

    instance=dict(type='dict',
        name=dict(type='dict'),
        state=dict(type='dict'),
        other=dict(type='dict')
    )

    required_if=[('state', 'present', ('name', 'other'))
    ]

    argument_spec = dict(
        instance=dict(type='dict', elements='dict', options=instance)
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        required_if=required_if,
        supports_check_mode=True
    )


    module.exit_json(name="test")


def main():
    run_module()


if __name__ == '__main__':
    main()

The errors that it returns to me are the following :

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: AttributeError: 'str' object has no attribute 'get'
fatal: [localhost]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last)
AttributeError: 'str' object has no attribute 'get'\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

Or if I try to modify and do otherwise :

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Invalid type dict for option '{'name': 'test', 'state': 'present', 'other': 'mano'}', elements value check is supported only with 'list' type"}

This error is I think due to the type and the element when defining the instance in argument_spec.

My goal is the following: I would like to have different sub-modules here for example "instance" we could have had other options and to be able to ask for mandatory fields depending on the sub-module here "instance" and its "state".

I saw that normally it should be possible to do it with "required_if" and "options" from AnsibleModule

1

There are 1 best solutions below

2
On

There are a couple of problem here.

First, required_if can only refer to top-level options. By setting required_if=('state', 'present', ('name', 'other')), you are describing a condition that looks like:

- my_module:
    state: present
    name: foo
    other: bar

But that doesn't match the structure of your argument_spec.

Second, your argument spec doesn't match your example usage, which is the source of the AttributeError: 'str' object has no attribute 'get' error. Your spec says that the values of name, state, and other should be dictionaries:

instance=dict(type='dict',
    name=dict(type='dict'),
    state=dict(type='dict'),
    other=dict(type='dict')
)

argument_spec = dict(
    instance=dict(type='dict', elements='dict', options=instance)
)

But you're passing in strings. You also appear to be erroneously setting type in instance, and elements, according to the documentation, is only useful with list options. I think you want:

instance=dict(
    name=dict(type='str'),
    state=dict(type='str'),
    other=dict(type='str')
)

argument_spec = dict(
    instance=dict(type='dict', options=instance)
)

The above will support calling the module like this:

- hosts: localhost
  gather_facts: false
  tasks:
    - my_module:
        instance:
          state: present
          name: foo
          other: bar