blob: f75fa85cae36f3529223784b816a03ea930e63c0 [file] [log] [blame]
Zack Williams6e87cbc2017-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
23import pprint
24import re
25import sys
26import xml.etree.ElementTree as ET
27import yaml
28import markedyaml
29
30# logging setup
31sh = logging.StreamHandler(sys.stderr)
32sh.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
33
34LOG = logging.getLogger("defaultsdoc.py")
35LOG.addHandler(sh)
36
37# parse args
38parser = argparse.ArgumentParser()
39
40parser.add_argument('-p', '--playbook_dir', default='../platform-install/',
41 action='append', required=False,
42 help="path to base playbook directory")
43
44parser.add_argument('-d', '--descriptions', default='scripts/descriptions.md',
45 action='store', required=False,
46 help="markdown file with descriptions")
47
48parser.add_argument('-t', '--template', default='scripts/defaults.md.j2',
49 action='store', required=False,
50 help="jinja2 template to fill with defaults")
51
52parser.add_argument('-o', '--output', default='defaults.md',
53 action='store', required=False,
54 help="output file")
55
56args = parser.parse_args()
57
58# find the branch we're on via the repo manifest
59manifest_path = os.path.abspath("../../.repo/manifest.xml")
60try:
61 tree = ET.parse(manifest_path)
62 manifest_xml = tree.getroot()
63 repo_default = manifest_xml.find('default')
64 repo_branch = repo_default.attrib['revision']
65except Exception:
66 LOG.exception("Error loading repo manifest")
67 sys.exit(1)
68
69role_defs = []
70profile_defs = []
71group_defs = []
72
73# frontmatter section is any text at the top of the descriptions.md file, and
74# comes before all other sections
75def_docs = {'frontmatter':{'description':''}}
76
77# find all the files to be processed
78for dirpath, dirnames, filenames in os.walk(args.playbook_dir):
79 basepath = re.sub(args.playbook_dir, '', dirpath)
80 for filename in filenames :
81 filepath = os.path.join(basepath, filename)
82
83 if fnmatch.fnmatch(filepath, "roles/*/defaults/*.yml"):
84 role_defs.append(filepath)
85
86 if fnmatch.fnmatch(filepath, "profile_manifests/*.yml"):
87 profile_defs.append(filepath)
88
89 if fnmatch.fnmatch(filepath, "group_vars/*.yml"):
90 group_defs.append(filepath)
91
92
93
94for rd in role_defs:
95 rd_vars = {}
96 # trim slash so basename grabs the final directory name
97 rd_basedir = os.path.basename(args.playbook_dir[:-1])
98 try:
99 rd_fullpath = os.path.abspath(os.path.join(args.playbook_dir, rd))
100 rd_partialpath = os.path.join(rd_basedir, rd)
101
102 # partial URL, without line nums
103 rd_url = "https://github.com/opencord/platform-install/tree/%s/%s" % (repo_branch, rd)
104
105
106 rd_fh= open(rd_fullpath, 'r')
107
108 # markedloader is for line #'s
109 loader = markedyaml.MarkedLoader(rd_fh.read())
110 marked_vars = loader.get_data()
111
112 rd_fh.seek(0) # go to front of file
113
114 # yaml.safe_load is for vars in a better format
115 rd_vars = yaml.safe_load(rd_fh)
116
117 rd_fh.close()
118
119 except yaml.YAMLError:
120 LOG.exception("Problem loading file: %s" % rd)
121 sys.exit(1)
122
123 if rd_vars:
124
125 for key, val in rd_vars.iteritems():
126
127 # build full URL to lines. Lines numbered from zero, so +1 on them to match github
128 if marked_vars[key].start_mark.line == marked_vars[key].end_mark.line:
129 full_url = "%s#L%d" % (rd_url, marked_vars[key].start_mark.line+1)
130 else:
131 full_url = "%s#L%d-L%d" % (rd_url, marked_vars[key].start_mark.line, marked_vars[key].end_mark.line)
132
133 if key in def_docs:
134 if def_docs[key]['defval'] == val:
135 def_docs[key]['reflist'].append({'path':rd_partialpath, 'link':full_url})
136 else:
137 LOG.error(" %s has different default > %s : %s" % (rd, key, val))
138 else:
139 to_print = { str(key): val }
140 pp = yaml.dump(to_print, indent=4, allow_unicode=False, default_flow_style=False)
141
142 def_docs[key] = {
143 'defval': val,
144 'defval_pp': pp,
145 'description': "",
146 'reflist': [{'path':rd_partialpath, 'link':full_url}],
147 }
148
149# read in descriptions file
150descriptions = {}
151with open(args.descriptions, 'r') as descfile:
152 desc_name = 'frontmatter'
153 desc_lines = ''
154
155 for d_l in descfile:
156 # see if this is a header line at beginning of docs
157 desc_header = re.match(r"##\s+([\w_]+)", d_l)
158
159 if desc_header:
160 # add previous description to dict
161 descriptions[desc_name] = desc_lines
162
163 # set this as the next name, wipe out lines
164 desc_name = desc_header.group(1)
165 desc_lines = ''
166 else:
167 desc_lines += d_l
168
169 descriptions[desc_name] = desc_lines
170
171# add descriptions to def_docs
172for d_name, d_text in descriptions.iteritems():
173 if d_name in def_docs:
174 def_docs[d_name]['description'] = d_text
175 else:
176 LOG.error("Description exists for '%s' but doesn't exist in defaults" % d_name)
177
178# check for missing descriptions
179for key in sorted(def_docs):
180 if not def_docs[key]['description']:
181 LOG.error("No description found for '%s'" % key)
182
183# Add to template and write to output file
184j2env = jinja2.Environment(
185 loader = jinja2.FileSystemLoader('.')
186)
187
188template = j2env.get_template(args.template)
189
190with open(args.output, 'w') as f:
191 f.write(template.render(def_docs=def_docs))