import_playbook validation cleanup (#85358)

* use declarative FA validation
* deleted redundant/broken imperative validation
* added test case to ensure templating

Co-authored-by: Matt Clay <matt@mystile.com>
pull/83271/merge
Matt Davis 6 months ago committed by GitHub
parent 3354d0d4e2
commit dde10a9afb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -221,8 +221,6 @@ class FieldAttributeBase:
def validate(self, all_vars=None): def validate(self, all_vars=None):
""" validation that is done at parse time, not load time """ """ validation that is done at parse time, not load time """
all_vars = {} if all_vars is None else all_vars
if not self._validated: if not self._validated:
# walk all fields in the object # walk all fields in the object
for (name, attribute) in self.fattributes.items(): for (name, attribute) in self.fattributes.items():

@ -19,12 +19,7 @@ from __future__ import annotations
import os import os
import ansible.constants as C
from ansible.errors import AnsibleParserError, AnsibleAssertionError
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils._internal._datatag import AnsibleTagHelper
from ansible.module_utils.six import string_types
from ansible.parsing.splitter import split_args
from ansible.playbook.attribute import NonInheritableFieldAttribute from ansible.playbook.attribute import NonInheritableFieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
@ -32,16 +27,15 @@ from ansible.playbook.taggable import Taggable
from ansible.utils.collection_loader import AnsibleCollectionConfig from ansible.utils.collection_loader import AnsibleCollectionConfig
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
from ansible._internal._templating._engine import TemplateEngine from ansible._internal._templating._engine import TemplateEngine
from ansible.utils.display import Display
display = Display()
class PlaybookInclude(Base, Conditional, Taggable): class PlaybookInclude(Base, Conditional, Taggable):
import_playbook = NonInheritableFieldAttribute(isa='string') import_playbook = NonInheritableFieldAttribute(isa='string', required=True)
vars_val = NonInheritableFieldAttribute(isa='dict', default=dict, alias='vars') vars_val = NonInheritableFieldAttribute(isa='dict', default=dict, alias='vars')
_post_validate_object = True # manually post_validate to get free arg validation/coercion
@staticmethod @staticmethod
def load(data, basedir, variable_manager=None, loader=None): def load(data, basedir, variable_manager=None, loader=None):
return PlaybookInclude().load_data(ds=data, basedir=basedir, variable_manager=variable_manager, loader=loader) return PlaybookInclude().load_data(ds=data, basedir=basedir, variable_manager=variable_manager, loader=loader)
@ -62,18 +56,22 @@ class PlaybookInclude(Base, Conditional, Taggable):
new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader) new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader)
all_vars = self.vars.copy() all_vars = self.vars.copy()
if variable_manager: if variable_manager:
all_vars |= variable_manager.get_vars() all_vars |= variable_manager.get_vars()
templar = TemplateEngine(loader=loader, variables=all_vars) templar = TemplateEngine(loader=loader, variables=all_vars)
new_obj.post_validate(templar)
# then we use the object to load a Playbook # then we use the object to load a Playbook
pb = Playbook(loader=loader) pb = Playbook(loader=loader)
file_name = templar.template(new_obj.import_playbook) file_name = new_obj.import_playbook
# check for FQCN # check for FQCN
resource = _get_collection_playbook_path(file_name) resource = _get_collection_playbook_path(file_name)
if resource is not None: if resource is not None:
playbook = resource[1] playbook = resource[1]
playbook_collection = resource[2] playbook_collection = resource[2]
@ -92,6 +90,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
else: else:
# it is NOT a collection playbook, setup adjacent paths # it is NOT a collection playbook, setup adjacent paths
AnsibleCollectionConfig.playbook_paths.append(os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict')))) AnsibleCollectionConfig.playbook_paths.append(os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict'))))
# broken, see: https://github.com/ansible/ansible/issues/85357
pb._load_playbook_data(file_name=playbook, variable_manager=variable_manager, vars=self.vars.copy()) pb._load_playbook_data(file_name=playbook, variable_manager=variable_manager, vars=self.vars.copy())
@ -120,49 +119,3 @@ class PlaybookInclude(Base, Conditional, Taggable):
task_block._when = new_obj.when[:] + task_block.when[:] task_block._when = new_obj.when[:] + task_block.when[:]
return pb return pb
def preprocess_data(self, ds):
"""
Reorganizes the data for a PlaybookInclude datastructure to line
up with what we expect the proper attributes to be
"""
if not isinstance(ds, dict):
raise AnsibleAssertionError('ds (%s) should be a dict but was a %s' % (ds, type(ds)))
# the new, cleaned datastructure, which will have legacy items reduced to a standard structure suitable for the
# attributes of the task class; copy any tagged data to preserve things like origin
new_ds = AnsibleTagHelper.tag_copy(ds, {})
for (k, v) in ds.items():
if k in C._ACTION_IMPORT_PLAYBOOK:
self._preprocess_import(ds, new_ds, k, v)
else:
# some basic error checking, to make sure vars are properly
# formatted and do not conflict with k=v parameters
if k == 'vars':
if 'vars' in new_ds:
raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
elif not isinstance(v, dict):
raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
new_ds[k] = v
return super(PlaybookInclude, self).preprocess_data(new_ds)
def _preprocess_import(self, ds, new_ds, k, v):
"""
Splits the playbook import line up into filename and parameters
"""
if v is None:
raise AnsibleParserError("playbook import parameter is missing", obj=ds)
elif not isinstance(v, string_types):
raise AnsibleParserError("playbook import parameter must be a string indicating a file path, got %s instead" % type(v), obj=ds)
# The import_playbook line must include at least one item, which is the filename
# to import. Anything after that should be regarded as a parameter to the import
items = split_args(v)
if len(items) == 0:
raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
# DTFIX3: investigate this as a possible "problematic strip"
new_ds['import_playbook'] = AnsibleTagHelper.tag_copy(v, items[0].strip())

@ -1,5 +1,5 @@
# Test and validate playbook import # Test and validate playbook import
- import_playbook: playbook1.yml - import_playbook: '{{ "playbook1.yml" }}' # ensure templating occurs correctly
- import_playbook: validate1.yml - import_playbook: validate1.yml
# Test and validate conditional import # Test and validate conditional import

Loading…
Cancel
Save