示例#1
0
def test_on_collection_load():
    finder = get_default_finder()
    reset_collections_loader_state(finder)

    load_handler = MagicMock()
    AnsibleCollectionConfig.on_collection_load += load_handler

    m = import_module('ansible_collections.testns.testcoll')
    load_handler.assert_called_once_with(collection_name='testns.testcoll', collection_path=os.path.dirname(m.__file__))

    _meta = _get_collection_metadata('testns.testcoll')
    assert _meta
    # FIXME: compare to disk

    finder = get_default_finder()
    reset_collections_loader_state(finder)

    AnsibleCollectionConfig.on_collection_load += MagicMock(side_effect=Exception('bang'))
    with pytest.raises(Exception) as ex:
        import_module('ansible_collections.testns.testcoll')
    assert 'bang' in str(ex.value)
示例#2
0
    def __getitem__(self, key):
        try:
            if not isinstance(key, string_types):
                raise ValueError('key must be a string')

            key = to_native(key)

            if '.' not in key:  # might be a built-in or legacy, check the delegatee dict first, then try for a last-chance base redirect
                func = self._delegatee.get(key)

                if func:
                    return func

                # didn't find it in the pre-built Jinja env, assume it's a former builtin and follow the normal routing path
                leaf_key = key
                key = 'ansible.builtin.' + key
            else:
                leaf_key = key.split('.')[-1]

            acr = AnsibleCollectionRef.try_parse_fqcr(key, self._dirname)

            if not acr:
                raise KeyError('invalid plugin name: {0}'.format(key))

            ts = _get_collection_metadata(acr.collection)

            # TODO: implement support for collection-backed redirect (currently only builtin)
            # TODO: implement cycle detection (unified across collection redir as well)

            routing_entry = ts.get('plugin_routing', {}).get(self._dirname, {}).get(leaf_key, {})

            deprecation_entry = routing_entry.get('deprecation')
            if deprecation_entry:
                warning_text = deprecation_entry.get('warning_text')
                removal_date = deprecation_entry.get('removal_date')
                removal_version = deprecation_entry.get('removal_version')

                if not warning_text:
                    warning_text = '{0} "{1}" is deprecated'.format(self._dirname, key)

                display.deprecated(warning_text, version=removal_version, date=removal_date, collection_name=acr.collection)

            tombstone_entry = routing_entry.get('tombstone')

            if tombstone_entry:
                warning_text = tombstone_entry.get('warning_text')
                removal_date = tombstone_entry.get('removal_date')
                removal_version = tombstone_entry.get('removal_version')

                if not warning_text:
                    warning_text = '{0} "{1}" has been removed'.format(self._dirname, key)

                exc_msg = display.get_deprecation_message(warning_text, version=removal_version, date=removal_date,
                                                          collection_name=acr.collection, removed=True)

                raise AnsiblePluginRemovedError(exc_msg)

            redirect_fqcr = routing_entry.get('redirect', None)
            if redirect_fqcr:
                acr = AnsibleCollectionRef.from_fqcr(ref=redirect_fqcr, ref_type=self._dirname)
                display.vvv('redirecting {0} {1} to {2}.{3}'.format(self._dirname, key, acr.collection, acr.resource))
                key = redirect_fqcr
            # TODO: handle recursive forwarding (not necessary for builtin, but definitely for further collection redirs)

            func = self._collection_jinja_func_cache.get(key)

            if func:
                return func

            try:
                pkg = import_module(acr.n_python_package_name)
            except ImportError:
                raise KeyError()

            parent_prefix = acr.collection

            if acr.subdirs:
                parent_prefix = '{0}.{1}'.format(parent_prefix, acr.subdirs)

            # TODO: implement collection-level redirect

            for dummy, module_name, ispkg in pkgutil.iter_modules(pkg.__path__, prefix=parent_prefix + '.'):
                if ispkg:
                    continue

                try:
                    plugin_impl = self._pluginloader.get(module_name)
                except Exception as e:
                    raise TemplateSyntaxError(to_native(e), 0)

                method_map = getattr(plugin_impl, self._method_map_name)

                for func_name, func in iteritems(method_map()):
                    fq_name = '.'.join((parent_prefix, func_name))
                    # FIXME: detect/warn on intra-collection function name collisions
                    if USE_JINJA2_NATIVE and func_name in C.STRING_TYPE_FILTERS:
                        self._collection_jinja_func_cache[fq_name] = _wrap_native_text(func)
                    else:
                        self._collection_jinja_func_cache[fq_name] = _unroll_iterator(func)

            function_impl = self._collection_jinja_func_cache[key]
            return function_impl
        except AnsiblePluginRemovedError as apre:
            raise TemplateSyntaxError(to_native(apre), 0)
        except KeyError:
            raise
        except Exception as ex:
            display.warning('an unexpected error occurred during Jinja2 environment setup: {0}'.format(to_native(ex)))
            display.vvv('exception during Jinja2 environment setup: {0}'.format(format_exc()))
            raise TemplateSyntaxError(to_native(ex), 0)
示例#3
0
文件: base.py 项目: phy1729/ansible
    def _resolve_group(self, fq_group_name, mandatory=True):
        if not AnsibleCollectionRef.is_valid_fqcr(fq_group_name):
            collection_name = 'ansible.builtin'
            fq_group_name = collection_name + '.' + fq_group_name
        else:
            collection_name = '.'.join(fq_group_name.split('.')[0:2])

        # Check if the group has already been resolved and cached
        if fq_group_name in self.play._group_actions:
            return fq_group_name, self.play._group_actions[fq_group_name]

        try:
            action_groups = _get_collection_metadata(collection_name).get('action_groups', {})
        except ValueError:
            if not mandatory:
                display.vvvvv("Error loading module_defaults: could not resolve the module_defaults group %s" % fq_group_name)
                return fq_group_name, []

            raise AnsibleParserError("Error loading module_defaults: could not resolve the module_defaults group %s" % fq_group_name)

        # The collection may or may not use the fully qualified name
        # Don't fail if the group doesn't exist in the collection
        resource_name = fq_group_name.split(collection_name + '.')[-1]
        action_group = action_groups.get(
            fq_group_name,
            action_groups.get(resource_name)
        )
        if action_group is None:
            if not mandatory:
                display.vvvvv("Error loading module_defaults: could not resolve the module_defaults group %s" % fq_group_name)
                return fq_group_name, []
            raise AnsibleParserError("Error loading module_defaults: could not resolve the module_defaults group %s" % fq_group_name)

        resolved_actions = []
        include_groups = []

        found_group_metadata = False
        for action in action_group:
            # Everything should be a string except the metadata entry
            if not isinstance(action, string_types):
                _validate_action_group_metadata(action, found_group_metadata, fq_group_name)

                if isinstance(action['metadata'], dict):
                    found_group_metadata = True

                    include_groups = action['metadata'].get('extend_group', [])
                    if isinstance(include_groups, string_types):
                        include_groups = [include_groups]
                    if not isinstance(include_groups, list):
                        # Bad entries may be a warning above, but prevent tracebacks by setting it back to the acceptable type.
                        include_groups = []
                continue

            # The collection may or may not use the fully qualified name.
            # If not, it's part of the current collection.
            if not AnsibleCollectionRef.is_valid_fqcr(action):
                action = collection_name + '.' + action
            resolved_action = self._resolve_action(action, mandatory=False)
            if resolved_action:
                resolved_actions.append(resolved_action)

        for action in resolved_actions:
            if action not in self.play._action_groups:
                self.play._action_groups[action] = []
            self.play._action_groups[action].append(fq_group_name)

        self.play._group_actions[fq_group_name] = resolved_actions

        # Resolve extended groups last, after caching the group in case they recursively refer to each other
        for include_group in include_groups:
            if not AnsibleCollectionRef.is_valid_fqcr(include_group):
                include_group_collection = collection_name
                include_group = collection_name + '.' + include_group
            else:
                include_group_collection = '.'.join(include_group.split('.')[0:2])

            dummy, group_actions = self._resolve_group(include_group, mandatory=False)

            for action in group_actions:
                if action not in self.play._action_groups:
                    self.play._action_groups[action] = []
                self.play._action_groups[action].append(fq_group_name)

            self.play._group_actions[fq_group_name].extend(group_actions)
            resolved_actions.extend(group_actions)

        return fq_group_name, resolved_actions
示例#4
0
    def __getitem__(self, key):
        original_key = key
        self._load_ansible_plugins()

        try:
            if not isinstance(key, string_types):
                raise ValueError('key must be a string')

            key = to_native(key)

            if '.' not in key:  # might be a built-in or legacy, check the delegatee dict first, then try for a last-chance base redirect
                func = self._delegatee.get(key)

                if func:
                    return func

            key, leaf_key = get_fqcr_and_name(key)
            seen = set()

            while True:
                if key in seen:
                    raise TemplateSyntaxError(
                        'recursive collection redirect found for %r' %
                        original_key, 0)
                seen.add(key)

                acr = AnsibleCollectionRef.try_parse_fqcr(key, self._dirname)

                if not acr:
                    raise KeyError('invalid plugin name: {0}'.format(key))

                ts = _get_collection_metadata(acr.collection)

                # TODO: implement cycle detection (unified across collection redir as well)

                routing_entry = ts.get('plugin_routing',
                                       {}).get(self._dirname,
                                               {}).get(leaf_key, {})

                deprecation_entry = routing_entry.get('deprecation')
                if deprecation_entry:
                    warning_text = deprecation_entry.get('warning_text')
                    removal_date = deprecation_entry.get('removal_date')
                    removal_version = deprecation_entry.get('removal_version')

                    if not warning_text:
                        warning_text = '{0} "{1}" is deprecated'.format(
                            self._dirname, key)

                    display.deprecated(warning_text,
                                       version=removal_version,
                                       date=removal_date,
                                       collection_name=acr.collection)

                tombstone_entry = routing_entry.get('tombstone')

                if tombstone_entry:
                    warning_text = tombstone_entry.get('warning_text')
                    removal_date = tombstone_entry.get('removal_date')
                    removal_version = tombstone_entry.get('removal_version')

                    if not warning_text:
                        warning_text = '{0} "{1}" has been removed'.format(
                            self._dirname, key)

                    exc_msg = display.get_deprecation_message(
                        warning_text,
                        version=removal_version,
                        date=removal_date,
                        collection_name=acr.collection,
                        removed=True)

                    raise AnsiblePluginRemovedError(exc_msg)

                redirect = routing_entry.get('redirect', None)
                if redirect:
                    next_key, leaf_key = get_fqcr_and_name(
                        redirect, collection=acr.collection)
                    display.vvv(
                        'redirecting (type: {0}) {1}.{2} to {3}'.format(
                            self._dirname, acr.collection, acr.resource,
                            next_key))
                    key = next_key
                else:
                    break

            func = self._collection_jinja_func_cache.get(key)

            if func:
                return func

            try:
                pkg = import_module(acr.n_python_package_name)
            except ImportError:
                raise KeyError()

            parent_prefix = acr.collection

            if acr.subdirs:
                parent_prefix = '{0}.{1}'.format(parent_prefix, acr.subdirs)

            # TODO: implement collection-level redirect

            for dummy, module_name, ispkg in pkgutil.iter_modules(
                    pkg.__path__, prefix=parent_prefix + '.'):
                if ispkg:
                    continue

                try:
                    plugin_impl = self._pluginloader.get(module_name)
                except Exception as e:
                    raise TemplateSyntaxError(to_native(e), 0)

                try:
                    method_map = getattr(plugin_impl, self._method_map_name)
                    func_items = method_map().items()
                except Exception as e:
                    display.warning(
                        "Skipping %s plugin %s as it seems to be invalid: %r" %
                        (self._dirname, to_text(
                            plugin_impl._original_path), e), )
                    continue

                for func_name, func in func_items:
                    fq_name = '.'.join((parent_prefix, func_name))
                    # FIXME: detect/warn on intra-collection function name collisions
                    if self._pluginloader.class_name == 'FilterModule':
                        if fq_name.startswith(('ansible.builtin.', 'ansible.legacy.')) and \
                                func_name in C.STRING_TYPE_FILTERS:
                            self._collection_jinja_func_cache[
                                fq_name] = _wrap_native_text(func)
                        else:
                            self._collection_jinja_func_cache[
                                fq_name] = _unroll_iterator(func)
                    else:
                        self._collection_jinja_func_cache[fq_name] = func

            function_impl = self._collection_jinja_func_cache[key]
            return function_impl
        except AnsiblePluginRemovedError as apre:
            raise TemplateSyntaxError(to_native(apre), 0)
        except KeyError:
            raise
        except Exception as ex:
            display.warning(
                'an unexpected error occurred during Jinja2 environment setup: {0}'
                .format(to_native(ex)))
            display.vvv(
                'exception during Jinja2 environment setup: {0}'.format(
                    format_exc()))
            raise TemplateSyntaxError(to_native(ex), 0)
示例#5
0
    def __getitem__(self, key):
        try:
            if not isinstance(key, string_types):
                raise ValueError('key must be a string')

            key = to_native(key)

            if '.' not in key:  # might be a built-in or legacy, check the delegatee dict first, then try for a last-chance base redirect
                func = self._delegatee.get(key)

                if func:
                    return func

                ts = _get_collection_metadata('ansible.builtin')

                # TODO: implement support for collection-backed redirect (currently only builtin)
                # TODO: implement cycle detection (unified across collection redir as well)
                redirect_fqcr = ts.get('plugin_routing', {}).get(self._dirname, {}).get(key, {}).get('redirect', None)
                if redirect_fqcr:
                    acr = AnsibleCollectionRef.from_fqcr(ref=redirect_fqcr, ref_type=self._dirname)
                    display.vvv('redirecting {0} {1} to {2}.{3}'.format(self._dirname, key, acr.collection, acr.resource))
                    key = redirect_fqcr
                # TODO: handle recursive forwarding (not necessary for builtin, but definitely for further collection redirs)

            func = self._collection_jinja_func_cache.get(key)

            if func:
                return func

            acr = AnsibleCollectionRef.try_parse_fqcr(key, self._dirname)

            if not acr:
                raise KeyError('invalid plugin name: {0}'.format(key))

            try:
                pkg = import_module(acr.n_python_package_name)
            except ImportError:
                raise KeyError()

            parent_prefix = acr.collection

            if acr.subdirs:
                parent_prefix = '{0}.{1}'.format(parent_prefix, acr.subdirs)

            # TODO: implement collection-level redirect

            for dummy, module_name, ispkg in pkgutil.iter_modules(pkg.__path__, prefix=parent_prefix + '.'):
                if ispkg:
                    continue

                try:
                    plugin_impl = self._pluginloader.get(module_name)
                except Exception as e:
                    raise TemplateSyntaxError(to_native(e), 0)

                method_map = getattr(plugin_impl, self._method_map_name)

                for f in iteritems(method_map()):
                    fq_name = '.'.join((parent_prefix, f[0]))
                    # FIXME: detect/warn on intra-collection function name collisions
                    self._collection_jinja_func_cache[fq_name] = f[1]

            function_impl = self._collection_jinja_func_cache[key]
            return function_impl
        except KeyError:
            raise
        except Exception as ex:
            display.warning('an unexpected error occurred during Jinja2 environment setup: {0}'.format(to_native(ex)))
            display.vvv('exception during Jinja2 environment setup: {0}'.format(format_exc()))
            raise