diff --git a/lambdas/status_reports/src/t4_lambda_status_reports/__init__.py b/lambdas/status_reports/src/t4_lambda_status_reports/__init__.py index 29f17df43b0..86e2c5e8c6f 100644 --- a/lambdas/status_reports/src/t4_lambda_status_reports/__init__.py +++ b/lambdas/status_reports/src/t4_lambda_status_reports/__init__.py @@ -27,7 +27,9 @@ def create_syn(): async def list_stack_canaries(cfn, stack_name: str) -> T.List[str]: result = [] - async for page in cfn.get_paginator("list_stack_resources").paginate(StackName=stack_name): + async for page in cfn.get_paginator("list_stack_resources").paginate( + StackName=stack_name + ): for r in page["StackResourceSummaries"]: if r["ResourceType"] == "AWS::Synthetics::Canary": result.append(r["PhysicalResourceId"]) @@ -36,13 +38,12 @@ async def list_stack_canaries(cfn, stack_name: str) -> T.List[str]: async def drain(syn, method: str, key: str, names: T.List[str]) -> T.List[dict]: chunks = [ - names[i:i + CANARIES_PER_REQUEST] + names[i : i + CANARIES_PER_REQUEST] for i in range(0, len(names), CANARIES_PER_REQUEST) ] - pages = await asyncio.gather(*[ - getattr(syn, method)(Names=chunk) - for chunk in chunks - ]) + pages = await asyncio.gather( + *[getattr(syn, method)(Names=chunk) for chunk in chunks] + ) return list(itertools.chain.from_iterable(p[key] for p in pages)) @@ -102,7 +103,9 @@ async def get_canaries(syn, cfn, stack_name: str) -> T.List[dict]: async def get_resources(cfn, stack_name: str) -> T.List[dict]: result = [] - async for page in cfn.get_paginator("list_stack_resources").paginate(StackName=stack_name): + async for page in cfn.get_paginator("list_stack_resources").paginate( + StackName=stack_name + ): result.extend(page["StackResourceSummaries"]) return result @@ -112,129 +115,12 @@ async def get_stack_data(cfn, stack_name: str) -> dict: return resp["Stacks"][0] -jenv = jinja2.Environment(autoescape=jinja2.select_autoescape()) -# TODO: styling -tmpl = jenv.from_string(""" - - - - - Status Report - - -

Quilt Status Report

-
-
CloudFormation Stack:
-
{{ stack_name }}
-
AWS Region:
-
{{ aws_region }}
-
Timestamp:
-
{{ now.isoformat() }}
-
- -

Operational Qualification

- - - - - - - - - - - {% for canary in canaries %} - - - - - - - - {% endfor %} - -
TestScheduleStateLast Run
{{ canary.group }} / {{ canary.title }}{{ canary.schedule }} - {% if canary.ok %} - Passed - {% elif canary.ok is false %} - Failed - {% else %} - Running - {% endif %} - - {% if canary.lastRun %} - {{ canary.lastRun }} - {% else %} - N/A - {% endif %} -
- -

Installation Qualification

- -

Stack Resources

- - - - - - - - - - - - {% for r in resources %} - - - - - - - - {% endfor %} - -
Logical IDPhysical IDTypeStatusLast Updated
{{ r.LogicalResourceId }}{{ r.PhysicalResourceId }}{{ r.ResourceType }}{{ r.ResourceStatus }}{{ r.LastUpdatedTimestamp.isoformat() }}
- -

Stack Outputs

- - - - - - - - - - {% for o in stack_data.Outputs %} - - - - - - {% endfor %} - -
Output KeyValueDescription
{{ o.OutputKey }}{{ o.OutputValue }}{{ o.Description }}
- -

Stack Parameters

- - - - - - - - - {% for p in stack_data.Parameters %} - - - - - {% endfor %} - -
Parameter KeyValue
{{ p.ParameterKey }}{{ p.ParameterValue }}
- - -""") +jenv = jinja2.Environment( + loader=jinja2.PackageLoader("t4_lambda_status_reports"), + autoescape=jinja2.select_autoescape(), +) + +tmpl = jenv.get_template("entry.html.jinja") async def generate_status_report(stack_name: str): @@ -245,6 +131,11 @@ async def generate_status_report(stack_name: str): get_resources(cfn, stack_name), get_stack_data(cfn, stack_name), ) + catalog_url = next( + o["OutputValue"] + for o in stack_data["Outputs"] + if o["OutputKey"] == "QuiltWebHost" + ) html = tmpl.render( stack_name=stack_name, aws_region=AWS_REGION, @@ -252,6 +143,7 @@ async def generate_status_report(stack_name: str): canaries=canaries, resources=resources, stack_data=stack_data, + catalog_url=catalog_url, ) return now, html @@ -284,6 +176,7 @@ async def lambda_handler(*_): if __name__ == "__main__": import sys + args = sys.argv[1:] stack_name = args[0] if len(args) >= 1 else os.getenv("STACK_NAME") assert stack_name diff --git a/lambdas/status_reports/src/t4_lambda_status_reports/templates/base.html.jinja b/lambdas/status_reports/src/t4_lambda_status_reports/templates/base.html.jinja new file mode 100644 index 00000000000..6ccdf247f03 --- /dev/null +++ b/lambdas/status_reports/src/t4_lambda_status_reports/templates/base.html.jinja @@ -0,0 +1,27 @@ +{% macro stylesheet(href) %} + +{% endmacro %} + + + + + + {{ stylesheet("https://fonts.googleapis.com/css?family=Roboto+Mono:400,700|Roboto:300,400,500") }} + {{ stylesheet("https://unpkg.com/material-components-web@14.0.0/dist/material-components-web.min.css") }} + + + + + {% block title required %}{% endblock %} + + + {% block body required %}{% endblock %} + + diff --git a/lambdas/status_reports/src/t4_lambda_status_reports/templates/entry.html.jinja b/lambdas/status_reports/src/t4_lambda_status_reports/templates/entry.html.jinja new file mode 100644 index 00000000000..31e341e06c5 --- /dev/null +++ b/lambdas/status_reports/src/t4_lambda_status_reports/templates/entry.html.jinja @@ -0,0 +1,309 @@ +{% extends "base.html.jinja" %} + +{% block title %}Status Report for {{ stack_name }} Quilt stack{% endblock %} + +{% block style %} + body { + margin: 0; + } + .root { + margin: 1rem; + } + .mono { + font-family: 'Roboto Mono'; + } + .bold { + font-weight: 500; + } + .color-ok { + color: green; + } + .color-err { + color: red; + } + .color-info { + color: blue; + } + .header__logo { + height: 64px; + margin-bottom: -8px; + margin-top: -6px; + } + .footer { + align-items: center; + display: flex; + margin-top: 1.5rem; + } + .footer__logo { + height: 32px; + margin-right: 8px; + } +{% endblock %} + +{% macro table() %} +
+
+ + {{ caller() }} +
+
+
+{% endmacro %} + +{% macro thead() %} + + + {{ caller() }} + + +{% endmacro %} + +{% macro tbody() %} + + {{ caller() }} + +{% endmacro %} + +{% macro th() %} + + {{ caller() }} + +{% endmacro %} + +{% macro tr() %} + + {{ caller() }} + +{% endmacro %} + +{% macro td(classes="") %} + + {{ caller() }} + +{% endmacro %} + +{% macro typo(el="p", variant="body1", classes="") %} + <{{ el }} class="mdc-typography--{{ variant }} {{ classes }}"> + {{ caller() }} + +{% endmacro %} + +{% macro status_color(status) %} + color-{% if status.endswith('_COMPLETE') %}ok{% elif status.endswith('_FAILED') %}err{% else %}info{% endif %} +{% endmacro %} + +{% block body %} +
+ {% call typo("h1", "headline2") %} + + Status Report for {{ stack_name }} Quilt stack + {% endcall %} + + {% call typo() %} + Generated at {{ now }} + {% endcall %} + {% call typo() %} + Other reports available at + + {{ catalog_url }}/admin/status + + {% endcall %} + + {% call typo("h3", "headline4") %}Stack Metadata{% endcall %} + {% call table() %} + {% call tbody() %} + {% call tr() %} + {% call td("bold") %}Catalog URL{% endcall %} + {% call td() %} + + {{ catalog_url }} + + {% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Stack Name{% endcall %} + {% call td() %} + + {{ stack_name }} + + {% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Stack ID{% endcall %} + {% call td() %} + + {{ stack_data.StackId }} + + {% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}AWS Region{% endcall %} + {% call td() %}{{ aws_region }}{% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Creation Time{% endcall %} + {% call td() %}{{ stack_data.CreationTime }}{% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Last Updated Time{% endcall %} + {% call td() %}{{ stack_data.LastUpdatedTime }}{% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Status{% endcall %} + {% call td() %} + + {{ stack_data.StackStatus }} + + {% if stack_data.StackStatusReason %} + ({{ stack_data.StackStatusReason }}) + {% endif %} + {% endcall %} + {% endcall %} + {% call tr() %} + {% call td("bold") %}Description{% endcall %} + {% call td() %}{{ stack_data.Description }}{% endcall %} + {% endcall %} + {% endcall %} + {% endcall %} + + {% call typo("h2", "headline3") %}Operational Qualification{% endcall %} + {% call table() %} + {% call thead() %} + {% call th() %}Test{% endcall %} + {% call th() %}Schedule{% endcall %} + {% call th() %}State{% endcall %} + {% call th() %}Last Run{% endcall %} + {% endcall %} + {% call tbody() %} + {% for canary in canaries %} + {% call tr() %} + {% call td() %} + + {{ canary.group }} / {{ canary.title }} + + {% endcall %} + {% call td() %}{{ canary.schedule }}{% endcall %} + {% call td() %} + {% if canary.ok %} + Passed + {% elif canary.ok is false %} + Failed + {% else %} + Running + {% endif %} + {% endcall %} + {% call td() %} + {% if canary.lastRun %} + {{ canary.lastRun }} + {% else %} + N/A + {% endif %} + {% endcall %} + {% endcall %} + {% endfor %} + {% endcall %} + {% endcall %} + + {% call typo("h2", "headline3") %}Installation Qualification{% endcall %} + + {% call typo("h3", "headline4") %}Stack Resources{% endcall %} + {% call table() %} + {% call thead() %} + {% call th() %}Logical ID{% endcall %} + {% call th() %}Physical ID{% endcall %} + {% call th() %}Type{% endcall %} + {% call th() %}Status{% endcall %} + {% call th() %}Last Updated{% endcall %} + {% endcall %} + {% call tbody() %} + {% for r in resources %} + {% call tr() %} + {% call td("mono") %}{{ r.LogicalResourceId }}{% endcall %} + {% call td("mono") %}{{ r.PhysicalResourceId }}{% endcall %} + {% call td("mono") %}{{ r.ResourceType }}{% endcall %} + {% call td("mono") %} + + {{ r.ResourceStatus }} + + {% endcall %} + {% call td("mono") %}{{ r.LastUpdatedTimestamp }}{% endcall %} + {% endcall %} + {% endfor %} + {% endcall %} + {% endcall %} + + {% call typo("h3", "headline4") %}Stack Outputs{% endcall %} + {% call table() %} + {% call thead() %} + {% call th() %}Output Key{% endcall %} + {% call th() %}Value{% endcall %} + {% call th() %}Description{% endcall %} + {% endcall %} + {% call tbody() %} + {% for o in stack_data.Outputs %} + {% call tr() %} + {% call td("mono") %}{{ o.OutputKey }}{% endcall %} + {% call td("mono") %}{{ o.OutputValue }}{% endcall %} + {% call td() %}{{ o.Description }}{% endcall %} + {% endcall %} + {% endfor %} + {% endcall %} + {% endcall %} + + {% call typo("h3", "headline4") %}Stack Parameters{% endcall %} + {% call table() %} + {% call thead() %} + {% call th() %}Parameter Key{% endcall %} + {% call th() %}Value{% endcall %} + {% endcall %} + {% call tbody() %} + {% for p in stack_data.Parameters %} + {% call tr() %} + {% call td("mono") %}{{ p.ParameterKey }}{% endcall %} + {% call td("mono") %}{{ p.ParameterValue }}{% endcall %} + {% endcall %} + {% endfor %} + {% endcall %} + {% endcall %} + + +
+{% endblock %}