/lib/ansible/playbook/block.py

https://github.com/debfx/ansible · Python · 421 lines · 297 code · 65 blank · 59 comment · 102 complexity · 221395feb43498ca38ecfe1d7eea64be 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. from ansible.errors import AnsibleParserError
  21. from ansible.playbook.attribute import FieldAttribute
  22. from ansible.playbook.base import Base
  23. from ansible.playbook.become import Become
  24. from ansible.playbook.conditional import Conditional
  25. from ansible.playbook.helpers import load_list_of_tasks
  26. from ansible.playbook.role import Role
  27. from ansible.playbook.taggable import Taggable
  28. from ansible.utils.sentinel import Sentinel
  29. class Block(Base, Become, Conditional, Taggable):
  30. # main block fields containing the task lists
  31. _block = FieldAttribute(isa='list', default=list, inherit=False)
  32. _rescue = FieldAttribute(isa='list', default=list, inherit=False)
  33. _always = FieldAttribute(isa='list', default=list, inherit=False)
  34. # other fields
  35. _delegate_to = FieldAttribute(isa='string')
  36. _delegate_facts = FieldAttribute(isa='bool')
  37. # for future consideration? this would be functionally
  38. # similar to the 'else' clause for exceptions
  39. # _otherwise = FieldAttribute(isa='list')
  40. def __init__(self, play=None, parent_block=None, role=None, task_include=None, use_handlers=False, implicit=False):
  41. self._play = play
  42. self._role = role
  43. self._parent = None
  44. self._dep_chain = None
  45. self._use_handlers = use_handlers
  46. self._implicit = implicit
  47. # end of role flag
  48. self._eor = False
  49. if task_include:
  50. self._parent = task_include
  51. elif parent_block:
  52. self._parent = parent_block
  53. super(Block, self).__init__()
  54. def __repr__(self):
  55. return "BLOCK(uuid=%s)(id=%s)(parent=%s)" % (self._uuid, id(self), self._parent)
  56. def __eq__(self, other):
  57. '''object comparison based on _uuid'''
  58. return self._uuid == other._uuid
  59. def __ne__(self, other):
  60. '''object comparison based on _uuid'''
  61. return self._uuid != other._uuid
  62. def get_vars(self):
  63. '''
  64. Blocks do not store variables directly, however they may be a member
  65. of a role or task include which does, so return those if present.
  66. '''
  67. all_vars = self.vars.copy()
  68. if self._parent:
  69. all_vars.update(self._parent.get_vars())
  70. return all_vars
  71. @staticmethod
  72. def load(data, play=None, parent_block=None, role=None, task_include=None, use_handlers=False, variable_manager=None, loader=None):
  73. implicit = not Block.is_block(data)
  74. b = Block(play=play, parent_block=parent_block, role=role, task_include=task_include, use_handlers=use_handlers, implicit=implicit)
  75. return b.load_data(data, variable_manager=variable_manager, loader=loader)
  76. @staticmethod
  77. def is_block(ds):
  78. is_block = False
  79. if isinstance(ds, dict):
  80. for attr in ('block', 'rescue', 'always'):
  81. if attr in ds:
  82. is_block = True
  83. break
  84. return is_block
  85. def preprocess_data(self, ds):
  86. '''
  87. If a simple task is given, an implicit block for that single task
  88. is created, which goes in the main portion of the block
  89. '''
  90. if not Block.is_block(ds):
  91. if isinstance(ds, list):
  92. return super(Block, self).preprocess_data(dict(block=ds))
  93. else:
  94. return super(Block, self).preprocess_data(dict(block=[ds]))
  95. return super(Block, self).preprocess_data(ds)
  96. def _load_block(self, attr, ds):
  97. try:
  98. return load_list_of_tasks(
  99. ds,
  100. play=self._play,
  101. block=self,
  102. role=self._role,
  103. task_include=None,
  104. variable_manager=self._variable_manager,
  105. loader=self._loader,
  106. use_handlers=self._use_handlers,
  107. )
  108. except AssertionError as e:
  109. raise AnsibleParserError("A malformed block was encountered while loading a block", obj=self._ds, orig_exc=e)
  110. def _load_rescue(self, attr, ds):
  111. try:
  112. return load_list_of_tasks(
  113. ds,
  114. play=self._play,
  115. block=self,
  116. role=self._role,
  117. task_include=None,
  118. variable_manager=self._variable_manager,
  119. loader=self._loader,
  120. use_handlers=self._use_handlers,
  121. )
  122. except AssertionError as e:
  123. raise AnsibleParserError("A malformed block was encountered while loading rescue.", obj=self._ds, orig_exc=e)
  124. def _load_always(self, attr, ds):
  125. try:
  126. return load_list_of_tasks(
  127. ds,
  128. play=self._play,
  129. block=self,
  130. role=self._role,
  131. task_include=None,
  132. variable_manager=self._variable_manager,
  133. loader=self._loader,
  134. use_handlers=self._use_handlers,
  135. )
  136. except AssertionError as e:
  137. raise AnsibleParserError("A malformed block was encountered while loading always", obj=self._ds, orig_exc=e)
  138. def _validate_always(self, attr, name, value):
  139. if value and not self.block:
  140. raise AnsibleParserError("'%s' keyword cannot be used without 'block'" % name, obj=self._ds)
  141. _validate_rescue = _validate_always
  142. def get_dep_chain(self):
  143. if self._dep_chain is None:
  144. if self._parent:
  145. return self._parent.get_dep_chain()
  146. else:
  147. return None
  148. else:
  149. return self._dep_chain[:]
  150. def copy(self, exclude_parent=False, exclude_tasks=False):
  151. def _dupe_task_list(task_list, new_block):
  152. new_task_list = []
  153. for task in task_list:
  154. new_task = task.copy(exclude_parent=True)
  155. if task._parent:
  156. new_task._parent = task._parent.copy(exclude_tasks=True)
  157. if task._parent == new_block:
  158. # If task._parent is the same as new_block, just replace it
  159. new_task._parent = new_block
  160. else:
  161. # task may not be a direct child of new_block, search for the correct place to insert new_block
  162. cur_obj = new_task._parent
  163. while cur_obj._parent and cur_obj._parent != new_block:
  164. cur_obj = cur_obj._parent
  165. cur_obj._parent = new_block
  166. else:
  167. new_task._parent = new_block
  168. new_task_list.append(new_task)
  169. return new_task_list
  170. new_me = super(Block, self).copy()
  171. new_me._play = self._play
  172. new_me._use_handlers = self._use_handlers
  173. new_me._eor = self._eor
  174. if self._dep_chain is not None:
  175. new_me._dep_chain = self._dep_chain[:]
  176. new_me._parent = None
  177. if self._parent and not exclude_parent:
  178. new_me._parent = self._parent.copy(exclude_tasks=True)
  179. if not exclude_tasks:
  180. new_me.block = _dupe_task_list(self.block or [], new_me)
  181. new_me.rescue = _dupe_task_list(self.rescue or [], new_me)
  182. new_me.always = _dupe_task_list(self.always or [], new_me)
  183. new_me._role = None
  184. if self._role:
  185. new_me._role = self._role
  186. new_me.validate()
  187. return new_me
  188. def serialize(self):
  189. '''
  190. Override of the default serialize method, since when we're serializing
  191. a task we don't want to include the attribute list of tasks.
  192. '''
  193. data = dict()
  194. for attr in self._valid_attrs:
  195. if attr not in ('block', 'rescue', 'always'):
  196. data[attr] = getattr(self, attr)
  197. data['dep_chain'] = self.get_dep_chain()
  198. data['eor'] = self._eor
  199. if self._role is not None:
  200. data['role'] = self._role.serialize()
  201. if self._parent is not None:
  202. data['parent'] = self._parent.copy(exclude_tasks=True).serialize()
  203. data['parent_type'] = self._parent.__class__.__name__
  204. return data
  205. def deserialize(self, data):
  206. '''
  207. Override of the default deserialize method, to match the above overridden
  208. serialize method
  209. '''
  210. # import is here to avoid import loops
  211. from ansible.playbook.task_include import TaskInclude
  212. from ansible.playbook.handler_task_include import HandlerTaskInclude
  213. # we don't want the full set of attributes (the task lists), as that
  214. # would lead to a serialize/deserialize loop
  215. for attr in self._valid_attrs:
  216. if attr in data and attr not in ('block', 'rescue', 'always'):
  217. setattr(self, attr, data.get(attr))
  218. self._dep_chain = data.get('dep_chain', None)
  219. self._eor = data.get('eor', False)
  220. # if there was a serialized role, unpack it too
  221. role_data = data.get('role')
  222. if role_data:
  223. r = Role()
  224. r.deserialize(role_data)
  225. self._role = r
  226. parent_data = data.get('parent')
  227. if parent_data:
  228. parent_type = data.get('parent_type')
  229. if parent_type == 'Block':
  230. p = Block()
  231. elif parent_type == 'TaskInclude':
  232. p = TaskInclude()
  233. elif parent_type == 'HandlerTaskInclude':
  234. p = HandlerTaskInclude()
  235. p.deserialize(parent_data)
  236. self._parent = p
  237. self._dep_chain = self._parent.get_dep_chain()
  238. def set_loader(self, loader):
  239. self._loader = loader
  240. if self._parent:
  241. self._parent.set_loader(loader)
  242. elif self._role:
  243. self._role.set_loader(loader)
  244. dep_chain = self.get_dep_chain()
  245. if dep_chain:
  246. for dep in dep_chain:
  247. dep.set_loader(loader)
  248. def _get_parent_attribute(self, attr, extend=False, prepend=False):
  249. '''
  250. Generic logic to get the attribute or parent attribute for a block value.
  251. '''
  252. extend = self._valid_attrs[attr].extend
  253. prepend = self._valid_attrs[attr].prepend
  254. try:
  255. value = self._attributes[attr]
  256. # If parent is static, we can grab attrs from the parent
  257. # otherwise, defer to the grandparent
  258. if getattr(self._parent, 'statically_loaded', True):
  259. _parent = self._parent
  260. else:
  261. _parent = self._parent._parent
  262. if _parent and (value is Sentinel or extend):
  263. try:
  264. if getattr(_parent, 'statically_loaded', True):
  265. if hasattr(_parent, '_get_parent_attribute'):
  266. parent_value = _parent._get_parent_attribute(attr)
  267. else:
  268. parent_value = _parent._attributes.get(attr, Sentinel)
  269. if extend:
  270. value = self._extend_value(value, parent_value, prepend)
  271. else:
  272. value = parent_value
  273. except AttributeError:
  274. pass
  275. if self._role and (value is Sentinel or extend):
  276. try:
  277. parent_value = self._role._attributes.get(attr, Sentinel)
  278. if extend:
  279. value = self._extend_value(value, parent_value, prepend)
  280. else:
  281. value = parent_value
  282. dep_chain = self.get_dep_chain()
  283. if dep_chain and (value is Sentinel or extend):
  284. dep_chain.reverse()
  285. for dep in dep_chain:
  286. dep_value = dep._attributes.get(attr, Sentinel)
  287. if extend:
  288. value = self._extend_value(value, dep_value, prepend)
  289. else:
  290. value = dep_value
  291. if value is not Sentinel and not extend:
  292. break
  293. except AttributeError:
  294. pass
  295. if self._play and (value is Sentinel or extend):
  296. try:
  297. play_value = self._play._attributes.get(attr, Sentinel)
  298. if play_value is not Sentinel:
  299. if extend:
  300. value = self._extend_value(value, play_value, prepend)
  301. else:
  302. value = play_value
  303. except AttributeError:
  304. pass
  305. except KeyError:
  306. pass
  307. return value
  308. def filter_tagged_tasks(self, play_context, all_vars):
  309. '''
  310. Creates a new block, with task lists filtered based on the tags contained
  311. within the play_context object.
  312. '''
  313. def evaluate_and_append_task(target):
  314. tmp_list = []
  315. for task in target:
  316. if isinstance(task, Block):
  317. tmp_list.append(evaluate_block(task))
  318. elif (task.action == 'meta' or
  319. (task.action == 'include' and task.evaluate_tags([], play_context.skip_tags, all_vars=all_vars)) or
  320. task.evaluate_tags(play_context.only_tags, play_context.skip_tags, all_vars=all_vars)):
  321. tmp_list.append(task)
  322. return tmp_list
  323. def evaluate_block(block):
  324. new_block = self.copy(exclude_tasks=True)
  325. new_block.block = evaluate_and_append_task(block.block)
  326. new_block.rescue = evaluate_and_append_task(block.rescue)
  327. new_block.always = evaluate_and_append_task(block.always)
  328. return new_block
  329. return evaluate_block(self)
  330. def has_tasks(self):
  331. return len(self.block) > 0 or len(self.rescue) > 0 or len(self.always) > 0
  332. def get_include_params(self):
  333. if self._parent:
  334. return self._parent.get_include_params()
  335. else:
  336. return dict()
  337. def all_parents_static(self):
  338. '''
  339. Determine if all of the parents of this block were statically loaded
  340. or not. Since Task/TaskInclude objects may be in the chain, they simply
  341. call their parents all_parents_static() method. Only Block objects in
  342. the chain check the statically_loaded value of the parent.
  343. '''
  344. from ansible.playbook.task_include import TaskInclude
  345. if self._parent:
  346. if isinstance(self._parent, TaskInclude) and not self._parent.statically_loaded:
  347. return False
  348. return self._parent.all_parents_static()
  349. return True
  350. def get_first_parent_include(self):
  351. from ansible.playbook.task_include import TaskInclude
  352. if self._parent:
  353. if isinstance(self._parent, TaskInclude):
  354. return self._parent
  355. return self._parent.get_first_parent_include()
  356. return None