Exemplo n.º 1
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)
Exemplo n.º 2
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)