blob: c17ee49e5e89ed3655161c7c1c62c7ba99e1a48f [file] [log] [blame]
Zack Williams1b96a952017-07-17 16:48:01 -07001#!/usr/bin/env python
2# defaultsdoc.py - documentation for ansible default vaules
3
4# Copyright 2017-present Open Networking Foundation
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18import argparse
19import fnmatch
20import jinja2
21import logging
22import os
Zack Williams1b96a952017-07-17 16:48:01 -070023import re
24import sys
25import xml.etree.ElementTree as ET
26import yaml
27import markedyaml
28
29# logging setup
30sh = logging.StreamHandler(sys.stderr)
31sh.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
32
33LOG = logging.getLogger("defaultsdoc.py")
34LOG.addHandler(sh)
35
36# parse args
37parser = argparse.ArgumentParser()
38
39parser.add_argument('-p', '--playbook_dir', default='../platform-install/',
40 action='append', required=False,
41 help="path to base playbook directory")
42
43parser.add_argument('-d', '--descriptions', default='scripts/descriptions.md',
44 action='store', required=False,
45 help="markdown file with descriptions")
46
47parser.add_argument('-t', '--template', default='scripts/defaults.md.j2',
48 action='store', required=False,
49 help="jinja2 template to fill with defaults")
50
51parser.add_argument('-o', '--output', default='defaults.md',
52 action='store', required=False,
53 help="output file")
54
55args = parser.parse_args()
56
57# find the branch we're on via the repo manifest
Zack Williamsbe0b2c72018-01-25 14:48:38 -070058manifest_path = os.path.abspath("../../.repo/manifest.xml")
Zack Williams1b96a952017-07-17 16:48:01 -070059try:
60 tree = ET.parse(manifest_path)
61 manifest_xml = tree.getroot()
62 repo_default = manifest_xml.find('default')
63 repo_branch = repo_default.attrib['revision']
64except Exception:
65 LOG.exception("Error loading repo manifest")
66 sys.exit(1)
67
68role_defs = []
69profile_defs = []
70group_defs = []
Zack Williamsbe0b2c72018-01-25 14:48:38 -070071def_docs = {}
Zack Williams1b96a952017-07-17 16:48:01 -070072
73# find all the files to be processed
74for dirpath, dirnames, filenames in os.walk(args.playbook_dir):
75 basepath = re.sub(args.playbook_dir, '', dirpath)
Zack Williamsbe0b2c72018-01-25 14:48:38 -070076 for filename in filenames:
Zack Williams1b96a952017-07-17 16:48:01 -070077 filepath = os.path.join(basepath, filename)
78
79 if fnmatch.fnmatch(filepath, "roles/*/defaults/*.yml"):
80 role_defs.append(filepath)
81
82 if fnmatch.fnmatch(filepath, "profile_manifests/*.yml"):
83 profile_defs.append(filepath)
84
85 if fnmatch.fnmatch(filepath, "group_vars/*.yml"):
86 group_defs.append(filepath)
87
88
Zack Williams1b96a952017-07-17 16:48:01 -070089for rd in role_defs:
90 rd_vars = {}
91 # trim slash so basename grabs the final directory name
92 rd_basedir = os.path.basename(args.playbook_dir[:-1])
93 try:
94 rd_fullpath = os.path.abspath(os.path.join(args.playbook_dir, rd))
95 rd_partialpath = os.path.join(rd_basedir, rd)
96
97 # partial URL, without line nums
Zack Williamsbe0b2c72018-01-25 14:48:38 -070098 rd_url = "https://github.com/opencord/platform-install/tree/%s/%s" % (
99 repo_branch, rd)
Zack Williams1b96a952017-07-17 16:48:01 -0700100
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700101 rd_fh = open(rd_fullpath, 'r')
Zack Williams1b96a952017-07-17 16:48:01 -0700102
103 # markedloader is for line #'s
104 loader = markedyaml.MarkedLoader(rd_fh.read())
105 marked_vars = loader.get_data()
106
107 rd_fh.seek(0) # go to front of file
108
109 # yaml.safe_load is for vars in a better format
110 rd_vars = yaml.safe_load(rd_fh)
111
112 rd_fh.close()
113
114 except yaml.YAMLError:
115 LOG.exception("Problem loading file: %s" % rd)
116 sys.exit(1)
117
118 if rd_vars:
119
120 for key, val in rd_vars.iteritems():
121
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700122 # build full URL to lines. Lines numbered from zero, so +1 on them
123 # to match github
124 if marked_vars[key].start_mark.line == marked_vars[
125 key].end_mark.line:
126 full_url = "%s#L%d" % (rd_url,
127 marked_vars[key].start_mark.line + 1)
128 else:
129 full_url = "%s#L%d-L%d" % (rd_url,
130 marked_vars[key].start_mark.line,
131 marked_vars[key].end_mark.line)
Zack Williams1b96a952017-07-17 16:48:01 -0700132
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700133 if key in def_docs:
Zack Williams1b96a952017-07-17 16:48:01 -0700134 if def_docs[key]['defval'] == val:
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700135 def_docs[key]['reflist'].append(
136 {'path': rd_partialpath, 'link': full_url})
Zack Williams1b96a952017-07-17 16:48:01 -0700137 else:
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700138 LOG.error(
139 " %s has different default > %s : %s" %
140 (rd, key, val))
141 else:
142 to_print = {str(key): val}
143 pp = yaml.dump(
144 to_print,
145 indent=4,
146 allow_unicode=False,
147 default_flow_style=False)
Zack Williams1b96a952017-07-17 16:48:01 -0700148
149 def_docs[key] = {
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700150 'defval': val,
151 'defval_pp': pp,
152 'description': "",
153 'reflist': [{'path': rd_partialpath, 'link': full_url}],
154 }
Zack Williams1b96a952017-07-17 16:48:01 -0700155
156# read in descriptions file
157descriptions = {}
158with open(args.descriptions, 'r') as descfile:
159 desc_name = 'frontmatter'
160 desc_lines = ''
161
162 for d_l in descfile:
163 # see if this is a header line at beginning of docs
164 desc_header = re.match(r"##\s+([\w_]+)", d_l)
165
166 if desc_header:
167 # add previous description to dict
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700168 descriptions[desc_name] = desc_lines.strip()
Zack Williams1b96a952017-07-17 16:48:01 -0700169
170 # set this as the next name, wipe out lines
171 desc_name = desc_header.group(1)
172 desc_lines = ''
173 else:
174 desc_lines += d_l
175
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700176 descriptions[desc_name] = desc_lines.strip()
177
178# Get the frontmatter out of descriptions, and remove the header line
179frontmatter = re.sub(r'^#.*\n\n', '', descriptions.pop('frontmatter', None))
Zack Williams1b96a952017-07-17 16:48:01 -0700180
181# add descriptions to def_docs
182for d_name, d_text in descriptions.iteritems():
183 if d_name in def_docs:
184 def_docs[d_name]['description'] = d_text
185 else:
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700186 LOG.error(
187 "Description exists for '%s' but doesn't exist in defaults" %
188 d_name)
Zack Williams1b96a952017-07-17 16:48:01 -0700189
190# check for missing descriptions
191for key in sorted(def_docs):
192 if not def_docs[key]['description']:
193 LOG.error("No description found for '%s'" % key)
194
195# Add to template and write to output file
196j2env = jinja2.Environment(
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700197 loader=jinja2.FileSystemLoader('.')
Zack Williams1b96a952017-07-17 16:48:01 -0700198)
199
200template = j2env.get_template(args.template)
201
202with open(args.output, 'w') as f:
Zack Williamsbe0b2c72018-01-25 14:48:38 -0700203 f.write(template.render(def_docs=def_docs, frontmatter=frontmatter))