mirror of https://github.com/ansible/ansible.git
add action_groups support to collections (#74039)
* Canonicalize module_defaults actions and action_groups pre-fork and cache them on the play * Call get_action_args_with_defaults with the resolved FQCN plugin and don't pass the redirect list * Add validation for action_group metadata and a toggle to disable the warnings * Handle groups recursively referring to each other * Remove special-casing for non-fqcn actions in module_defaults groups * Error for actions and groups in module_defaults that can't be resolved * Error for fully templated module_defaults * Add integration tests for action_groups * Changelogpull/75259/head
parent
9af0d91676
commit
3b861abce1
@ -0,0 +1,9 @@
|
||||
bugfixes:
|
||||
- Fully qualified 'ansible.legacy' and 'ansible.builtin' plugin names work in conjunction with module_defaults.
|
||||
breaking_changes:
|
||||
- Action, module, and group names in module_defaults must be static values. Their values can still be templates.
|
||||
- Unresolvable groups, action plugins, and modules in module_defaults are an error.
|
||||
- Fully qualified 'ansible.legacy' plugin names are not included implicitly in action_groups.
|
||||
minor_changes:
|
||||
- Collections can define action_groups in ``meta/runtime.yml``.
|
||||
- action_groups can include actions from other groups by using the special ``metadata`` dictionary field.
|
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ios_facts
|
||||
short_description: supporting network facts module
|
||||
description:
|
||||
- supporting network facts module for gather_facts + module_defaults tests
|
||||
options:
|
||||
gather_subset:
|
||||
description:
|
||||
- When supplied, this argument restricts the facts collected
|
||||
to a given subset.
|
||||
- Possible values for this argument include
|
||||
C(all), C(hardware), C(config), and C(interfaces).
|
||||
- Specify a list of values to include a larger subset.
|
||||
- Use a value with an initial C(!) to collect all facts except that subset.
|
||||
required: false
|
||||
default: '!config'
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
"""main entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
gather_subset=dict(default='!config')
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
module.exit_json(ansible_facts={'gather_subset': module.params['gather_subset'], '_ansible_facts_gathered': True})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 = '''
|
||||
---
|
||||
module: metadata
|
||||
version_added: 2.12
|
||||
short_description: Test module with a specific name
|
||||
description: Test module with a specific name
|
||||
options:
|
||||
data:
|
||||
description: Required option to test module_defaults work
|
||||
required: True
|
||||
type: str
|
||||
author:
|
||||
- Ansible Core Team
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
data=dict(type='str', required=True),
|
||||
),
|
||||
)
|
||||
|
||||
module.exit_json()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# 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 = '''
|
||||
---
|
||||
module: ping
|
||||
version_added: historical
|
||||
short_description: Try to connect to host, verify a usable python and return C(pong) on success
|
||||
description:
|
||||
- A trivial test module, this module always returns C(pong) on successful
|
||||
contact. It does not make sense in playbooks, but it is useful from
|
||||
C(/usr/bin/ansible) to verify the ability to login and that a usable Python is configured.
|
||||
- This is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node.
|
||||
- For Windows targets, use the M(ansible.windows.win_ping) module instead.
|
||||
- For Network targets, use the M(ansible.netcommon.net_ping) module instead.
|
||||
options:
|
||||
data:
|
||||
description:
|
||||
- Data to return for the C(ping) return value.
|
||||
- If this parameter is set to C(crash), the module will cause an exception.
|
||||
type: str
|
||||
default: pong
|
||||
seealso:
|
||||
- module: ansible.netcommon.net_ping
|
||||
- module: ansible.windows.win_ping
|
||||
author:
|
||||
- Ansible Core Team
|
||||
- Michael DeHaan
|
||||
notes:
|
||||
- Supports C(check_mode).
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Test we can logon to 'webservers' and execute python with json lib.
|
||||
# ansible webservers -m ping
|
||||
|
||||
- name: Example from an Ansible Playbook
|
||||
ansible.builtin.ping:
|
||||
|
||||
- name: Induce an exception to see what happens
|
||||
ansible.builtin.ping:
|
||||
data: crash
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
ping:
|
||||
description: Value provided with the data parameter.
|
||||
returned: success
|
||||
type: str
|
||||
sample: pong
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
data=dict(type='str', default='pong'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if module.params['data'] == 'crash':
|
||||
raise Exception("boom")
|
||||
|
||||
result = dict(
|
||||
ping=module.params['data'],
|
||||
)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# 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 = '''
|
||||
---
|
||||
module: ping
|
||||
version_added: historical
|
||||
short_description: Try to connect to host, verify a usable python and return C(pong) on success
|
||||
description:
|
||||
- A trivial test module, this module always returns C(pong) on successful
|
||||
contact. It does not make sense in playbooks, but it is useful from
|
||||
C(/usr/bin/ansible) to verify the ability to login and that a usable Python is configured.
|
||||
- This is NOT ICMP ping, this is just a trivial test module that requires Python on the remote-node.
|
||||
- For Windows targets, use the M(ansible.windows.win_ping) module instead.
|
||||
- For Network targets, use the M(ansible.netcommon.net_ping) module instead.
|
||||
options:
|
||||
data:
|
||||
description:
|
||||
- Data to return for the C(ping) return value.
|
||||
- If this parameter is set to C(crash), the module will cause an exception.
|
||||
type: str
|
||||
default: pong
|
||||
seealso:
|
||||
- module: ansible.netcommon.net_ping
|
||||
- module: ansible.windows.win_ping
|
||||
author:
|
||||
- Ansible Core Team
|
||||
- Michael DeHaan
|
||||
notes:
|
||||
- Supports C(check_mode).
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Test we can logon to 'webservers' and execute python with json lib.
|
||||
# ansible webservers -m ping
|
||||
|
||||
- name: Example from an Ansible Playbook
|
||||
ansible.builtin.ping:
|
||||
|
||||
- name: Induce an exception to see what happens
|
||||
ansible.builtin.ping:
|
||||
data: crash
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
ping:
|
||||
description: Value provided with the data parameter.
|
||||
returned: success
|
||||
type: str
|
||||
sample: pong
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
data=dict(type='str', default='pong'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if module.params['data'] == 'crash':
|
||||
raise Exception("boom")
|
||||
|
||||
result = dict(
|
||||
ping=module.params['data'],
|
||||
)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
module_defaults:
|
||||
group/{{ group_name }}:
|
||||
data: value
|
||||
tasks:
|
||||
- ping:
|
@ -0,0 +1,123 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
vars:
|
||||
reset_color: '\x1b\[0m'
|
||||
color: '\x1b\[[0-9];[0-9]{2}m'
|
||||
tasks:
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.empty_metadata
|
||||
|
||||
- command: ansible-playbook test_metadata_warning.yml
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning not in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: "Invalid metadata was found"
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.bad_metadata_format
|
||||
|
||||
- command: ansible-playbook test_metadata_warning.yml
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: >-
|
||||
Invalid metadata was found for action_group testns.testcoll.bad_metadata_format while loading module_defaults.
|
||||
The only expected key is metadata, but got keys: metadata, unexpected_key
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.multiple_metadata
|
||||
|
||||
- command: ansible-playbook test_metadata_warning.yml
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: >-
|
||||
Invalid metadata was found for action_group testns.testcoll.multiple_metadata while loading module_defaults.
|
||||
The group contains multiple metadata entries.
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.bad_metadata_options
|
||||
|
||||
- command: 'ansible-playbook test_metadata_warning.yml'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: >-
|
||||
Invalid metadata was found for action_group testns.testcoll.bad_metadata_options while loading module_defaults.
|
||||
The metadata contains unexpected keys: unexpected_key
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.bad_metadata_type
|
||||
|
||||
- command: ansible-playbook test_metadata_warning.yml
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: >-
|
||||
Invalid metadata was found for action_group testns.testcoll.bad_metadata_type while loading module_defaults.
|
||||
The metadata is not a dictionary. Got ['testgroup']
|
||||
|
||||
- template:
|
||||
src: test_metadata_warning.yml.j2
|
||||
dest: test_metadata_warning.yml
|
||||
vars:
|
||||
group_name: testns.testcoll.bad_metadata_option_type
|
||||
|
||||
- command: ansible-playbook test_metadata_warning.yml
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: >-
|
||||
Invalid metadata was found for action_group testns.testcoll.bad_metadata_option_type while loading module_defaults.
|
||||
The metadata contains unexpected key types: extend_group is {'name': 'testgroup'} (expected type list)
|
||||
|
||||
- name: test disabling action_group metadata validation
|
||||
command: ansible-playbook test_metadata_warning.yml
|
||||
environment:
|
||||
ANSIBLE_VALIDATE_ACTION_GROUP_METADATA: False
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: metadata_warning not in warnings
|
||||
vars:
|
||||
warnings: "{{ result.stderr | regex_replace(reset_color) | regex_replace(color) | regex_replace('\\n', ' ') }}"
|
||||
metadata_warning: "Invalid metadata was found for action_group"
|
||||
|
||||
- file:
|
||||
path: test_metadata_warning.yml
|
||||
state: absent
|
@ -0,0 +1,132 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: test ansible.legacy short group name
|
||||
module_defaults:
|
||||
group/testgroup:
|
||||
data: test
|
||||
block:
|
||||
- legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'pong'"
|
||||
|
||||
- ansible.legacy.legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'pong'"
|
||||
|
||||
- ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.legacy.ping: # resolves to ansible.builtin.ping
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.builtin.ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.builtin.formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- name: test group that includes a legacy action
|
||||
module_defaults:
|
||||
# As of 2.12, legacy actions must be included in the action group definition
|
||||
group/testlegacy:
|
||||
data: test
|
||||
block:
|
||||
- legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.legacy.legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- name: test ansible.builtin fully qualified group name
|
||||
module_defaults:
|
||||
group/ansible.builtin.testgroup:
|
||||
data: test
|
||||
block:
|
||||
# ansible.builtin does not contain ansible.legacy
|
||||
- legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping != 'test'"
|
||||
|
||||
# ansible.builtin does not contain ansible.legacy
|
||||
- ansible.legacy.legacy_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping != 'test'"
|
||||
|
||||
- ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
# Resolves to ansible.builtin.ping
|
||||
- ansible.legacy.ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.builtin.ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.builtin.formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- name: test collection group name
|
||||
module_defaults:
|
||||
group/testns.testcoll.testgroup:
|
||||
data: test
|
||||
block:
|
||||
# Plugin resolving to a different collection does not get the default
|
||||
- ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping != 'test'"
|
||||
|
||||
- formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- ansible.builtin.formerly_core_ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- testns.testcoll.ping:
|
||||
register: result
|
||||
- assert:
|
||||
that: "result.ping == 'test'"
|
||||
|
||||
- metadata:
|
||||
collections:
|
||||
- testns.testcoll
|
Loading…
Reference in New Issue