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))
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)
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)))
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