def test_collection_invalid_data_block():
    """Test that collection as a dict at the block level fails with parser error"""
    collection_name = {'name': 'foo'}
    with pytest.raises(AssibleParserError):
        Block.load(dict(
            block=[dict(name="test task", collections=collection_name)]
        ))
Exemple #2
0
    def _create_noop_block_from(self, original_block, parent):
        noop_block = Block(parent_block=parent)
        noop_block.block = self._replace_with_noop(original_block.block)
        noop_block.always = self._replace_with_noop(original_block.always)
        noop_block.rescue = self._replace_with_noop(original_block.rescue)

        return noop_block
Exemple #3
0
def load_list_of_blocks(ds,
                        play,
                        parent_block=None,
                        role=None,
                        task_include=None,
                        use_handlers=False,
                        variable_manager=None,
                        loader=None):
    '''
    Given a list of mixed task/block data (parsed from YAML),
    return a list of Block() objects, where implicit blocks
    are created for each bare Task.
    '''

    # we import here to prevent a circular dependency with imports
    from assible.playbook.block import Block

    if not isinstance(ds, (list, type(None))):
        raise AssibleAssertionError('%s should be a list or None but is %s' %
                                    (ds, type(ds)))

    block_list = []
    if ds:
        count = iter(range(len(ds)))
        for i in count:
            block_ds = ds[i]
            # Implicit blocks are created by bare tasks listed in a play without
            # an explicit block statement. If we have two implicit blocks in a row,
            # squash them down to a single block to save processing time later.
            implicit_blocks = []
            while block_ds is not None and not Block.is_block(block_ds):
                implicit_blocks.append(block_ds)
                i += 1
                # Advance the iterator, so we don't repeat
                next(count, None)
                try:
                    block_ds = ds[i]
                except IndexError:
                    block_ds = None

            # Loop both implicit blocks and block_ds as block_ds is the next in the list
            for b in (implicit_blocks, block_ds):
                if b:
                    block_list.append(
                        Block.load(
                            b,
                            play=play,
                            parent_block=parent_block,
                            role=role,
                            task_include=task_include,
                            use_handlers=use_handlers,
                            variable_manager=variable_manager,
                            loader=loader,
                        ))

    return block_list
Exemple #4
0
    def compile(self):
        '''
        Compiles and returns the task list for this play, compiled from the
        roles (which are themselves compiled recursively) and/or the list of
        tasks specified in the play.
        '''

        # create a block containing a single flush handlers meta
        # task, so we can be sure to run handlers at certain points
        # of the playbook execution
        flush_block = Block.load(data={'meta': 'flush_handlers'},
                                 play=self,
                                 variable_manager=self._variable_manager,
                                 loader=self._loader)

        for task in flush_block.block:
            task.implicit = True

        block_list = []

        block_list.extend(self.pre_tasks)
        block_list.append(flush_block)
        block_list.extend(self._compile_roles())
        block_list.extend(self.tasks)
        block_list.append(flush_block)
        block_list.extend(self.post_tasks)
        block_list.append(flush_block)

        return block_list
Exemple #5
0
 def test_deserialize(self):
     ds = dict(
         block=[dict(action='block')],
         rescue=[dict(action='rescue')],
         always=[dict(action='always')],
     )
     b = Block.load(ds)
     data = dict(parent=ds, parent_type='Block')
     b.deserialize(data)
     self.assertIsInstance(b._parent, Block)
Exemple #6
0
 def test_load_block_simple(self):
     ds = dict(
         block=[],
         rescue=[],
         always=[],
         # otherwise=[],
     )
     b = Block.load(ds)
     self.assertEqual(b.block, [])
     self.assertEqual(b.rescue, [])
     self.assertEqual(b.always, [])
Exemple #7
0
    def deserialize(self, data):

        # import is here to avoid import loops
        from assible.playbook.task_include import TaskInclude
        from assible.playbook.handler_task_include import HandlerTaskInclude

        parent_data = data.get('parent', None)
        if parent_data:
            parent_type = data.get('parent_type')
            if parent_type == 'Block':
                p = Block()
            elif parent_type == 'TaskInclude':
                p = TaskInclude()
            elif parent_type == 'HandlerTaskInclude':
                p = HandlerTaskInclude()
            p.deserialize(parent_data)
            self._parent = p
            del data['parent']

        role_data = data.get('role')
        if role_data:
            r = Role()
            r.deserialize(role_data)
            self._role = r
            del data['role']

        self._assible_internal_redirect_list = data.get('_assible_internal_redirect_list', [])

        self.implicit = data.get('implicit', False)

        super(Task, self).deserialize(data)
Exemple #8
0
 def test_load_block_with_tasks(self):
     ds = dict(
         block=[dict(action='block')],
         rescue=[dict(action='rescue')],
         always=[dict(action='always')],
         # otherwise=[dict(action='otherwise')],
     )
     b = Block.load(ds)
     self.assertEqual(len(b.block), 1)
     self.assertIsInstance(b.block[0], Task)
     self.assertEqual(len(b.rescue), 1)
     self.assertIsInstance(b.rescue[0], Task)
     self.assertEqual(len(b.always), 1)
     self.assertIsInstance(b.always[0], Task)
Exemple #9
0
    def build_parent_block(self):
        '''
        This method is used to create the parent block for the included tasks
        when ``apply`` is specified
        '''
        apply_attrs = self.args.pop('apply', {})
        if apply_attrs:
            apply_attrs['block'] = []
            p_block = Block.load(
                apply_attrs,
                play=self._parent._play,
                task_include=self,
                role=self._role,
                variable_manager=self._variable_manager,
                loader=self._loader,
            )
        else:
            p_block = self

        return p_block
Exemple #10
0
 def test_load_implicit_block(self):
     ds = [dict(action='foo')]
     b = Block.load(ds)
     self.assertEqual(len(b.block), 1)
     self.assertIsInstance(b.block[0], Task)
Exemple #11
0
 def test_construct_empty_block(self):
     b = Block()
Exemple #12
0
def load_list_of_tasks(ds,
                       play,
                       block=None,
                       role=None,
                       task_include=None,
                       use_handlers=False,
                       variable_manager=None,
                       loader=None):
    '''
    Given a list of task datastructures (parsed from YAML),
    return a list of Task() or TaskInclude() objects.
    '''

    # we import here to prevent a circular dependency with imports
    from assible.playbook.block import Block
    from assible.playbook.handler import Handler
    from assible.playbook.task import Task
    from assible.playbook.task_include import TaskInclude
    from assible.playbook.role_include import IncludeRole
    from assible.playbook.handler_task_include import HandlerTaskInclude
    from assible.template import Templar

    if not isinstance(ds, list):
        raise AssibleAssertionError(
            'The ds (%s) should be a list but was a %s' % (ds, type(ds)))

    task_list = []
    for task_ds in ds:
        if not isinstance(task_ds, dict):
            raise AssibleAssertionError(
                'The ds (%s) should be a dict but was a %s' % (ds, type(ds)))

        if 'block' in task_ds:
            t = Block.load(
                task_ds,
                play=play,
                parent_block=block,
                role=role,
                task_include=task_include,
                use_handlers=use_handlers,
                variable_manager=variable_manager,
                loader=loader,
            )
            task_list.append(t)
        else:
            args_parser = ModuleArgsParser(task_ds)
            try:
                (action, args,
                 delegate_to) = args_parser.parse(skip_action_validation=True)
            except AssibleParserError as e:
                # if the raises exception was created with obj=ds args, then it includes the detail
                # so we dont need to add it so we can just re raise.
                if e._obj:
                    raise
                # But if it wasn't, we can add the yaml object now to get more detail
                raise AssibleParserError(to_native(e), obj=task_ds, orig_exc=e)

            if action in ('include', 'import_tasks', 'include_tasks'):

                if use_handlers:
                    include_class = HandlerTaskInclude
                else:
                    include_class = TaskInclude

                t = include_class.load(task_ds,
                                       block=block,
                                       role=role,
                                       task_include=None,
                                       variable_manager=variable_manager,
                                       loader=loader)

                all_vars = variable_manager.get_vars(play=play, task=t)
                templar = Templar(loader=loader, variables=all_vars)

                # check to see if this include is dynamic or static:
                # 1. the user has set the 'static' option to false or true
                # 2. one of the appropriate config options was set
                if action == 'include_tasks':
                    is_static = False
                elif action == 'import_tasks':
                    is_static = True
                elif t.static is not None:
                    display.deprecated(
                        "The use of 'static' has been deprecated. "
                        "Use 'import_tasks' for static inclusion, or 'include_tasks' for dynamic inclusion",
                        version='2.12',
                        collection_name='assible.builtin')
                    is_static = t.static
                else:
                    is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \
                        (use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \
                        (not templar.is_template(t.args['_raw_params']) and t.all_parents_static() and not t.loop)

                if is_static:
                    if t.loop is not None:
                        if action == 'import_tasks':
                            raise AssibleParserError(
                                "You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.",
                                obj=task_ds)
                        else:
                            raise AssibleParserError(
                                "You cannot use 'static' on an include with a loop",
                                obj=task_ds)

                    # we set a flag to indicate this include was static
                    t.statically_loaded = True

                    # handle relative includes by walking up the list of parent include
                    # tasks and checking the relative result to see if it exists
                    parent_include = block
                    cumulative_path = None

                    found = False
                    subdir = 'tasks'
                    if use_handlers:
                        subdir = 'handlers'
                    while parent_include is not None:
                        if not isinstance(parent_include, TaskInclude):
                            parent_include = parent_include._parent
                            continue
                        try:
                            parent_include_dir = os.path.dirname(
                                templar.template(
                                    parent_include.args.get('_raw_params')))
                        except AssibleUndefinedVariable as e:
                            if not parent_include.statically_loaded:
                                raise AssibleParserError(
                                    "Error when evaluating variable in dynamic parent include path: %s. "
                                    "When using static imports, the parent dynamic include cannot utilize host facts "
                                    "or variables from inventory" %
                                    parent_include.args.get('_raw_params'),
                                    obj=task_ds,
                                    suppress_extended_error=True,
                                    orig_exc=e)
                            raise
                        if cumulative_path is None:
                            cumulative_path = parent_include_dir
                        elif not os.path.isabs(cumulative_path):
                            cumulative_path = os.path.join(
                                parent_include_dir, cumulative_path)
                        include_target = templar.template(
                            t.args['_raw_params'])
                        if t._role:
                            new_basedir = os.path.join(t._role._role_path,
                                                       subdir, cumulative_path)
                            include_file = loader.path_dwim_relative(
                                new_basedir, subdir, include_target)
                        else:
                            include_file = loader.path_dwim_relative(
                                loader.get_basedir(), cumulative_path,
                                include_target)

                        if os.path.exists(include_file):
                            found = True
                            break
                        else:
                            parent_include = parent_include._parent

                    if not found:
                        try:
                            include_target = templar.template(
                                t.args['_raw_params'])
                        except AssibleUndefinedVariable as e:
                            raise AssibleParserError(
                                "Error when evaluating variable in import path: %s.\n\n"
                                "When using static imports, ensure that any variables used in their names are defined in vars/vars_files\n"
                                "or extra-vars passed in from the command line. Static imports cannot use variables from facts or inventory\n"
                                "sources like group or host vars." %
                                t.args['_raw_params'],
                                obj=task_ds,
                                suppress_extended_error=True,
                                orig_exc=e)
                        if t._role:
                            include_file = loader.path_dwim_relative(
                                t._role._role_path, subdir, include_target)
                        else:
                            include_file = loader.path_dwim(include_target)

                    try:
                        data = loader.load_from_file(include_file)
                        if data is None:
                            display.warning(
                                'file %s is empty and had no tasks to include'
                                % include_file)
                            continue
                        elif not isinstance(data, list):
                            raise AssibleParserError(
                                "included task files must contain a list of tasks",
                                obj=data)

                        # since we can't send callbacks here, we display a message directly in
                        # the same fashion used by the on_include callback. We also do it here,
                        # because the recursive nature of helper methods means we may be loading
                        # nested includes, and we want the include order printed correctly
                        display.vv("statically imported: %s" % include_file)
                    except AssibleFileNotFound:
                        if action != 'include' or t.static or \
                           C.DEFAULT_TASK_INCLUDES_STATIC or \
                           C.DEFAULT_HANDLER_INCLUDES_STATIC and use_handlers:
                            raise
                        display.deprecated(
                            "Included file '%s' not found, however since this include is not "
                            "explicitly marked as 'static: yes', we will try and include it dynamically "
                            "later. In the future, this will be an error unless 'static: no' is used "
                            "on the include task. If you do not want missing includes to be considered "
                            "dynamic, use 'static: yes' on the include or set the global assible.cfg "
                            "options to make all includes static for tasks and/or handlers"
                            % include_file,
                            version="2.12",
                            collection_name='assible.builtin')
                        task_list.append(t)
                        continue

                    ti_copy = t.copy(exclude_parent=True)
                    ti_copy._parent = block
                    included_blocks = load_list_of_blocks(
                        data,
                        play=play,
                        parent_block=None,
                        task_include=ti_copy,
                        role=role,
                        use_handlers=use_handlers,
                        loader=loader,
                        variable_manager=variable_manager,
                    )

                    # FIXME: remove once 'include' is removed
                    # pop tags out of the include args, if they were specified there, and assign
                    # them to the include. If the include already had tags specified, we raise an
                    # error so that users know not to specify them both ways
                    tags = ti_copy.vars.pop('tags', [])
                    if isinstance(tags, string_types):
                        tags = tags.split(',')

                    if len(tags) > 0:
                        if action in ('include_tasks', 'import_tasks'):
                            raise AssibleParserError(
                                'You cannot specify "tags" inline to the task, it is a task keyword'
                            )
                        if len(ti_copy.tags) > 0:
                            raise AssibleParserError(
                                "Include tasks should not specify tags in more than one way (both via args and directly on the task). "
                                "Mixing styles in which tags are specified is prohibited for whole import hierarchy, not only for single import statement",
                                obj=task_ds,
                                suppress_extended_error=True,
                            )
                        display.deprecated(
                            "You should not specify tags in the include parameters. All tags should be specified using the task-level option",
                            version="2.12",
                            collection_name='assible.builtin')
                    else:
                        tags = ti_copy.tags[:]

                    # now we extend the tags on each of the included blocks
                    for b in included_blocks:
                        b.tags = list(set(b.tags).union(tags))
                    # END FIXME

                    # FIXME: handlers shouldn't need this special handling, but do
                    #        right now because they don't iterate blocks correctly
                    if use_handlers:
                        for b in included_blocks:
                            task_list.extend(b.block)
                    else:
                        task_list.extend(included_blocks)
                else:
                    t.is_static = False
                    task_list.append(t)

            elif action in ('include_role', 'import_role'):
                ir = IncludeRole.load(
                    task_ds,
                    block=block,
                    role=role,
                    task_include=None,
                    variable_manager=variable_manager,
                    loader=loader,
                )

                #   1. the user has set the 'static' option to false or true
                #   2. one of the appropriate config options was set
                is_static = False
                if action == 'import_role':
                    is_static = True

                elif ir.static is not None:
                    display.deprecated(
                        "The use of 'static' for 'include_role' has been deprecated. "
                        "Use 'import_role' for static inclusion, or 'include_role' for dynamic inclusion",
                        version='2.12',
                        collection_name='assible.builtin')
                    is_static = ir.static

                if is_static:
                    if ir.loop is not None:
                        if action == 'import_role':
                            raise AssibleParserError(
                                "You cannot use loops on 'import_role' statements. You should use 'include_role' instead.",
                                obj=task_ds)
                        else:
                            raise AssibleParserError(
                                "You cannot use 'static' on an include_role with a loop",
                                obj=task_ds)

                    # we set a flag to indicate this include was static
                    ir.statically_loaded = True

                    # template the role name now, if needed
                    all_vars = variable_manager.get_vars(play=play, task=ir)
                    templar = Templar(loader=loader, variables=all_vars)
                    ir._role_name = templar.template(ir._role_name)

                    # uses compiled list from object
                    blocks, _ = ir.get_block_list(
                        variable_manager=variable_manager, loader=loader)
                    task_list.extend(blocks)
                else:
                    # passes task object itself for latter generation of list
                    task_list.append(ir)
            else:
                if use_handlers:
                    t = Handler.load(task_ds,
                                     block=block,
                                     role=role,
                                     task_include=task_include,
                                     variable_manager=variable_manager,
                                     loader=loader)
                else:
                    t = Task.load(task_ds,
                                  block=block,
                                  role=role,
                                  task_include=task_include,
                                  variable_manager=variable_manager,
                                  loader=loader)

                task_list.append(t)

    return task_list
Exemple #13
0
def test_process_include_simulate_free_block_role_tasks(
        mock_iterator, mock_variable_manager):
    """Test loading the same role returns different included files

    In the case of free, we may end up with included files from roles that
    have the same parent but are different tasks. Previously the comparison
    for equality did not check if the tasks were the same and only checked
    that the parents were the same. This lead to some tasks being run
    incorrectly and some tasks being silient dropped."""

    fake_loader = DictDataLoader({
        'include_test.yml':
        "",
        '/etc/assible/roles/foo_role/tasks/task1.yml':
        """
            - debug: msg=task1
        """,
        '/etc/assible/roles/foo_role/tasks/task2.yml':
        """
            - debug: msg=task2
        """,
    })

    hostname = "testhost1"
    hostname2 = "testhost2"

    role1_ds = {
        'name': 'task1 include',
        'include_role': {
            'name': 'foo_role',
            'tasks_from': 'task1.yml'
        }
    }
    role2_ds = {
        'name': 'task2 include',
        'include_role': {
            'name': 'foo_role',
            'tasks_from': 'task2.yml'
        }
    }
    parent_task_ds = {'block': [role1_ds, role2_ds]}
    parent_block = Block.load(parent_task_ds, loader=fake_loader)

    parent_block._play = None

    include_role1_ds = {
        'include_args': {
            'name': 'foo_role',
            'tasks_from': 'task1.yml'
        }
    }
    include_role2_ds = {
        'include_args': {
            'name': 'foo_role',
            'tasks_from': 'task2.yml'
        }
    }

    include_role1 = IncludeRole.load(role1_ds,
                                     block=parent_block,
                                     loader=fake_loader)
    include_role2 = IncludeRole.load(role2_ds,
                                     block=parent_block,
                                     loader=fake_loader)

    result1 = task_result.TaskResult(host=hostname,
                                     task=include_role1,
                                     return_data=include_role1_ds)
    result2 = task_result.TaskResult(host=hostname2,
                                     task=include_role2,
                                     return_data=include_role2_ds)
    results = [result1, result2]

    res = IncludedFile.process_include_results(results, mock_iterator,
                                               fake_loader,
                                               mock_variable_manager)
    assert isinstance(res, list)
    # we should get two different includes
    assert len(res) == 2
    assert res[0]._filename == 'foo_role'
    assert res[1]._filename == 'foo_role'
    # with different tasks
    assert res[0]._task != res[1]._task

    assert res[0]._hosts == ['testhost1']
    assert res[1]._hosts == ['testhost2']

    assert res[0]._args == {}
    assert res[1]._args == {}

    assert res[0]._vars == {}
    assert res[1]._vars == {}
Exemple #14
0
    def __init__(self, inventory, play, play_context, variable_manager, all_vars, start_at_done=False):
        self._play = play
        self._blocks = []
        self._variable_manager = variable_manager

        # Default options to gather
        gather_subset = self._play.gather_subset
        gather_timeout = self._play.gather_timeout
        fact_path = self._play.fact_path

        setup_block = Block(play=self._play)
        # Gathering facts with run_once would copy the facts from one host to
        # the others.
        setup_block.run_once = False
        setup_task = Task(block=setup_block)
        setup_task.action = 'gather_facts'
        setup_task.name = 'Gathering Facts'
        setup_task.args = {
            'gather_subset': gather_subset,
        }

        # Unless play is specifically tagged, gathering should 'always' run
        if not self._play.tags:
            setup_task.tags = ['always']

        if gather_timeout:
            setup_task.args['gather_timeout'] = gather_timeout
        if fact_path:
            setup_task.args['fact_path'] = fact_path
        setup_task.set_loader(self._play._loader)
        # short circuit fact gathering if the entire playbook is conditional
        if self._play._included_conditional is not None:
            setup_task.when = self._play._included_conditional[:]
        setup_block.block = [setup_task]

        setup_block = setup_block.filter_tagged_tasks(all_vars)
        self._blocks.append(setup_block)

        for block in self._play.compile():
            new_block = block.filter_tagged_tasks(all_vars)
            if new_block.has_tasks():
                self._blocks.append(new_block)

        self._host_states = {}
        start_at_matched = False
        batch = inventory.get_hosts(self._play.hosts, order=self._play.order)
        self.batch_size = len(batch)
        for host in batch:
            self._host_states[host.name] = HostState(blocks=self._blocks)
            # if we're looking to start at a specific task, iterate through
            # the tasks for this host until we find the specified task
            if play_context.start_at_task is not None and not start_at_done:
                while True:
                    (s, task) = self.get_next_task_for_host(host, peek=True)
                    if s.run_state == self.ITERATING_COMPLETE:
                        break
                    if task.name == play_context.start_at_task or (task.name and fnmatch.fnmatch(task.name, play_context.start_at_task)) or \
                       task.get_name() == play_context.start_at_task or fnmatch.fnmatch(task.get_name(), play_context.start_at_task):
                        start_at_matched = True
                        break
                    else:
                        self.get_next_task_for_host(host)

                # finally, reset the host's state to ITERATING_SETUP
                if start_at_matched:
                    self._host_states[host.name].did_start_at_task = True
                    self._host_states[host.name].run_state = self.ITERATING_SETUP

        if start_at_matched:
            # we have our match, so clear the start_at_task field on the
            # play context to flag that we've started at a task (and future
            # plays won't try to advance)
            play_context.start_at_task = None