Skip to content

Commit

Permalink
[uss_qualifier] Separate artifact-making from main.py (#321)
Browse files Browse the repository at this point in the history
* Separate artifact-making from main.py

* Fix shell lint

* Add multiple-config documentation per comments
  • Loading branch information
BenjaminPelletier authored Nov 7, 2023
1 parent 079c92b commit 1973813
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 112 deletions.
4 changes: 4 additions & 0 deletions monitoring/uss_qualifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ At this point, uss_qualifier can be run again with a different configuration tar

Note that all baseline test configurations using local mocks can be run with `monitoring/uss_qualifier/run_locally.sh`.

### Artifacts

Part of a configuration defines artifacts that should be produced by the test run. The raw output of the test run is a raw TestRunReport, which can be produced with the `raw_report` artifact option and has the file name `report.json`. Given a `report.json`, any other artifacts can be generated with [`make_artifacts.sh`](./make_artifacts.sh). From the repository root, for instance: `monitoring/uss_qualifier/make_artifacts.sh configurations.personal.my_artifacts file://output/report.json`. That command loads the report at monitoring/uss_qualifier/output/report.json along with the configuration at monitoring/configurations/personal/my_artifacts.yaml and write the artifacts defined in the my_artifacts configuration.

### Local testing

See the [local testing page](local_testing.md) for more information regarding running uss_qualifier on a single local system.
Expand Down
118 changes: 6 additions & 112 deletions monitoring/uss_qualifier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,11 @@
from monitoring.monitorlib.versioning import get_code_version, get_commit_hash
from monitoring.uss_qualifier.configurations.configuration import (
USSQualifierConfiguration,
ArtifactsConfiguration,
RawReportConfiguration,
USSQualifierConfigurationV1,
)
from monitoring.uss_qualifier.fileio import load_dict_with_references
from monitoring.uss_qualifier.reports.documents import make_report_html
from monitoring.uss_qualifier.reports.sequence_view import generate_sequence_view
from monitoring.uss_qualifier.reports.tested_requirements import (
generate_tested_requirements,
)
from monitoring.uss_qualifier.reports.report import TestRunReport, redact_access_tokens
from monitoring.uss_qualifier.reports.templates import render_templates
from monitoring.uss_qualifier.reports.artifacts import generate_artifacts
from monitoring.uss_qualifier.reports.report import TestRunReport
from monitoring.uss_qualifier.reports.validation.report_validation import (
validate_report,
)
Expand All @@ -46,12 +39,6 @@ def parseArgs() -> argparse.Namespace:
required=True,
)

parser.add_argument(
"--report",
default=None,
help="(Overrides setting in artifacts configuration) File name of the report to write (if test configuration provided) or read (if test configuration not provided); Several comma-separated file names matching the configurations may be specified",
)

parser.add_argument(
"--config-output",
default=None,
Expand Down Expand Up @@ -119,7 +106,6 @@ def execute_test_run(whole_config: USSQualifierConfiguration):
def run_config(
config_name: str,
config_output: str,
report_path: str,
skip_validation: bool,
exit_before_execution: bool,
):
Expand Down Expand Up @@ -155,94 +141,12 @@ def run_config(
return

config: USSQualifierConfigurationV1 = whole_config.v1
if report_path:
if not config.artifacts:
config.artifacts = ArtifactsConfiguration(
RawReportConfiguration(report_path=report_path)
)
elif not config.artifacts.report:
config.artifacts.report = RawReportConfiguration(report_path=report_path)
else:
config.artifacts.report.report_path = report_path

do_not_save_report = False
if config.test_run:
logger.info("Executing test run")
report = execute_test_run(whole_config)
elif config.artifacts and config.artifacts.report:
with open(config.artifacts.report.report_path, "r") as f:
report = ImplicitDict.parse(json.load(f), TestRunReport)
do_not_save_report = True # No reason to re-save what we just loaded
else:
raise ValueError(
"No input provided; test_run or artifacts.report.report_path must be specified in configuration"
)

if config.artifacts:
os.makedirs(config.artifacts.output_path, exist_ok=True)

def _should_redact(cfg) -> bool:
return "redact_access_tokens" in cfg and cfg.redact_access_tokens
logger.info("Executing test run")
report = execute_test_run(whole_config)

logger.info(f"Redacting access tokens from report")
redacted_report = ImplicitDict.parse(
json.loads(json.dumps(report)), TestRunReport
)
redact_access_tokens(redacted_report)

if config.artifacts.raw_report and not do_not_save_report:
# Raw report
path = os.path.join(config.artifacts.output_path, "report.json")
logger.info(f"Writing raw report to {path}")
raw_report = config.artifacts.raw_report
report_to_write = redacted_report if _should_redact(raw_report) else report
with open(path, "w") as f:
if "indent" in raw_report and raw_report.indent is not None:
json.dump(report_to_write, f, indent=raw_report.indent)
else:
json.dump(report_to_write, f)

if config.artifacts.report_html:
# HTML rendering of raw report
path = os.path.join(config.artifacts.output_path, "report.html")
logger.info(f"Writing HTML report to {path}")
report_to_write = (
redacted_report
if _should_redact(config.artifacts.report_html)
else report
)
with open(path, "w") as f:
f.write(make_report_html(report_to_write))

if config.artifacts.templated_reports:
# Templated reports
render_templates(
config.artifacts.output_path,
config.artifacts.templated_reports,
redacted_report,
)

if config.artifacts.tested_requirements:
# Tested requirements view
for tested_reqs_config in config.artifacts.tested_requirements:
path = os.path.join(
config.artifacts.output_path, tested_reqs_config.report_name
)
logger.info(f"Writing tested requirements view to {path}")
generate_tested_requirements(redacted_report, tested_reqs_config, path)

if config.artifacts.sequence_view:
# Sequence view
path = os.path.join(config.artifacts.output_path, "sequence")
logger.info(f"Writing sequence view to {path}")
report_to_write = (
redacted_report
if _should_redact(config.artifacts.sequence_view)
else report
)
generate_sequence_view(
report_to_write, config.artifacts.sequence_view, path
)
if config.artifacts:
generate_artifacts(report, config.artifacts)

if "validation" in config and config.validation:
logger.info(f"Validating test run report for configuration '{config_name}'")
Expand All @@ -269,23 +173,13 @@ def main() -> int:
else:
config_outputs = ["" for _ in config_names]

if args.report:
report_paths = str(args.report).split(",")
if len(report_paths) != len(config_names):
raise ValueError(
f"Need matching number of report, expected {len(config_names)}, got {len(report_paths)}"
)
else:
report_paths = ["" for _ in config_names]

for idx, config_name in enumerate(config_names):
logger.info(
f"========== Running uss_qualifier for configuration {config_name} =========="
)
run_config(
config_name,
config_outputs[idx],
report_paths[idx],
args.skip_validation,
args.exit_before_execution,
)
Expand Down
75 changes: 75 additions & 0 deletions monitoring/uss_qualifier/make_artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!env/bin/python3

import argparse
import os
import sys

from implicitdict import ImplicitDict
from loguru import logger

from monitoring.uss_qualifier.configurations.configuration import (
USSQualifierConfiguration,
USSQualifierConfigurationV1,
)
from monitoring.uss_qualifier.fileio import load_dict_with_references
from monitoring.uss_qualifier.reports.artifacts import generate_artifacts
from monitoring.uss_qualifier.reports.report import TestRunReport, redact_access_tokens


def parseArgs() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate artifacts from USS Qualifier report"
)

parser.add_argument(
"--config",
help="Configuration string according to monitoring/uss_qualifier/configurations/README.md; Several comma-separated strings may be specified",
required=True,
)

parser.add_argument(
"--report",
help="File name of the report to read; Several comma-separated file names matching the configurations may be specified",
required=True,
)

return parser.parse_args()


def main() -> int:
args = parseArgs()

config_names = str(args.config).split(",")

report_paths = str(args.report).split(",")
if len(report_paths) != len(config_names):
raise ValueError(
f"Need matching number of report, expected {len(config_names)}, got {len(report_paths)}"
)

for idx, config_name in enumerate(config_names):
logger.info(
f"========== Generating artifacts for configuration {config_name} =========="
)

config_src = load_dict_with_references(config_name)
whole_config = ImplicitDict.parse(config_src, USSQualifierConfiguration)

report_src = load_dict_with_references(report_paths[idx])
report = ImplicitDict.parse(report_src, TestRunReport)

config: USSQualifierConfigurationV1 = whole_config.v1
if config.artifacts:
generate_artifacts(report, config.artifacts)
else:
logger.warning(f"No artifacts to generate for {config_name}")

logger.info(
f"========== Completed generating artifacts for configuration {config_name} =========="
)

return os.EX_OK


if __name__ == "__main__":
sys.exit(main())
51 changes: 51 additions & 0 deletions monitoring/uss_qualifier/make_artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash

set -eo pipefail

if [[ $# -lt 2 ]]; then
echo "Usage: $0 <CONFIG_NAME(s)> <REPORT_NAME(s)>"
echo "Generates artifacts according to the specified configuration(s) using the specified report(s)"
echo "<CONFIG_NAME>: Location of the configuration file (or multiple locations separated by commas)."
echo "<REPORT_NAME>: Location of the report file (or multiple locations separated by commas)."
exit 1
fi

# Find and change to repo root directory
OS=$(uname)
if [[ "$OS" == "Darwin" ]]; then
# OSX uses BSD readlink
BASEDIR="$(dirname "$0")"
else
BASEDIR=$(readlink -e "$(dirname "$0")")
fi
cd "${BASEDIR}/../.." || exit 1

(
cd monitoring || exit 1
make image
)

CONFIG_NAME="${1}"

REPORT_NAME="${2}"

echo "Generating artifacts from configuration(s): ${CONFIG_NAME}"
echo "Reading report(s) from: ${REPORT_NAME}"

OUTPUT_DIR="monitoring/uss_qualifier/output"
mkdir -p "$OUTPUT_DIR"

CACHE_DIR="monitoring/uss_qualifier/.templates_cache"
mkdir -p "$CACHE_DIR"

# shellcheck disable=SC2086
docker run --name uss_qualifier \
--rm \
-u "$(id -u):$(id -g)" \
-e PYTHONBUFFERED=1 \
-e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \
-v "$(pwd)/$OUTPUT_DIR:/app/$OUTPUT_DIR" \
-v "$(pwd)/$CACHE_DIR:/app/$CACHE_DIR" \
-w /app/monitoring/uss_qualifier \
interuss/monitoring \
python make_artifacts.py --config "$CONFIG_NAME" --report "$REPORT_NAME"
71 changes: 71 additions & 0 deletions monitoring/uss_qualifier/reports/artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import json
import os

from loguru import logger

from implicitdict import ImplicitDict
from monitoring.uss_qualifier.configurations.configuration import ArtifactsConfiguration
from monitoring.uss_qualifier.reports.documents import make_report_html
from monitoring.uss_qualifier.reports.report import TestRunReport, redact_access_tokens
from monitoring.uss_qualifier.reports.sequence_view import generate_sequence_view
from monitoring.uss_qualifier.reports.templates import render_templates
from monitoring.uss_qualifier.reports.tested_requirements import (
generate_tested_requirements,
)


def generate_artifacts(report: TestRunReport, artifacts: ArtifactsConfiguration):
os.makedirs(artifacts.output_path, exist_ok=True)

def _should_redact(cfg) -> bool:
return "redact_access_tokens" in cfg and cfg.redact_access_tokens

logger.info(f"Redacting access tokens from report")
redacted_report = ImplicitDict.parse(json.loads(json.dumps(report)), TestRunReport)
redact_access_tokens(redacted_report)

if artifacts.raw_report:
# Raw report
path = os.path.join(artifacts.output_path, "report.json")
logger.info(f"Writing raw report to {path}")
raw_report = artifacts.raw_report
report_to_write = redacted_report if _should_redact(raw_report) else report
with open(path, "w") as f:
if "indent" in raw_report and raw_report.indent is not None:
json.dump(report_to_write, f, indent=raw_report.indent)
else:
json.dump(report_to_write, f)

if artifacts.report_html:
# HTML rendering of raw report
path = os.path.join(artifacts.output_path, "report.html")
logger.info(f"Writing HTML report to {path}")
report_to_write = (
redacted_report if _should_redact(artifacts.report_html) else report
)
with open(path, "w") as f:
f.write(make_report_html(report_to_write))

if artifacts.templated_reports:
# Templated reports
render_templates(
artifacts.output_path,
artifacts.templated_reports,
redacted_report,
)

if artifacts.tested_requirements:
# Tested requirements view
for tested_reqs_config in artifacts.tested_requirements:
path = os.path.join(artifacts.output_path, tested_reqs_config.report_name)
logger.info(f"Writing tested requirements view to {path}")
generate_tested_requirements(redacted_report, tested_reqs_config, path)

if artifacts.sequence_view:
# Sequence view
path = os.path.join(artifacts.output_path, "sequence")
logger.info(f"Writing sequence view to {path}")
report_to_write = (
redacted_report if _should_redact(artifacts.sequence_view) else report
)
generate_sequence_view(report_to_write, artifacts.sequence_view, path)

0 comments on commit 1973813

Please sign in to comment.