/lib/ansible/playbook/playbook_include.py

https://github.com/debfx/ansible · Python · 156 lines · 88 code · 8 blank · 60 comment · 5 complexity · 62594381e0e451de20ca8da2a0c6e954 MD5 · raw file

  1. # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
  2. #
  3. # This file is part of Ansible
  4. #
  5. # Ansible is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # Ansible is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  17. # Make coding more python3-ish
  18. from __future__ import (absolute_import, division, print_function)
  19. __metaclass__ = type
  20. import os
  21. from ansible.errors import AnsibleParserError, AnsibleAssertionError
  22. from ansible.module_utils.six import iteritems, string_types
  23. from ansible.parsing.splitter import split_args, parse_kv
  24. from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
  25. from ansible.playbook.attribute import FieldAttribute
  26. from ansible.playbook.base import Base
  27. from ansible.playbook.conditional import Conditional
  28. from ansible.playbook.taggable import Taggable
  29. from ansible.template import Templar
  30. class PlaybookInclude(Base, Conditional, Taggable):
  31. _import_playbook = FieldAttribute(isa='string')
  32. _vars = FieldAttribute(isa='dict', default=dict)
  33. @staticmethod
  34. def load(data, basedir, variable_manager=None, loader=None):
  35. return PlaybookInclude().load_data(ds=data, basedir=basedir, variable_manager=variable_manager, loader=loader)
  36. def load_data(self, ds, basedir, variable_manager=None, loader=None):
  37. '''
  38. Overrides the base load_data(), as we're actually going to return a new
  39. Playbook() object rather than a PlaybookInclude object
  40. '''
  41. # import here to avoid a dependency loop
  42. from ansible.playbook import Playbook
  43. from ansible.playbook.play import Play
  44. # first, we use the original parent method to correctly load the object
  45. # via the load_data/preprocess_data system we normally use for other
  46. # playbook objects
  47. new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader)
  48. all_vars = self.vars.copy()
  49. if variable_manager:
  50. all_vars.update(variable_manager.get_vars())
  51. templar = Templar(loader=loader, variables=all_vars)
  52. # then we use the object to load a Playbook
  53. pb = Playbook(loader=loader)
  54. file_name = templar.template(new_obj.import_playbook)
  55. if not os.path.isabs(file_name):
  56. file_name = os.path.join(basedir, file_name)
  57. pb._load_playbook_data(file_name=file_name, variable_manager=variable_manager, vars=self.vars.copy())
  58. # finally, update each loaded playbook entry with any variables specified
  59. # on the included playbook and/or any tags which may have been set
  60. for entry in pb._entries:
  61. # conditional includes on a playbook need a marker to skip gathering
  62. if new_obj.when and isinstance(entry, Play):
  63. entry._included_conditional = new_obj.when[:]
  64. temp_vars = entry.vars.copy()
  65. temp_vars.update(new_obj.vars)
  66. param_tags = temp_vars.pop('tags', None)
  67. if param_tags is not None:
  68. entry.tags.extend(param_tags.split(','))
  69. entry.vars = temp_vars
  70. entry.tags = list(set(entry.tags).union(new_obj.tags))
  71. if entry._included_path is None:
  72. entry._included_path = os.path.dirname(file_name)
  73. # Check to see if we need to forward the conditionals on to the included
  74. # plays. If so, we can take a shortcut here and simply prepend them to
  75. # those attached to each block (if any)
  76. if new_obj.when:
  77. for task_block in (entry.pre_tasks + entry.roles + entry.tasks + entry.post_tasks):
  78. task_block._attributes['when'] = new_obj.when[:] + task_block.when[:]
  79. return pb
  80. def preprocess_data(self, ds):
  81. '''
  82. Regorganizes the data for a PlaybookInclude datastructure to line
  83. up with what we expect the proper attributes to be
  84. '''
  85. if not isinstance(ds, dict):
  86. raise AnsibleAssertionError('ds (%s) should be a dict but was a %s' % (ds, type(ds)))
  87. # the new, cleaned datastructure, which will have legacy
  88. # items reduced to a standard structure
  89. new_ds = AnsibleMapping()
  90. if isinstance(ds, AnsibleBaseYAMLObject):
  91. new_ds.ansible_pos = ds.ansible_pos
  92. for (k, v) in iteritems(ds):
  93. if k in ('include', 'import_playbook'):
  94. self._preprocess_import(ds, new_ds, k, v)
  95. else:
  96. # some basic error checking, to make sure vars are properly
  97. # formatted and do not conflict with k=v parameters
  98. if k == 'vars':
  99. if 'vars' in new_ds:
  100. raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
  101. elif not isinstance(v, dict):
  102. raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
  103. new_ds[k] = v
  104. return super(PlaybookInclude, self).preprocess_data(new_ds)
  105. def _preprocess_import(self, ds, new_ds, k, v):
  106. '''
  107. Splits the playbook import line up into filename and parameters
  108. '''
  109. if v is None:
  110. raise AnsibleParserError("playbook import parameter is missing", obj=ds)
  111. elif not isinstance(v, string_types):
  112. raise AnsibleParserError("playbook import parameter must be a string indicating a file path, got %s instead" % type(v), obj=ds)
  113. # The import_playbook line must include at least one item, which is the filename
  114. # to import. Anything after that should be regarded as a parameter to the import
  115. items = split_args(v)
  116. if len(items) == 0:
  117. raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
  118. else:
  119. new_ds['import_playbook'] = items[0]
  120. if len(items) > 1:
  121. # rejoin the parameter portion of the arguments and
  122. # then use parse_kv() to get a dict of params back
  123. params = parse_kv(" ".join(items[1:]))
  124. if 'tags' in params:
  125. new_ds['tags'] = params.pop('tags')
  126. if 'vars' in new_ds:
  127. raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
  128. new_ds['vars'] = params