Example #1
0
    def _check_for_new_args(self, doc):
        if self._is_new_module():
            return

        with CaptureStd():
            try:
                existing = module_loader.find_plugin(self.name, mod_type='.py')
                existing_doc, _, _ = get_docstring(existing, verbose=True)
                existing_options = existing_doc.get('options', {})
            except AssertionError:
                fragment = doc['extends_documentation_fragment']
                self.warnings.append('Pre-existing DOCUMENTATION fragment '
                                     'missing: %s' % fragment)
                return
            except Exception as e:
                self.warning_traces.append(e)
                self.warnings.append('Unknown pre-existing DOCUMENTATION '
                                     'error, see TRACE. Submodule refs may '
                                     'need updated')
                return

        try:
            mod_version_added = StrictVersion(
                str(existing_doc.get('version_added', '0.0'))
            )
        except ValueError:
            mod_version_added = StrictVersion('0.0')

        options = doc.get('options', {})

        should_be = '.'.join(ansible_version.split('.')[:2])
        strict_ansible_version = StrictVersion(should_be)

        for option, details in options.iteritems():
            new = not bool(existing_options.get(option))
            if not new:
                continue

            try:
                version_added = StrictVersion(
                    str(details.get('version_added', '0.0'))
                )
            except ValueError:
                version_added = details.get('version_added', '0.0')
                self.errors.append('version_added for new option (%s) '
                                   'is not a valid version number: %r' %
                                   (option, version_added))
                continue
            except:
                # If there is any other exception it should have been caught
                # in schema validation, so we won't duplicate errors by
                # listing it again
                continue

            if (strict_ansible_version != mod_version_added and
                    (version_added < strict_ansible_version or
                     strict_ansible_version < version_added)):
                self.errors.append('version_added for new option (%s) should '
                                   'be %s. Currently %s' %
                                   (option, should_be, version_added))
Example #2
0
    def _validate_docs(self):
        doc_info = self._get_docs()
        try:
            doc = yaml.safe_load(doc_info['DOCUMENTATION']['value'])
        except yaml.YAMLError as e:
            doc = None
            # This offsets the error line number to where the
            # DOCUMENTATION starts so we can just go to that line in the
            # module
            e.problem_mark.line += (
                doc_info['DOCUMENTATION']['lineno'] - 1
            )
            e.problem_mark.name = '%s.DOCUMENTATION' % self.name
            self.traces.append(e)
            self.errors.append('DOCUMENTATION is not valid YAML. Line %d '
                               'column %d' %
                               (e.problem_mark.line + 1,
                                e.problem_mark.column + 1))
        except AttributeError:
            self.errors.append('No DOCUMENTATION provided')
        else:
            with CaptureStd():
                try:
                    get_docstring(self.path, verbose=True)
                except AssertionError:
                    fragment = doc['extends_documentation_fragment']
                    self.errors.append('DOCUMENTATION fragment missing: %s' %
                                       fragment)
                except Exception as e:
                    self.traces.append(e)
                    self.errors.append('Unknown DOCUMENTATION error, see '
                                       'TRACE')

            self._validate_docs_schema(doc)
            self._check_version_added(doc)
            self._check_for_new_args(doc)

        if not bool(doc_info['EXAMPLES']['value']):
            self.errors.append('No EXAMPLES provided')

        if not bool(doc_info['RETURN']['value']):
            if self._is_new_module():
                self.errors.append('No RETURN documentation provided')
            else:
                self.warnings.append('No RETURN provided')
        else:
            try:
                yaml.safe_load(doc_info['RETURN']['value'])
            except yaml.YAMLError as e:
                e.problem_mark.line += (
                    doc_info['RETURN']['lineno'] - 1
                )
                e.problem_mark.name = '%s.RETURN' % self.name
                self.errors.append('RETURN is not valid YAML. Line %d '
                                   'column %d' %
                                   (e.problem_mark.line + 1,
                                    e.problem_mark.column + 1))
                self.traces.append(e)
Example #3
0
    def _check_for_new_args(self, doc):
        if not self.base_branch or self._is_new_module():
            return

        with CaptureStd():
            try:
                existing_doc, _, _, _ = get_docstring(self.base_module,
                                                      verbose=True)
                existing_options = existing_doc.get('options', {})
            except AssertionError:
                fragment = doc['extends_documentation_fragment']
                self.reporter.warning(
                    path=self.object_path,
                    code=392,
                    msg='Pre-existing DOCUMENTATION fragment missing: %s' %
                    fragment)
                return
            except Exception as e:
                self.reporter.warning_trace(path=self.object_path, tracebk=e)
                self.reporter.warning(
                    path=self.object_path,
                    code=391,
                    msg=('Unknown pre-existing DOCUMENTATION '
                         'error, see TRACE. Submodule refs may '
                         'need updated'))
                return

        try:
            mod_version_added = StrictVersion(
                str(existing_doc.get('version_added', '0.0')))
        except ValueError:
            mod_version_added = StrictVersion('0.0')

        options = doc.get('options', {})

        should_be = '.'.join(ansible_version.split('.')[:2])
        strict_ansible_version = StrictVersion(should_be)

        for option, details in options.items():
            try:
                names = [option] + details.get('aliases', [])
            except AttributeError:
                # Reporting of this syntax error will be handled by schema validation.
                continue

            if any(name in existing_options for name in names):
                continue

            try:
                version_added = StrictVersion(
                    str(details.get('version_added', '0.0')))
            except ValueError:
                version_added = details.get('version_added', '0.0')
                self.reporter.error(path=self.object_path,
                                    code=308,
                                    msg=('version_added for new option (%s) '
                                         'is not a valid version number: %r' %
                                         (option, version_added)))
                continue
            except:
                # If there is any other exception it should have been caught
                # in schema validation, so we won't duplicate errors by
                # listing it again
                continue

            if (strict_ansible_version != mod_version_added
                    and (version_added < strict_ansible_version
                         or strict_ansible_version < version_added)):
                self.reporter.error(
                    path=self.object_path,
                    code=309,
                    msg=('version_added for new option (%s) should '
                         'be %s. Currently %s' %
                         (option, should_be, version_added)))
Example #4
0
    def _validate_docs(self):
        doc_info = self._get_docs()
        deprecated = False
        if not bool(doc_info['DOCUMENTATION']['value']):
            self.reporter.error(path=self.object_path,
                                code=301,
                                msg='No DOCUMENTATION provided')
        else:
            doc, errors, traces = parse_yaml(
                doc_info['DOCUMENTATION']['value'],
                doc_info['DOCUMENTATION']['lineno'], self.name,
                'DOCUMENTATION')
            for error in errors:
                self.reporter.error(path=self.object_path, code=302, **error)
            for trace in traces:
                self.reporter.trace(path=self.object_path, tracebk=trace)
            if not errors and not traces:
                with CaptureStd():
                    try:
                        get_docstring(self.path, verbose=True)
                    except AssertionError:
                        fragment = doc['extends_documentation_fragment']
                        self.reporter.error(
                            path=self.object_path,
                            code=303,
                            msg='DOCUMENTATION fragment missing: %s' %
                            fragment)
                    except Exception:
                        self.reporter.trace(path=self.object_path,
                                            tracebk=traceback.format_exc())
                        self.reporter.error(
                            path=self.object_path,
                            code=304,
                            msg='Unknown DOCUMENTATION error, see TRACE')

                if 'options' in doc and doc['options'] is None and doc.get(
                        'extends_documentation_fragment'):
                    self.reporter.error(
                        path=self.object_path,
                        code=304,
                        msg=
                        ('DOCUMENTATION.options must be a dictionary/hash when used '
                         'with DOCUMENTATION.extends_documentation_fragment'))

                if self.object_name.startswith('_') and not os.path.islink(
                        self.object_path):
                    deprecated = True
                    if 'deprecated' not in doc or not doc.get('deprecated'):
                        self.reporter.error(
                            path=self.object_path,
                            code=318,
                            msg=
                            'Module deprecated, but DOCUMENTATION.deprecated is missing'
                        )

                if os.path.islink(self.object_path):
                    # This module has an alias, which we can tell as it's a symlink
                    # Rather than checking for `module: $filename` we need to check against the true filename
                    self._validate_docs_schema(
                        doc,
                        doc_schema(
                            os.readlink(self.object_path).split('.')[0]),
                        'DOCUMENTATION', 305)
                else:
                    # This is the normal case
                    self._validate_docs_schema(
                        doc, doc_schema(self.object_name.split('.')[0]),
                        'DOCUMENTATION', 305)

                self._check_version_added(doc)
                self._check_for_new_args(doc)

        if not bool(doc_info['EXAMPLES']['value']):
            self.reporter.error(path=self.object_path,
                                code=310,
                                msg='No EXAMPLES provided')
        else:
            _, errors, traces = parse_yaml(doc_info['EXAMPLES']['value'],
                                           doc_info['EXAMPLES']['lineno'],
                                           self.name,
                                           'EXAMPLES',
                                           load_all=True)
            for error in errors:
                self.reporter.error(path=self.object_path, code=311, **error)
            for trace in traces:
                self.reporter.trace(path=self.object_path, tracebk=trace)

        if not bool(doc_info['RETURN']['value']):
            if self._is_new_module():
                self.reporter.error(path=self.object_path,
                                    code=312,
                                    msg='No RETURN provided')
            else:
                self.reporter.warning(path=self.object_path,
                                      code=312,
                                      msg='No RETURN provided')
        else:
            data, errors, traces = parse_yaml(doc_info['RETURN']['value'],
                                              doc_info['RETURN']['lineno'],
                                              self.name, 'RETURN')
            if data:
                for ret_key in data:
                    self._validate_docs_schema(data[ret_key],
                                               return_schema(data[ret_key]),
                                               'RETURN.%s' % ret_key, 319)

            for error in errors:
                self.reporter.error(path=self.object_path, code=313, **error)
            for trace in traces:
                self.reporter.trace(path=self.object_path, tracebk=trace)

        if not bool(doc_info['ANSIBLE_METADATA']['value']):
            self.reporter.error(path=self.object_path,
                                code=314,
                                msg='No ANSIBLE_METADATA provided')
        else:
            metadata = None
            if isinstance(doc_info['ANSIBLE_METADATA']['value'], ast.Dict):
                metadata = ast.literal_eval(
                    doc_info['ANSIBLE_METADATA']['value'])
            else:
                metadata, errors, traces = parse_yaml(
                    doc_info['ANSIBLE_METADATA']['value'].s,
                    doc_info['ANSIBLE_METADATA']['lineno'], self.name,
                    'ANSIBLE_METADATA')
                for error in errors:
                    self.reporter.error(path=self.object_path,
                                        code=315,
                                        **error)
                for trace in traces:
                    self.reporter.trace(path=self.object_path, tracebk=trace)

            if metadata:
                self._validate_docs_schema(metadata,
                                           metadata_1_1_schema(deprecated),
                                           'ANSIBLE_METADATA', 316)

        return doc_info