various mod_args fixes (#60290)
* various mod_args fixes * filter task keywords when parsing actions from task_ds- prevents repeatedly banging on the pluginloader for things we know aren't modules/actions * clean up module/action error messaging. Death to `no action in task!`- actually list the candidate modules/actions from the task if present. * remove shadowed_module test * previous discussion was that this behavior isn't worth the complexity or performance costs in mod_args * fix/add test, remove module shadow logic * address review feedback
This commit is contained in:
parent
9efa00e762
commit
a40baf22fa
10 changed files with 36 additions and 55 deletions
2
changelogs/fragments/mod_args_tweaks.yml
Normal file
2
changelogs/fragments/mod_args_tweaks.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- changed task module/action parsing to report more helpful errors
|
|
@ -115,6 +115,15 @@ class ModuleArgsParser:
|
|||
raise AnsibleAssertionError("the type of 'task_ds' should be a dict, but is a %s" % type(task_ds))
|
||||
self._task_ds = task_ds
|
||||
self._collection_list = collection_list
|
||||
# delayed local imports to prevent circular import
|
||||
from ansible.playbook.task import Task
|
||||
from ansible.playbook.handler import Handler
|
||||
# store the valid Task/Handler attrs for quick access
|
||||
self._task_attrs = set(Task._valid_attrs.keys())
|
||||
self._task_attrs.update(set(Handler._valid_attrs.keys()))
|
||||
# HACK: why is static not a FieldAttribute on task with a post-validate to bomb if not include/import?
|
||||
self._task_attrs.add('static')
|
||||
self._task_attrs = frozenset(self._task_attrs)
|
||||
|
||||
def _split_module_string(self, module_string):
|
||||
'''
|
||||
|
@ -286,8 +295,11 @@ class ModuleArgsParser:
|
|||
|
||||
# module: <stuff> is the more new-style invocation
|
||||
|
||||
# walk the input dictionary to see we recognize a module name
|
||||
for (item, value) in iteritems(self._task_ds):
|
||||
# filter out task attributes so we're only querying unrecognized keys as actions/modules
|
||||
non_task_ds = dict((k, v) for k, v in iteritems(self._task_ds) if (k not in self._task_attrs) and (not k.startswith('with_')))
|
||||
|
||||
# walk the filtered input dictionary to see if we recognize a module name
|
||||
for item, value in iteritems(non_task_ds):
|
||||
if item in BUILTIN_TASKS or action_loader.has_plugin(item, collection_list=self._collection_list) or \
|
||||
module_loader.has_plugin(item, collection_list=self._collection_list):
|
||||
# finding more than one module name is a problem
|
||||
|
@ -299,14 +311,13 @@ class ModuleArgsParser:
|
|||
|
||||
# if we didn't see any module in the task at all, it's not a task really
|
||||
if action is None:
|
||||
if 'ping' not in module_loader:
|
||||
raise AnsibleParserError("The requested action was not found in configured module paths. "
|
||||
"Additionally, core modules are missing. If this is a checkout, "
|
||||
"run 'git pull --rebase' to correct this problem.",
|
||||
if non_task_ds: # there was one non-task action, but we couldn't find it
|
||||
bad_action = list(non_task_ds.keys())[0]
|
||||
raise AnsibleParserError("couldn't resolve module/action '{0}'. This often indicates a "
|
||||
"misspelling, missing collection, or incorrect module path.".format(bad_action),
|
||||
obj=self._task_ds)
|
||||
|
||||
else:
|
||||
raise AnsibleParserError("no action detected in task. This often indicates a misspelled module name, or incorrect module path.",
|
||||
raise AnsibleParserError("no module/action detected in task.",
|
||||
obj=self._task_ds)
|
||||
elif args.get('_raw_params', '') != '' and action not in RAW_PARAM_MODULES:
|
||||
templar = Templar(loader=None)
|
||||
|
|
|
@ -375,7 +375,7 @@ class PluginLoader:
|
|||
|
||||
return to_text(found_files[0])
|
||||
|
||||
def _find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
|
||||
def find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
|
||||
''' Find a plugin named name '''
|
||||
|
||||
global _PLUGIN_FILTERS
|
||||
|
@ -498,20 +498,6 @@ class PluginLoader:
|
|||
|
||||
return None
|
||||
|
||||
def find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
|
||||
''' Find a plugin named name '''
|
||||
|
||||
# Import here to avoid circular import
|
||||
from ansible.vars.reserved import is_reserved_name
|
||||
|
||||
plugin = self._find_plugin(name, mod_type=mod_type, ignore_deprecated=ignore_deprecated, check_aliases=check_aliases, collection_list=collection_list)
|
||||
if plugin and self.package == 'ansible.modules' and name not in ('gather_facts',) and is_reserved_name(name):
|
||||
raise AnsibleError(
|
||||
'Module "%s" shadows the name of a reserved keyword. Please rename or remove this module. Found at %s' % (name, plugin)
|
||||
)
|
||||
|
||||
return plugin
|
||||
|
||||
def has_plugin(self, name, collection_list=None):
|
||||
''' Checks if a plugin named name exists '''
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
shippable/posix/group3
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
- hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- command: whoami
|
||||
tags: foo
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
- hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "{{ lookup('vars', 'inventory_hostname') }}"
|
|
@ -1,14 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ux
|
||||
|
||||
OUT=$(ansible-playbook playbook.yml -i inventory -e @../../integration_config.yml "$@" 2>&1 | grep 'ERROR! Module "tags" shadows the name of a reserved keyword.')
|
||||
|
||||
if [[ -z "$OUT" ]]; then
|
||||
echo "Fake tags module did not cause error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# This playbook calls a lookup which shadows a keyword.
|
||||
# This is an ok situation, and should not error
|
||||
ansible-playbook playbook_lookup.yml -i ../../inventory -e @../../integration_config.yml "$@"
|
|
@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function
|
|||
__metaclass__ = type
|
||||
|
||||
import pytest
|
||||
import re
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.parsing.mod_args import ModuleArgsParser
|
||||
|
@ -124,5 +125,13 @@ class TestModArgsDwim:
|
|||
m.parse()
|
||||
|
||||
assert err.value.args[0].startswith("conflicting action statements: ")
|
||||
conflicts = set(err.value.args[0][len("conflicting action statements: "):].split(', '))
|
||||
assert conflicts == set(('ping', 'shell'))
|
||||
actions = set(re.search(r'(\w+), (\w+)', err.value.args[0]).groups())
|
||||
assert actions == set(['ping', 'shell'])
|
||||
|
||||
def test_bogus_action(self):
|
||||
args_dict = {'bogusaction': {}}
|
||||
m = ModuleArgsParser(args_dict)
|
||||
with pytest.raises(AnsibleParserError) as err:
|
||||
m.parse()
|
||||
|
||||
assert err.value.args[0].startswith("couldn't resolve module/action 'bogusaction'")
|
||||
|
|
|
@ -108,7 +108,7 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks):
|
|||
def test_empty_task(self):
|
||||
ds = [{}]
|
||||
self.assertRaisesRegexp(errors.AnsibleParserError,
|
||||
"no action detected in task. This often indicates a misspelled module name, or incorrect module path",
|
||||
"no module/action detected in task",
|
||||
helpers.load_list_of_tasks,
|
||||
ds, play=self.mock_play,
|
||||
variable_manager=self.mock_variable_manager, loader=self.fake_loader)
|
||||
|
@ -116,7 +116,7 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks):
|
|||
def test_empty_task_use_handlers(self):
|
||||
ds = [{}]
|
||||
self.assertRaisesRegexp(errors.AnsibleParserError,
|
||||
"no action detected in task. This often indicates a misspelled module name, or incorrect module path",
|
||||
"no module/action detected in task.",
|
||||
helpers.load_list_of_tasks,
|
||||
ds,
|
||||
use_handlers=True,
|
||||
|
@ -384,7 +384,7 @@ class TestLoadListOfBlocks(unittest.TestCase, MixinForMocks):
|
|||
ds = [{}]
|
||||
mock_play = MagicMock(name='MockPlay')
|
||||
self.assertRaisesRegexp(errors.AnsibleParserError,
|
||||
"no action detected in task. This often indicates a misspelled module name, or incorrect module path",
|
||||
"no module/action detected in task",
|
||||
helpers.load_list_of_blocks,
|
||||
ds, mock_play,
|
||||
parent_block=None,
|
||||
|
|
Loading…
Reference in a new issue