From aa0401d02be786d60845cb2c083350c4b8b1fa91 Mon Sep 17 00:00:00 2001 From: "Niraj Chaudhari (Persistent Systems Inc)" Date: Wed, 6 May 2026 09:57:47 +0530 Subject: [PATCH 1/3] Format changes --- .github/workflows/validate-bicep-params.yml | 34 +-- infra/main.parameters.json | 8 +- infra/main.waf.parameters.json | 12 +- infra/scripts/validate_bicep_params.py | 270 ++++++++++++++++++++ 4 files changed, 297 insertions(+), 27 deletions(-) diff --git a/.github/workflows/validate-bicep-params.yml b/.github/workflows/validate-bicep-params.yml index 3d8433b7..c9aee031 100644 --- a/.github/workflows/validate-bicep-params.yml +++ b/.github/workflows/validate-bicep-params.yml @@ -33,9 +33,16 @@ jobs: - name: Validate infra/ parameters id: validate_infra continue-on-error: true + env: + ACCELERATOR_NAME: ${{ env.accelerator_name }} run: | set +e - python infra/scripts/validate_bicep_params.py --dir infra --strict --no-color --json-output infra_results.json 2>&1 | tee infra_output.txt + RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + python infra/scripts/validate_bicep_params.py --dir infra --strict --no-color \ + --json-output infra_results.json \ + --html-output email_body.html \ + --accelerator-name "${ACCELERATOR_NAME}" \ + --run-url "${RUN_URL}" 2>&1 | tee infra_output.txt EXIT_CODE=${PIPESTATUS[0]} set -e echo "## Infra Param Validation" >> "$GITHUB_STEP_SUMMARY" @@ -60,44 +67,37 @@ jobs: name: bicep-validation-results path: | infra_results.json + email_body.html retention-days: 30 - name: Send schedule notification on failure - if: github.event_name == 'schedule' && steps.result.outputs.status == 'failure' + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && steps.result.outputs.status == 'failure' env: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_RUN_ID: ${{ github.run_id }} ACCELERATOR_NAME: ${{ env.accelerator_name }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - INFRA_OUTPUT=$(sed 's/&/\&/g; s//\>/g' infra_output.txt) + EMAIL_BODY=$(cat email_body.html) jq -n \ --arg name "${ACCELERATOR_NAME}" \ - --arg infra "$INFRA_OUTPUT" \ - --arg url "$RUN_URL" \ - '{subject: ("Bicep Parameter Validation Report - " + $name + " - Issues Detected"), body: ("

Dear Team,

The scheduled Bicep Parameter Validation for " + $name + " has detected parameter mapping errors.

infra/ Results:

" + $infra + "

Run URL: " + $url + "

Please fix the parameter mapping issues at your earliest convenience.

Best regards,
Your Automation Team

")}' \ + --arg body "$EMAIL_BODY" \ + '{subject: ("Bicep Parameter Validation Report - " + $name + " - Issues Detected"), body: $body}' \ | curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d @- || echo "Failed to send notification" - name: Send schedule notification on success - if: github.event_name == 'schedule' && steps.result.outputs.status == 'success' + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && steps.result.outputs.status == 'success' env: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_RUN_ID: ${{ github.run_id }} ACCELERATOR_NAME: ${{ env.accelerator_name }} run: | - RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - INFRA_OUTPUT=$(sed 's/&/\&/g; s//\>/g' infra_output.txt) + EMAIL_BODY=$(cat email_body.html) jq -n \ --arg name "${ACCELERATOR_NAME}" \ - --arg infra "$INFRA_OUTPUT" \ - --arg url "$RUN_URL" \ - '{subject: ("Bicep Parameter Validation Report - " + $name + " - Passed"), body: ("

Dear Team,

The scheduled Bicep Parameter Validation for " + $name + " has completed successfully. All parameter mappings are valid.

infra/ Results:

" + $infra + "

Run URL: " + $url + "

Best regards,
Your Automation Team

")}' \ + --arg body "$EMAIL_BODY" \ + '{subject: ("Bicep Parameter Validation Report - " + $name + " - Passed"), body: $body}' \ | curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ -d @- || echo "Failed to send notification" diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 27461ece..ab469f05 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -6,7 +6,7 @@ "value": "${AZURE_ENV_NAME}" }, "location": { - "value": "${AZURE_LOCATION}" + "value": "${AZURE_LOCATION2323}" }, "contentUnderstandingLocation": { "value": "${AZURE_ENV_CU_LOCATION}" @@ -17,13 +17,13 @@ "deploymentType": { "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" }, - "gptModelName": { + "gptModelName232": { "value": "${AZURE_ENV_GPT_MODEL_NAME}" }, - "gptModelVersion": { + "gptModelVersion232": { "value": "${AZURE_ENV_GPT_MODEL_VERSION}" }, - "gptDeploymentCapacity": { + "gptDeploymentCapacity23": { "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" }, "existingLogAnalyticsWorkspaceId": { diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index 7fdeab31..0224546d 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -6,21 +6,21 @@ "value": "${AZURE_ENV_NAME}" }, "location": { - "value": "${AZURE_LOCATION}" + "value": "${AZURE_LOCATION2342}" }, - "contentUnderstandingLocation": { + "contentUnderstandingL24ocation": { "value": "${AZURE_ENV_CU_LOCATION}" }, - "azureAiServiceLocation": { + "azureAiServ324iceLocation": { "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" }, - "deploymentType": { + "deployme234ntType": { "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" }, - "gptModelName": { + "gptModelN234ame": { "value": "${AZURE_ENV_GPT_MODEL_NAME}" }, - "gptModelVersion": { + "gptModelV324ersion": { "value": "${AZURE_ENV_GPT_MODEL_VERSION}" }, "gptDeploymentCapacity": { diff --git a/infra/scripts/validate_bicep_params.py b/infra/scripts/validate_bicep_params.py index 34ea8d48..6da7d91e 100644 --- a/infra/scripts/validate_bicep_params.py +++ b/infra/scripts/validate_bicep_params.py @@ -341,6 +341,246 @@ def print_report(results: list[ValidationResult], *, use_color: bool = True) -> print(f"{c['ERROR']}Parameter mapping issues detected!{c['RESET']}") +# --------------------------------------------------------------------------- +# HTML email report +# --------------------------------------------------------------------------- + +def _html_escape(text: str) -> str: + """Escape HTML special characters.""" + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + ) + + +def generate_html_report( + results: list[ValidationResult], + *, + accelerator_name: str = "", + run_url: str = "", + scan_dir: str = "", +) -> str: + """Build a structured HTML email body from validation results.""" + total_errors = sum( + 1 for r in results for i in r.issues if i.severity == "ERROR" + ) + total_warnings = sum( + 1 for r in results for i in r.issues if i.severity == "WARNING" + ) + has_errors = total_errors > 0 + overall_status = "Issues Detected" if has_errors else "Passed" + status_color = "#D32F2F" if has_errors else "#2E7D32" + status_bg = "#FFEBEE" if has_errors else "#E8F5E9" + status_icon = "❌" if has_errors else "✅" + + parts: list[str] = [] + + # --- Document wrapper (Outlook-compatible, no gradient/border-radius/box-shadow) --- + parts.append( + '' + '' + '' + '
' + '' + ) + + # --- Header banner (solid color, Outlook-safe) --- + parts.append( + f'' + ) + + # --- Summary card --- + parts.append( + f'") + + # --- Per-pair detail sections --- + parts.append('") + + # --- Footer with run URL --- + footer_parts: list[str] = [] + if run_url: + footer_parts.append( + f'View Workflow Run' + ) + if has_errors: + footer_parts.append( + '

' + 'Please fix the parameter mapping issues at your earliest convenience.

' + ) + footer_parts.append( + '

' + 'Best regards,
Your Automation Team

' + ) + parts.append( + f'' + ) + + # --- Close wrapper --- + parts.append("
' + f'

' + f'Bicep Parameter Validation Report

' + f'

' + f'{_html_escape(accelerator_name) if accelerator_name else "Accelerator"}' + f' — Automated Check

' + f'
' + f'' + f'' + f'
' + f'' + f'{status_icon} Overall Status: {overall_status}' + f'
' + f'' + ) + # Accelerator name pill + if accelerator_name: + parts.append( + f'' + ) + # Scan directory pill + if scan_dir: + parts.append( + f'' + ) + # Error count pill + err_pill_color = "#D32F2F" if total_errors > 0 else "#2E7D32" + parts.append( + f'' + ) + # Warning count pill + warn_pill_color = "#F57C00" if total_warnings > 0 else "#2E7D32" + parts.append( + f'' + ) + parts.append("
' + f'Accelerator
' + f'{_html_escape(accelerator_name)}' + f'
' + f'Scan Directory
' + f'{_html_escape(scan_dir)}/' + f'
' + f'Errors
' + f'' + f'{total_errors}
' + f'Warnings
' + f'' + f'{total_warnings}
') + for r in results: + errors = [i for i in r.issues if i.severity == "ERROR"] + warnings = [i for i in r.issues if i.severity == "WARNING"] + + if not r.issues: + badge = ( + 'PASS' + ) + elif errors: + badge = ( + 'FAIL' + ) + else: + badge = ( + 'WARN' + ) + + parts.append( + f'' + f'' + ) + + if r.issues: + # --- Errors section --- + if errors: + parts.append( + '' + '") + + # --- Warnings section --- + if warnings: + parts.append( + '' + '") + else: + parts.append( + '' + ) + + parts.append("
' + f'{badge} ' + f'' + f'{_html_escape(r.pair)}' + f'' + f'{len(errors)} error(s), {len(warnings)} warning(s)' + f'
' + '' + '● Errors
' + '' + '' + '' + '' + ) + for idx, issue in enumerate(errors): + bg = "#ffffff" if idx % 2 == 0 else "#fff5f5" + parts.append( + f'' + f'' + f'' + f'' + ) + parts.append("
ParameterDetails
' + f'{_html_escape(issue.param_name)}{_html_escape(issue.message)}
' + '' + '● Warnings
' + '' + '' + '' + '' + ) + for idx, issue in enumerate(warnings): + bg = "#ffffff" if idx % 2 == 0 else "#fffaf0" + parts.append( + f'' + f'' + f'' + f'' + ) + parts.append("
ParameterDetails
' + f'{_html_escape(issue.param_name)}{_html_escape(issue.message)}
All parameters validated successfully.' + '
") + + parts.append("
' + f'{"".join(footer_parts)}
") + return "".join(parts) + + # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- @@ -379,6 +619,23 @@ def main() -> int: type=Path, help="Write results as JSON to the given file path.", ) + parser.add_argument( + "--html-output", + type=Path, + help="Write a structured HTML email report to the given file path.", + ) + parser.add_argument( + "--accelerator-name", + type=str, + default="", + help="Accelerator display name for the HTML report header.", + ) + parser.add_argument( + "--run-url", + type=str, + default="", + help="Workflow run URL to include in the HTML report footer.", + ) args = parser.parse_args() results: list[ValidationResult] = [] @@ -415,6 +672,19 @@ def main() -> int: ) print(f"\nJSON report written to {args.json_output}") + # Optional HTML email report + if args.html_output: + scan_dir = str(args.dir) if args.dir else "" + html = generate_html_report( + results, + accelerator_name=args.accelerator_name, + run_url=args.run_url, + scan_dir=scan_dir, + ) + args.html_output.parent.mkdir(parents=True, exist_ok=True) + args.html_output.write_text(html, encoding="utf-8") + print(f"HTML report written to {args.html_output}") + has_errors = any(r.has_errors for r in results) return 1 if args.strict and has_errors else 0 From 483ec481390feae5126e837b6813e4e92a0dabde Mon Sep 17 00:00:00 2001 From: "Niraj Chaudhari (Persistent Systems Inc)" Date: Thu, 7 May 2026 13:51:10 +0530 Subject: [PATCH 2/3] Update email format 4 --- .github/workflows/validate-bicep-params.yml | 4 ++-- infra/main.parameters.json | 8 ++++---- infra/main.waf.parameters.json | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/validate-bicep-params.yml b/.github/workflows/validate-bicep-params.yml index c9aee031..68c5fba6 100644 --- a/.github/workflows/validate-bicep-params.yml +++ b/.github/workflows/validate-bicep-params.yml @@ -71,7 +71,7 @@ jobs: retention-days: 30 - name: Send schedule notification on failure - if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && steps.result.outputs.status == 'failure' + if: github.event_name == 'schedule' && steps.result.outputs.status == 'failure' env: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} ACCELERATOR_NAME: ${{ env.accelerator_name }} @@ -87,7 +87,7 @@ jobs: -d @- || echo "Failed to send notification" - name: Send schedule notification on success - if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && steps.result.outputs.status == 'success' + if: github.event_name == 'schedule' && steps.result.outputs.status == 'success' env: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} ACCELERATOR_NAME: ${{ env.accelerator_name }} diff --git a/infra/main.parameters.json b/infra/main.parameters.json index ab469f05..27461ece 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -6,7 +6,7 @@ "value": "${AZURE_ENV_NAME}" }, "location": { - "value": "${AZURE_LOCATION2323}" + "value": "${AZURE_LOCATION}" }, "contentUnderstandingLocation": { "value": "${AZURE_ENV_CU_LOCATION}" @@ -17,13 +17,13 @@ "deploymentType": { "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" }, - "gptModelName232": { + "gptModelName": { "value": "${AZURE_ENV_GPT_MODEL_NAME}" }, - "gptModelVersion232": { + "gptModelVersion": { "value": "${AZURE_ENV_GPT_MODEL_VERSION}" }, - "gptDeploymentCapacity23": { + "gptDeploymentCapacity": { "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" }, "existingLogAnalyticsWorkspaceId": { diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index 0224546d..7fdeab31 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -6,21 +6,21 @@ "value": "${AZURE_ENV_NAME}" }, "location": { - "value": "${AZURE_LOCATION2342}" + "value": "${AZURE_LOCATION}" }, - "contentUnderstandingL24ocation": { + "contentUnderstandingLocation": { "value": "${AZURE_ENV_CU_LOCATION}" }, - "azureAiServ324iceLocation": { + "azureAiServiceLocation": { "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" }, - "deployme234ntType": { + "deploymentType": { "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" }, - "gptModelN234ame": { + "gptModelName": { "value": "${AZURE_ENV_GPT_MODEL_NAME}" }, - "gptModelV324ersion": { + "gptModelVersion": { "value": "${AZURE_ENV_GPT_MODEL_VERSION}" }, "gptDeploymentCapacity": { From db79814d679b8846cb4cddc06084e7f1cb0e1efa Mon Sep 17 00:00:00 2001 From: "Niraj Chaudhari (Persistent Systems Inc)" Date: Thu, 7 May 2026 17:28:20 +0530 Subject: [PATCH 3/3] Resolve Copilot Comment --- .github/workflows/validate-bicep-params.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate-bicep-params.yml b/.github/workflows/validate-bicep-params.yml index 68c5fba6..4ae614ee 100644 --- a/.github/workflows/validate-bicep-params.yml +++ b/.github/workflows/validate-bicep-params.yml @@ -76,11 +76,13 @@ jobs: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} ACCELERATOR_NAME: ${{ env.accelerator_name }} run: | - EMAIL_BODY=$(cat email_body.html) + if [ ! -f email_body.html ]; then + echo "

Email body was not generated. Please check the workflow logs.

" > email_body.html + fi jq -n \ --arg name "${ACCELERATOR_NAME}" \ - --arg body "$EMAIL_BODY" \ + --rawfile body email_body.html \ '{subject: ("Bicep Parameter Validation Report - " + $name + " - Issues Detected"), body: $body}' \ | curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \ @@ -92,11 +94,13 @@ jobs: LOGICAPP_URL: ${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }} ACCELERATOR_NAME: ${{ env.accelerator_name }} run: | - EMAIL_BODY=$(cat email_body.html) + if [ ! -f email_body.html ]; then + echo "

Email body was not generated. Please check the workflow logs.

" > email_body.html + fi jq -n \ --arg name "${ACCELERATOR_NAME}" \ - --arg body "$EMAIL_BODY" \ + --rawfile body email_body.html \ '{subject: ("Bicep Parameter Validation Report - " + $name + " - Passed"), body: $body}' \ | curl -X POST "${LOGICAPP_URL}" \ -H "Content-Type: application/json" \