blob: 3ce209cdd77b0bd767f2313e4201ca8304485ec4 [file] [log] [blame]
#!/usr/bin/env python3
# SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
# SPDX-License-Identifier: Apache-2.0
from __future__ import absolute_import
import argparse
import datetime
import jinja2
import json
import logging
import os
# create shared logger
logging.basicConfig()
logger = logging.getLogger("sjsgsr")
# script starttime, used for timeboxing
starttime = datetime.datetime.now(datetime.timezone.utc)
startdate = datetime.datetime(
starttime.year, starttime.month, starttime.day, tzinfo=datetime.timezone.utc
)
def parse_sitebuilder_args():
"""
parse CLI arguments
"""
parser = argparse.ArgumentParser(description="Jenkins job results site renderer")
def readable_dir(path):
if os.path.isdir(path) and os.access(path, os.R_OK):
return path
raise argparse.ArgumentTypeError("%s is not a directory or unreadable" % path)
# Flags
parser.add_argument(
"--product_dir",
default="products",
type=readable_dir,
help="Directory containing product JSON created by buildcollector.py",
)
parser.add_argument(
"--template_dir",
default="templates",
type=readable_dir,
help="Directory with Jinja2 templates",
)
parser.add_argument(
"--site_dir",
default="site",
type=readable_dir,
help="Directory to write the static site into",
)
parser.add_argument(
"--debug", action="store_true", help="Print additional debugging information"
)
return parser.parse_args()
def jinja_env(template_dir):
"""
Returns a Jinja2 enviroment loading files from template_dir
"""
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_dir),
autoescape=jinja2.select_autoescape(["html"]),
undefined=jinja2.StrictUndefined,
trim_blocks=True,
lstrip_blocks=True,
)
# Jinja2 filters
def tsdatetime(value, fmt="%Y-%m-%d %H:%M %Z"):
# timestamps given ms precision, divide by 1000
dateval = datetime.datetime.fromtimestamp(
value // 1000, tz=datetime.timezone.utc
)
return dateval.strftime(fmt)
def tsdate(value, fmt="%Y-%m-%d"):
# timestamps given ms precision, divide by 1000
dateval = datetime.datetime.fromtimestamp(
value // 1000, tz=datetime.timezone.utc
)
return dateval.strftime(fmt)
def timebox(value, boxsize=24, count=7):
"""
Given a list of builds, groups in them into within a sequential range
Boxsize is given in hours
Count is number of boxes to return
Time starts from now()
"""
retbox = []
nowms = startdate.timestamp() * 1000 # ms precision datetime
hourms = 60 * 60 * 1000 # ms in an hour
for box in range(-1, count - 1):
startms = nowms - (box * hourms * boxsize)
endms = nowms - ((box + 1) * hourms * boxsize)
timebox_builds = 0
success_count = 0
builds = []
for bdata in value:
# loops multiple times over entire list of builds, could be
# optimized
bt = int(bdata["timestamp"])
if startms > bt > endms:
timebox_builds += 1
builds.append(bdata)
if bdata["result"] == "SUCCESS":
success_count += 1
# determine overall status for the timebox
if timebox_builds == 0:
status = "NONE"
elif timebox_builds == success_count:
status = "SUCCESS"
elif success_count == 0:
status = "FAILURE"
else: # timebox_builds > success_count
status = "UNSTABLE"
retbox.append(
{
"result": status,
"box_start": int(endms),
"outcome": "%d of %d" % (success_count, timebox_builds),
"builds": builds,
}
)
return retbox
env.filters["tsdatetime"] = tsdatetime
env.filters["tsdate"] = tsdate
env.filters["timebox"] = timebox
return env
def clean_name(name):
"""
Clean up a name string. Currently only replaces spaces with underscores
"""
return name.replace(" ", "_")
def render_to_file(j2env, context, template_name, path):
"""
Render out a template to file
"""
parent_dir = os.path.dirname(path)
os.makedirs(parent_dir, exist_ok=True)
template = j2env.get_template(template_name)
with open(path, "w") as outfile:
outfile.write(template.render(context))
def json_file_load(path):
"""
Get data from local file, return data as a dict
"""
with open(path) as jf:
try:
data = json.loads(jf.read())
except json.decoder.JSONDecodeError:
logger.exception("Unable to decode JSON from file: '%s'", path)
return data
# main function that calls other functions
if __name__ == "__main__":
args = parse_sitebuilder_args()
# only print log messages if debugging
if args.debug:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.CRITICAL)
j2env = jinja_env(args.template_dir)
buildtime = starttime.strftime("%Y-%m-%d %H:%M %Z")
# init list of projects
projects = {}
# list of projects
projdirs = os.listdir(args.product_dir)
for projdir in projdirs:
prodfiles = os.listdir("%s/%s" % (args.product_dir, projdir))
for prodfile in prodfiles:
# load product, and set buildtime
logger.debug("loading file file: '%s'", prodfile)
product = json_file_load("%s/%s/%s" % (args.product_dir, projdir, prodfile))
product.update({"buildtime": buildtime})
projname = product["onf_project"]
# build product filename, write out template
site_prod_filename = "%s/%s/%s/index.html" % (
args.site_dir,
projname,
clean_name(product["product_name"]),
)
render_to_file(j2env, product, "product.html", site_prod_filename)
# product to project list
if projname not in projects:
projects[projname] = [product]
else:
projects[projname].append(product)
# list of projects
for projname in sorted(projects.keys()):
proj_filename = "%s/%s/index.html" % (args.site_dir, projname)
products = projects[projname]
project_context = {
"buildtime": buildtime,
"project_name": projname,
"products": products,
}
render_to_file(j2env, project_context, "project.html", proj_filename)
# render index file
index_filename = "%s/index.html" % args.site_dir
index_context = {
"buildtime": buildtime,
"projects": projects,
}
render_to_file(j2env, index_context, "index.html", index_filename)