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
-
-
-
- Test |
- Schedule |
- State |
- Last Run |
-
-
-
- {% for canary in canaries %}
-
- {{ 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 %}
- |
-
-
- {% endfor %}
-
-
-
- Installation Qualification
-
- Stack Resources
-
-
-
- Logical ID |
- Physical ID |
- Type |
- Status |
- Last Updated |
-
-
-
- {% for r in resources %}
-
- {{ r.LogicalResourceId }} |
- {{ r.PhysicalResourceId }} |
- {{ r.ResourceType }} |
- {{ r.ResourceStatus }} |
- {{ r.LastUpdatedTimestamp.isoformat() }} |
-
- {% endfor %}
-
-
-
- Stack Outputs
-
-
-
- Output Key |
- Value |
- Description |
-
-
-
- {% for o in stack_data.Outputs %}
-
- {{ o.OutputKey }} |
- {{ o.OutputValue }} |
- {{ o.Description }} |
-
- {% endfor %}
-
-
-
- Stack Parameters
-
-
-
- Parameter Key |
- Value |
-
-
-
- {% for p in stack_data.Parameters %}
-
- {{ p.ParameterKey }} |
- {{ p.ParameterValue }} |
-
- {% endfor %}
-
-
-
-
-""")
+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() %}
+
+{% endmacro %}
+
+{% macro thead() %}
+
+
+
+{% endmacro %}
+
+{% macro tbody() %}
+
+ {{ caller() }}
+
+{% endmacro %}
+
+{% macro th() %}
+
+{% endmacro %}
+
+{% macro tr() %}
+
+ {{ caller() }}
+
+{% endmacro %}
+
+{% macro td(classes="") %}
+
+ {{ caller() }}
+ |
+{% endmacro %}
+
+{% macro typo(el="p", variant="body1", classes="") %}
+ <{{ el }} class="mdc-typography--{{ variant }} {{ classes }}">
+ {{ caller() }}
+ {{ el }}>
+{% 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 %}