| #!/usr/bin/env python |
| # defaultsdoc.py - documentation for ansible default vaules |
| |
| # Copyright 2017-present Open Networking Foundation |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import fnmatch |
| import jinja2 |
| import logging |
| import os |
| import pprint |
| import re |
| import sys |
| import xml.etree.ElementTree as ET |
| import yaml |
| import markedyaml |
| |
| # logging setup |
| sh = logging.StreamHandler(sys.stderr) |
| sh.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) |
| |
| LOG = logging.getLogger("defaultsdoc.py") |
| LOG.addHandler(sh) |
| |
| # parse args |
| parser = argparse.ArgumentParser() |
| |
| parser.add_argument('-p', '--playbook_dir', default='../platform-install/', |
| action='append', required=False, |
| help="path to base playbook directory") |
| |
| parser.add_argument('-d', '--descriptions', default='scripts/descriptions.md', |
| action='store', required=False, |
| help="markdown file with descriptions") |
| |
| parser.add_argument('-t', '--template', default='scripts/defaults.md.j2', |
| action='store', required=False, |
| help="jinja2 template to fill with defaults") |
| |
| parser.add_argument('-o', '--output', default='defaults.md', |
| action='store', required=False, |
| help="output file") |
| |
| args = parser.parse_args() |
| |
| # find the branch we're on via the repo manifest |
| manifest_path = os.path.abspath("../../.repo/manifest.xml") |
| try: |
| tree = ET.parse(manifest_path) |
| manifest_xml = tree.getroot() |
| repo_default = manifest_xml.find('default') |
| repo_branch = repo_default.attrib['revision'] |
| except Exception: |
| LOG.exception("Error loading repo manifest") |
| sys.exit(1) |
| |
| role_defs = [] |
| profile_defs = [] |
| group_defs = [] |
| |
| # frontmatter section is any text at the top of the descriptions.md file, and |
| # comes before all other sections |
| def_docs = {'frontmatter':{'description':''}} |
| |
| # find all the files to be processed |
| for dirpath, dirnames, filenames in os.walk(args.playbook_dir): |
| basepath = re.sub(args.playbook_dir, '', dirpath) |
| for filename in filenames : |
| filepath = os.path.join(basepath, filename) |
| |
| if fnmatch.fnmatch(filepath, "roles/*/defaults/*.yml"): |
| role_defs.append(filepath) |
| |
| if fnmatch.fnmatch(filepath, "profile_manifests/*.yml"): |
| profile_defs.append(filepath) |
| |
| if fnmatch.fnmatch(filepath, "group_vars/*.yml"): |
| group_defs.append(filepath) |
| |
| |
| |
| for rd in role_defs: |
| rd_vars = {} |
| # trim slash so basename grabs the final directory name |
| rd_basedir = os.path.basename(args.playbook_dir[:-1]) |
| try: |
| rd_fullpath = os.path.abspath(os.path.join(args.playbook_dir, rd)) |
| rd_partialpath = os.path.join(rd_basedir, rd) |
| |
| # partial URL, without line nums |
| rd_url = "https://github.com/opencord/platform-install/tree/%s/%s" % (repo_branch, rd) |
| |
| |
| rd_fh= open(rd_fullpath, 'r') |
| |
| # markedloader is for line #'s |
| loader = markedyaml.MarkedLoader(rd_fh.read()) |
| marked_vars = loader.get_data() |
| |
| rd_fh.seek(0) # go to front of file |
| |
| # yaml.safe_load is for vars in a better format |
| rd_vars = yaml.safe_load(rd_fh) |
| |
| rd_fh.close() |
| |
| except yaml.YAMLError: |
| LOG.exception("Problem loading file: %s" % rd) |
| sys.exit(1) |
| |
| if rd_vars: |
| |
| for key, val in rd_vars.iteritems(): |
| |
| # build full URL to lines. Lines numbered from zero, so +1 on them to match github |
| if marked_vars[key].start_mark.line == marked_vars[key].end_mark.line: |
| full_url = "%s#L%d" % (rd_url, marked_vars[key].start_mark.line+1) |
| else: |
| full_url = "%s#L%d-L%d" % (rd_url, marked_vars[key].start_mark.line, marked_vars[key].end_mark.line) |
| |
| if key in def_docs: |
| if def_docs[key]['defval'] == val: |
| def_docs[key]['reflist'].append({'path':rd_partialpath, 'link':full_url}) |
| else: |
| LOG.error(" %s has different default > %s : %s" % (rd, key, val)) |
| else: |
| to_print = { str(key): val } |
| pp = yaml.dump(to_print, indent=4, allow_unicode=False, default_flow_style=False) |
| |
| def_docs[key] = { |
| 'defval': val, |
| 'defval_pp': pp, |
| 'description': "", |
| 'reflist': [{'path':rd_partialpath, 'link':full_url}], |
| } |
| |
| # read in descriptions file |
| descriptions = {} |
| with open(args.descriptions, 'r') as descfile: |
| desc_name = 'frontmatter' |
| desc_lines = '' |
| |
| for d_l in descfile: |
| # see if this is a header line at beginning of docs |
| desc_header = re.match(r"##\s+([\w_]+)", d_l) |
| |
| if desc_header: |
| # add previous description to dict |
| descriptions[desc_name] = desc_lines |
| |
| # set this as the next name, wipe out lines |
| desc_name = desc_header.group(1) |
| desc_lines = '' |
| else: |
| desc_lines += d_l |
| |
| descriptions[desc_name] = desc_lines |
| |
| # add descriptions to def_docs |
| for d_name, d_text in descriptions.iteritems(): |
| if d_name in def_docs: |
| def_docs[d_name]['description'] = d_text |
| else: |
| LOG.error("Description exists for '%s' but doesn't exist in defaults" % d_name) |
| |
| # check for missing descriptions |
| for key in sorted(def_docs): |
| if not def_docs[key]['description']: |
| LOG.error("No description found for '%s'" % key) |
| |
| # Add to template and write to output file |
| j2env = jinja2.Environment( |
| loader = jinja2.FileSystemLoader('.') |
| ) |
| |
| template = j2env.get_template(args.template) |
| |
| with open(args.output, 'w') as f: |
| f.write(template.render(def_docs=def_docs)) |