PageRenderTime 33ms CodeModel.GetById 15ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/ansible/playbook/playbook_include.py

https://github.com/debfx/ansible
Python | 156 lines | 94 code | 8 blank | 54 comment | 0 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
 18# Make coding more python3-ish
 19from __future__ import (absolute_import, division, print_function)
 20__metaclass__ = type
 21
 22import os
 23
 24from ansible.errors import AnsibleParserError, AnsibleAssertionError
 25from ansible.module_utils.six import iteritems, string_types
 26from ansible.parsing.splitter import split_args, parse_kv
 27from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
 28from ansible.playbook.attribute import FieldAttribute
 29from ansible.playbook.base import Base
 30from ansible.playbook.conditional import Conditional
 31from ansible.playbook.taggable import Taggable
 32from ansible.template import Templar
 33
 34
 35class PlaybookInclude(Base, Conditional, Taggable):
 36
 37    _import_playbook = FieldAttribute(isa='string')
 38    _vars = FieldAttribute(isa='dict', default=dict)
 39
 40    @staticmethod
 41    def load(data, basedir, variable_manager=None, loader=None):
 42        return PlaybookInclude().load_data(ds=data, basedir=basedir, variable_manager=variable_manager, loader=loader)
 43
 44    def load_data(self, ds, basedir, variable_manager=None, loader=None):
 45        '''
 46        Overrides the base load_data(), as we're actually going to return a new
 47        Playbook() object rather than a PlaybookInclude object
 48        '''
 49
 50        # import here to avoid a dependency loop
 51        from ansible.playbook import Playbook
 52        from ansible.playbook.play import Play
 53
 54        # first, we use the original parent method to correctly load the object
 55        # via the load_data/preprocess_data system we normally use for other
 56        # playbook objects
 57        new_obj = super(PlaybookInclude, self).load_data(ds, variable_manager, loader)
 58
 59        all_vars = self.vars.copy()
 60        if variable_manager:
 61            all_vars.update(variable_manager.get_vars())
 62
 63        templar = Templar(loader=loader, variables=all_vars)
 64
 65        # then we use the object to load a Playbook
 66        pb = Playbook(loader=loader)
 67
 68        file_name = templar.template(new_obj.import_playbook)
 69        if not os.path.isabs(file_name):
 70            file_name = os.path.join(basedir, file_name)
 71
 72        pb._load_playbook_data(file_name=file_name, variable_manager=variable_manager, vars=self.vars.copy())
 73
 74        # finally, update each loaded playbook entry with any variables specified
 75        # on the included playbook and/or any tags which may have been set
 76        for entry in pb._entries:
 77
 78            # conditional includes on a playbook need a marker to skip gathering
 79            if new_obj.when and isinstance(entry, Play):
 80                entry._included_conditional = new_obj.when[:]
 81
 82            temp_vars = entry.vars.copy()
 83            temp_vars.update(new_obj.vars)
 84            param_tags = temp_vars.pop('tags', None)
 85            if param_tags is not None:
 86                entry.tags.extend(param_tags.split(','))
 87            entry.vars = temp_vars
 88            entry.tags = list(set(entry.tags).union(new_obj.tags))
 89            if entry._included_path is None:
 90                entry._included_path = os.path.dirname(file_name)
 91
 92            # Check to see if we need to forward the conditionals on to the included
 93            # plays. If so, we can take a shortcut here and simply prepend them to
 94            # those attached to each block (if any)
 95            if new_obj.when:
 96                for task_block in (entry.pre_tasks + entry.roles + entry.tasks + entry.post_tasks):
 97                    task_block._attributes['when'] = new_obj.when[:] + task_block.when[:]
 98
 99        return pb
100
101    def preprocess_data(self, ds):
102        '''
103        Regorganizes the data for a PlaybookInclude datastructure to line
104        up with what we expect the proper attributes to be
105        '''
106
107        if not isinstance(ds, dict):
108            raise AnsibleAssertionError('ds (%s) should be a dict but was a %s' % (ds, type(ds)))
109
110        # the new, cleaned datastructure, which will have legacy
111        # items reduced to a standard structure
112        new_ds = AnsibleMapping()
113        if isinstance(ds, AnsibleBaseYAMLObject):
114            new_ds.ansible_pos = ds.ansible_pos
115
116        for (k, v) in iteritems(ds):
117            if k in ('include', 'import_playbook'):
118                self._preprocess_import(ds, new_ds, k, v)
119            else:
120                # some basic error checking, to make sure vars are properly
121                # formatted and do not conflict with k=v parameters
122                if k == 'vars':
123                    if 'vars' in new_ds:
124                        raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
125                    elif not isinstance(v, dict):
126                        raise AnsibleParserError("vars for import_playbook statements must be specified as a dictionary", obj=ds)
127                new_ds[k] = v
128
129        return super(PlaybookInclude, self).preprocess_data(new_ds)
130
131    def _preprocess_import(self, ds, new_ds, k, v):
132        '''
133        Splits the playbook import line up into filename and parameters
134        '''
135
136        if v is None:
137            raise AnsibleParserError("playbook import parameter is missing", obj=ds)
138        elif not isinstance(v, string_types):
139            raise AnsibleParserError("playbook import parameter must be a string indicating a file path, got %s instead" % type(v), obj=ds)
140
141        # The import_playbook line must include at least one item, which is the filename
142        # to import. Anything after that should be regarded as a parameter to the import
143        items = split_args(v)
144        if len(items) == 0:
145            raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
146        else:
147            new_ds['import_playbook'] = items[0]
148            if len(items) > 1:
149                # rejoin the parameter portion of the arguments and
150                # then use parse_kv() to get a dict of params back
151                params = parse_kv(" ".join(items[1:]))
152                if 'tags' in params:
153                    new_ds['tags'] = params.pop('tags')
154                if 'vars' in new_ds:
155                    raise AnsibleParserError("import_playbook parameters cannot be mixed with 'vars' entries for import statements", obj=ds)
156                new_ds['vars'] = params