diff --git a/doc/requirements.txt b/doc/requirements.txt index 52eb88b4d..db0abbaba 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,4 +4,5 @@ openstackdocstheme>=2.2.2 # Apache-2.0 reno>=3.1.0 # Apache-2.0 doc8>=0.8.0 # Apache-2.0 bashate>=0.6.0 # Apache-2.0 +ruamel.yaml>=0.15.5 # MIT six>=1.11.0 # MIT diff --git a/doc/source/_exts/ansible-autodoc.py b/doc/source/_exts/ansible-autodoc.py index 47ae9048e..cc815677a 100644 --- a/doc/source/_exts/ansible-autodoc.py +++ b/doc/source/_exts/ansible-autodoc.py @@ -14,7 +14,7 @@ # under the License. -import importlib.util +import imp import os from docutils import core @@ -23,7 +23,48 @@ from docutils.parsers.rst import Directive from docutils.parsers import rst from docutils.writers.html4css1 import Writer +from sphinx import addnodes + import yaml +from ruamel.yaml import YAML as RYAML + +try: + import io + StringIO = io.StringIO +except ImportError: + import StringIO + + +class DocYaml(RYAML): + def _license_filter(self, data): + """This will filter out our boilerplate license heading in return data. + + The filter is used to allow documentation we're creating in variable + files to be rendered more beautifully. + """ + lines = list() + mark = True + for line in data.splitlines(): + if '# Copyright' in line: + mark = False + if mark: + lines.append(line) + if '# under the License' in line: + mark = True + return '\n'.join(lines) + + def dump(self, data, stream=None, **kw): + if not stream: + stream = StringIO() + try: + RYAML.dump(self, data, stream, **kw) + return self._license_filter(stream.getvalue().strip()) + finally: + stream.close() + + +DOCYAML = DocYaml() +DOCYAML.default_flow_style = False class AnsibleAutoPluginDirective(Directive): @@ -93,16 +134,11 @@ class AnsibleAutoPluginDirective(Directive): @staticmethod def load_module(filename): - module_spec = importlib.util.spec_from_file_location( - '__ansible_module__', filename - ) - module = importlib.util.module_from_spec(module_spec) - module_spec.loader.exec_module(module) - return module + return imp.load_source('__ansible_module__', filename) @staticmethod def build_documentation(module): - docs = yaml.safe_load(module.DOCUMENTATION) + docs = DOCYAML.load(module.DOCUMENTATION) doc_data = dict() doc_data['docs'] = docs['description'] doc_data['author'] = docs.get('author', list()) @@ -111,12 +147,10 @@ class AnsibleAutoPluginDirective(Directive): @staticmethod def build_examples(module): - examples = yaml.safe_load(module.EXAMPLES) + examples = DOCYAML.load(module.EXAMPLES) return_examples = list() for example in examples: - return_examples.append( - yaml.safe_dump([example], default_flow_style=False) - ) + return_examples.append(DOCYAML.dump([example])) return return_examples def _raw_html_block(self, data): @@ -140,10 +174,7 @@ class AnsibleAutoPluginDirective(Directive): def _literal_block(data, language='yaml', dump_data=True): if dump_data: literal = nodes.literal_block( - text=yaml.safe_dump( - data, - default_flow_style=False - ) + text=DOCYAML.dump(data) ) else: literal = nodes.literal_block(text=data) @@ -174,92 +205,110 @@ class AnsibleAutoPluginDirective(Directive): def _run_role(self, role): section = self._section_block( - title='Role Documentation', + title="Role Documentation", text='Welcome to the "{}" role documentation.'.format( os.path.basename(role) - ) + ), ) + molecule_defaults = None abspath_role = os.path.dirname(os.path.abspath(role)) - molecule_shared_file = os.path.join(os.path.dirname(abspath_role), - ".config/molecule/config.yml") + molecule_shared_file = os.path.join( + os.path.dirname(abspath_role), ".config/molecule/config.yml" + ) if os.path.exists(molecule_shared_file): with open(molecule_shared_file) as msf: - molecule_defaults = yaml.safe_load(msf.read()) + molecule_defaults = DOCYAML.load(msf.read()) - defaults_file = os.path.join(role, 'defaults', 'main.yml') + defaults_file = os.path.join(role, "defaults", "main.yml") if os.path.exists(defaults_file): with open(defaults_file) as f: - role_defaults = yaml.safe_load(f.read()) + role_defaults = DOCYAML.load(f.read()) section.append( self._yaml_section( to_yaml_data=role_defaults, - section_title='Role Defaults', - section_text='This section highlights all of the defaults' - ' and variables set within the "{}"' - ' role.'.format( - os.path.basename(role) - ) + section_title="Role Defaults", + section_text="This section highlights all of the defaults" + ' and variables set within the "{}"' + " role.".format(os.path.basename(role)), ) ) - vars_path = os.path.join(role, 'vars') + vars_path = os.path.join(role, "vars") if os.path.exists(vars_path): for v_file in os.listdir(vars_path): vars_file = os.path.join(vars_path, v_file) with open(vars_file) as f: - vars_values = yaml.safe_load(f.read()) + vars_values = DOCYAML.load(f.read()) section.append( self._yaml_section( to_yaml_data=vars_values, - section_title='Role Variables: {}'.format(v_file) + section_title="Role Variables: {}".format(v_file), ) ) + test_list = nodes.field_list() test_section = self._section_block( - title='Molecule Scenarios', + title="Molecule Scenarios", text='Molecule is being used to test the "{}" role. The' - ' following section highlights the drivers in service' - ' and provides an example playbook showing how the role' - ' is leveraged.'.format( - os.path.basename(role) - ) + " following section highlights the drivers in service" + " and provides an example playbook showing how the role" + " is leveraged.".format(os.path.basename(role)), ) - molecule_path = os.path.join(role, 'molecule') + + molecule_path = os.path.join(role, "molecule") if os.path.exists(molecule_path): for test in os.listdir(molecule_path): molecule_section = self._section_block( - title='Scenario: {}'.format(test) + title="Scenario: {}".format(test) ) - molecule_file = os.path.join( - molecule_path, - test, - 'molecule.yml' - ) - with open(molecule_file) as f: - molecule_conf = yaml.safe_load(f.read()) + molecule_file = os.path.join(molecule_path, test, "molecule.yml") + if not os.path.exists(molecule_file): + continue + with open(molecule_file) as f: + molecule_conf = DOCYAML.load(f.read()) + + # if molecule.yml file from the scenarios, we get the + # information from the molecule shared configuration file. if not molecule_conf: molecule_conf = molecule_defaults - molecule_section.append( - self._yaml_section( - to_yaml_data=molecule_conf, - section_title='Example {} configuration'.format(test) - ) - ) - - default_playbook = [molecule_path, test, 'converge.yml'] - - provisioner_data = None # Now that we use a shared molecule configuration file, the # molecule.yml file in the role scenarios could be empty or # contains only overriding keys. - if molecule_conf: - provisioner_data = molecule_conf.get('provisioner') - else: - provisioner_data = molecule_defaults.get('provisioner') + driver_data = molecule_conf.get('driver', + molecule_defaults.get('driver')) + + if driver_data: + molecule_section.append( + nodes.field_name(text="Driver: {}".format(driver_data["name"])) + ) + + options = driver_data.get("options") + if options: + molecule_section.append( + self._yaml_section( + to_yaml_data=options, section_title="Molecule Options" + ) + ) + + platforms_data = molecule_conf.get('platforms', + molecule_defaults.get('platforms')) + + if platforms_data: + molecule_section.append( + self._yaml_section( + to_yaml_data=platforms_data, + section_title="Molecule Platform(s)", + ) + ) + + default_playbook = [molecule_path, test, "converge.yml"] + + provisioner_data = molecule_conf.get('provisioner', + molecule_defaults.get('provisioner')) if provisioner_data: inventory = provisioner_data.get('inventory') @@ -267,9 +316,10 @@ class AnsibleAutoPluginDirective(Directive): molecule_section.append( self._yaml_section( to_yaml_data=inventory, - section_title='Molecule Inventory' + section_title="Molecule Inventory", ) ) + try: converge = provisioner_data['playbooks']['converge'] default_playbook = default_playbook[:-1] + [converge] @@ -277,36 +327,35 @@ class AnsibleAutoPluginDirective(Directive): pass molecule_playbook_path = os.path.join(*default_playbook) + with open(molecule_playbook_path) as f: - molecule_playbook = yaml.safe_load(f.read()) + molecule_playbook = DOCYAML.load(f.read()) molecule_section.append( self._yaml_section( to_yaml_data=molecule_playbook, - section_title='Example {} playbook'.format(test) + section_title="Example {} playbook".format(test), ) ) - test_section.append(molecule_section) + test_list.append(molecule_section) else: + test_section.append(test_list) section.append(test_section) self.run_returns.append(section) # Document any libraries nested within the role - library_path = os.path.join(role, 'library') + library_path = os.path.join(role, "library") if os.path.exists(library_path): self.options['documentation'] = True self.options['examples'] = True for lib in os.listdir(library_path): - if lib.endswith('.py'): + if lib.endswith(".py"): self._run_module( module=self.load_module( - filename=os.path.join( - library_path, - lib - ) + filename=os.path.join(library_path, lib) ), - module_title='Embedded module: {}'.format(lib), - example_title='Examples for embedded module' + module_title="Embedded module: {}".format(lib), + example_title="Examples for embedded module", ) def _run_module(self, module, module_title="Module Documentation",