azure-validate
Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks,
Install
npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-skills/skills/azure-validate
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
git clone https://github.com/microsoft/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Azure Validate
AUTHORITATIVE GUIDANCE — Follow these instructions exactly unless they contradict security policies given to you.
⛔ STOP — PREREQUISITE CHECK REQUIRED
Before proceeding, verify this prerequisite is met:
azure-prepare was invoked and completed →
.azure/deployment-plan.mdexists with statusApprovedor laterIf the plan is missing, STOP IMMEDIATELY and invoke azure-prepare first.
The complete workflow ensures success:
azure-prepare→azure-validate→azure-deploy
Triggers
- Check if app is ready to deploy
- Validate azure.yaml or Bicep
- Run preflight checks
- Troubleshoot deployment errors
Rules
- Run after azure-prepare, before azure-deploy
- All checks must pass—do not deploy with failures
- ⛔ Destructive actions require
ask_user— global-rules
Steps
Run the workflow script and follow its instructions. It walks you through each validation step one at a time, recording progress in .azure/validate-status.json. Use references/scripts/workflow.ps1 on Windows or references/scripts/workflow.sh on macOS/Linux.
Start by calling the script without the completed-step argument:
pwsh references/scripts/workflow.ps1 -WorkspacePath <workspace-path>
# macOS/Linux: bash references/scripts/workflow.sh --workspace-path <workspace-path>
Each run prints the next action and the value to pass next. Perform the action, then re-run with that value (-CompletedStep <value> for pwsh, --completed-step <value> for bash). Repeat until it reports the azure-validate workflow is complete.
The steps reference recipe details in references/recipes/README.md and role checks in references/role-verification.md.
⛔ VALIDATION AUTHORITY
This skill is the officially verified way to set plan status to
Validated. You MUST follow the script's instructions to completion before setting status toValidated. Do NOT set status toValidatedwithout doing so.
⚠️ NEXT STEP — DEPENDS ON USER INTENT
After ALL validations pass, check whether the user asked to deploy:
- If the user explicitly requested deployment, you MUST invoke azure-deploy to execute it. Do NOT run
azd up,azd deploy, or any deployment commands directly — let azure-deploy handle execution.- If the user only asked to validate or prepare (not deploy), STOP after recording proof and setting status to
Validated. Report the validation results and do NOT invoke azure-deploy.If any validation failed, fix the issues and re-run azure-validate before proceeding.
Files (skills)
-
references
-
recipes
-
azcli
-
errors.md 521 B
# AZCLI Validation Errors | Error | Fix | |-------|-----| | `AADSTS700082: Token expired` | `az login` | | `Please run 'az login'` | `az login` | | `AADSTS50076: MFA required` | `az login --use-device-code` | | `AuthorizationFailed` | Request Contributor role | | `npm ci` fails with `missing: package-lock.json` | Run `npm install --package-lock-only` in the service directory before building | | `Template validation failed` | Check Bicep syntax | ## Debug ```bash az <command> --verbose --debug ``` -
README.md 2.8 KB
# AZCLI Validation Validation steps for Azure CLI deployments. ## Prerequisites - `./infra/main.bicep` exists - Docker available (if containerized) ## Validation Steps - [ ] 1. Core Validation (CLI, auth, build, validate, what-if) — run [`validate-deployment` script](../scripts/validate-deployment.sh) - [ ] 2. Docker Build (if containerized) - [ ] 3. Azure Policy Validation ## Validation Details ### 1. Core Validation Script The core validation checks are a fixed, deterministic sequence. Run the shared **validate-deployment** helper instead of executing and parsing each command by hand. It confirms the Azure CLI is installed and authenticated, compiles the Bicep template (`az bicep build`), validates it against the target scope (`az deployment ... validate`), and runs a what-if preview — printing a compact PASS/FAIL summary plus a what-if change count (Create/Modify/Delete). - Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh) - PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1) **Subscription scope:** ```bash ../scripts/validate-deployment.sh --scope sub --location <location> ``` ```powershell ../scripts/validate-deployment.ps1 -Scope sub -Location <location> ``` **Resource group scope:** ```bash ../scripts/validate-deployment.sh --scope group --resource-group <rg-name> ``` ```powershell ../scripts/validate-deployment.ps1 -Scope group -ResourceGroup <rg-name> ``` Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json` (skipped if absent). Pass `--subscription <id>` to target a specific subscription. **Interpreting results:** - `OVERALL: PASS` — all five checks passed; record the summary in Section 7 (Validation Proof). - Any step `FAIL` — the script prints the failing command's error. Remediate: - **Authenticated** fails → `az login`, then `az account set --subscription <id>`. - **Azure CLI installed** fails → install via `mcp_azure_mcp_extension_cli_install(cli-type: "az")`. - Otherwise see [Error handling](./errors.md). ### 2. Docker Build (if containerized) **Before building**, validate the Docker build context: 1. Read the `Dockerfile` in `./src/<service>` 2. If the Dockerfile contains `npm ci`, verify `package-lock.json` exists in the same directory 3. If `package-lock.json` is missing, generate it: ```bash cd ./src/<service> npm install --package-lock-only ``` **Then build:** ```bash docker build -t <image>:test ./src/<service> ``` ### 3. Azure Policy Validation See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. ## References - [Error handling](./errors.md) ## Next All checks pass → **azure-deploy**
-
-
azd
-
scripts
-
set-aspire-aca-env.ps1 3.5 KB · in bundle
-
set-aspire-aca-env.sh 3.6 KB
#!/usr/bin/env bash # Set the Container Apps environment variables that Aspire "limited mode" leaves unpopulated. # # When Aspire runs in "limited mode", `azd provision` creates the Azure resources # (Container Registry, Managed Identity, Container Apps Environment) but does NOT populate the # env vars that `azd deploy` needs to reference them. This script fills that gap. # # Run it AFTER `azd provision` but BEFORE `azd deploy`. # # USAGE: # ./set-aspire-aca-env.sh [-e <azd-env-name>] # # -e, --environment Optional azd environment name (forwarded to `azd env` calls). # Defaults to the current/default azd environment. # # The script only sets a variable if it is currently missing, and prints what it did so the # result can be understood without re-inspecting `azd env get-values`: # AZURE_CONTAINER_REGISTRY_ENDPOINT <- az acr list ... [0].loginServer # AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID <- az identity list ... [0].id # MANAGED_IDENTITY_CLIENT_ID <- az identity list ... [0].clientId set -e AZD_ENV_NAME="" while [ $# -gt 0 ]; do case "$1" in -e|--environment) AZD_ENV_NAME="$2" shift 2 ;; *) echo "ERROR: Unknown argument: $1" >&2 echo "USAGE: ./set-aspire-aca-env.sh [-e <azd-env-name>]" >&2 exit 1 ;; esac done # Build the shared `-e <name>` argument list for azd calls (empty when no env name given). AZD_ENV_ARGS="" if [ -n "$AZD_ENV_NAME" ]; then AZD_ENV_ARGS="-e $AZD_ENV_NAME" fi # Capture azd environment values via command substitution so `set -e` aborts if the # `azd env get-values` call itself fails (rather than silently continuing with no values). AZD_VALUES=$(azd env get-values $AZD_ENV_ARGS) # get_env_value <KEY> — print the (unquoted) value of KEY from AZD_VALUES, empty if absent. # Uses only POSIX-friendly tools so it works on the widely-available Bash 3.2 (e.g. macOS). get_env_value() { printf '%s\n' "$AZD_VALUES" \ | grep "^$1=" \ | head -n 1 \ | sed -e "s/^$1=//" -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/" } RG_NAME=$(get_env_value AZURE_RESOURCE_GROUP) if [ -z "$RG_NAME" ]; then echo "ERROR: AZURE_RESOURCE_GROUP is not set in the azd environment." >&2 echo "Run 'azd provision' before this script so the resource group is available." >&2 exit 1 fi # set_if_missing <ENV_VAR_NAME> <description> <resolver command...> # The resolver command is passed as arguments and run via "$@" — no eval. set_if_missing() { var_name="$1" description="$2" shift 2 existing=$(get_env_value "$var_name") if [ -n "$existing" ]; then echo "$var_name: already present ($existing)" return 0 fi value=$("$@") if [ -z "$value" ]; then echo "ERROR: Could not resolve $var_name ($description) in resource group '$RG_NAME'." >&2 echo "Confirm 'azd provision' completed and the resource exists." >&2 exit 1 fi azd env set $AZD_ENV_ARGS "$var_name" "$value" echo "$var_name: set to $value" } echo "Resource group: $RG_NAME" set_if_missing \ "AZURE_CONTAINER_REGISTRY_ENDPOINT" \ "container registry login server" \ az acr list --resource-group "$RG_NAME" --query "[0].loginServer" -o tsv set_if_missing \ "AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID" \ "managed identity resource id" \ az identity list --resource-group "$RG_NAME" --query "[0].id" -o tsv set_if_missing \ "MANAGED_IDENTITY_CLIENT_ID" \ "managed identity client id" \ az identity list --resource-group "$RG_NAME" --query "[0].clientId" -o tsv echo "Aspire Container Apps environment variables are ready for 'azd deploy'."
-
-
aspire.md 1.7 KB
# Aspire Validation > ⚠️ **Only load this file when the project is a .NET Aspire application.** Validation steps specific to .NET Aspire projects deployed via AZD. ## Detection A project is Aspire-based if any of these are true: | Indicator | Check | |-----------|-------| | AppHost project | `find . -name "*.AppHost.csproj"` | | Aspire.Hosting package | `grep -r "Aspire.Hosting" . --include="*.csproj"` | **If none found → skip this file entirely.** --- ## Pre-Provisioning: Functions Secret Storage > ⚠️ **CRITICAL — Must run BEFORE `azd provision`.** Check if the project uses Azure Functions within Aspire and ensure `AzureWebJobsSecretStorageType` is configured. See [Aspire Functions Secrets Reference](../../aspire-functions-secrets.md) for detection commands, fix examples, and full details. **If `AddAzureFunctionsProject` is NOT found**, skip this section. --- ## Post-Provisioning: Container Apps Environment Variables > ⚠️ **CRITICAL — Run AFTER `azd provision` but BEFORE `azd deploy`.** When using Aspire with Container Apps in "limited mode" (in-memory infrastructure generation), `azd provision` creates Azure resources but doesn't automatically populate environment variables that `azd deploy` needs. **Run the helper script** ([scripts/set-aspire-aca-env.sh](scripts/set-aspire-aca-env.sh) or [scripts/set-aspire-aca-env.ps1](scripts/set-aspire-aca-env.ps1)). It sets `AZURE_CONTAINER_REGISTRY_ENDPOINT`, `AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID`, and `MANAGED_IDENTITY_CLIENT_ID` (only the ones that are missing). **bash:** ```bash ./scripts/set-aspire-aca-env.sh # or: -e <azd-env-name> ``` **PowerShell:** ```powershell ./scripts/set-aspire-aca-env.ps1 # or: -Environment <azd-env-name> ``` -
environment.md 2 KB
# AZD Environment Setup > **⛔ MANDATORY**: You MUST set up an AZD environment before running any deployment commands. ## Step 1: Check Existing Environments ```bash azd env list ``` **If an environment is already selected** (marked with `*`), check its current values: ```bash azd env get-values ``` If `AZURE_ENV_NAME`, `AZURE_SUBSCRIPTION_ID`, and `AZURE_LOCATION` are already set, confirm with user: ``` Question: "An AZD environment is already configured. Would you like to use it?" Environment: {env-name} Subscription: {subscription-id} Location: {location} Choices: [ "Yes, use this environment (Recommended)", "No, create a new environment" ] ``` If user confirms → skip to **Verify Configuration**. Otherwise → continue to Step 2. **If NO environment exists or none is selected:** - Continue to Step 2 --- ## Step 2: Create New Environment > **⛔ DO NOT use generic names like "dev", "prod", or "test"** > > These cause naming conflicts in Azure resource groups and resources. Always generate a unique name. ### Generate Suggested Name Use this pattern: ``` {project-name}-{random-4-chars} ``` **Examples:** - `dadjokes-x7k2` - `todoapp-m3p9` - `myapi-q5w8` ### Prompt User **You MUST use `ask_user` to confirm the environment name:** ``` I need to create an AZD environment for this deployment. Suggested name: {project-name}-{random-4-chars} Would you like to use this name or enter a custom one? ``` ### Create Environment After user confirms: ```bash azd env new <environment-name> --no-prompt ``` --- ## Step 3: Configure Environment Set subscription and location: ```bash azd env set AZURE_SUBSCRIPTION_ID <subscription-id> azd env set AZURE_LOCATION <location> ``` --- ## Verify Configuration ```bash azd env get-values ``` Confirm these values are set: - `AZURE_ENV_NAME` - `AZURE_SUBSCRIPTION_ID` - `AZURE_LOCATION` --- ## Only Then Proceed After environment is configured, proceed with `azd up --no-prompt`. -
errors.md 2.7 KB
# AZD Validation Errors ## Pre-Deployment Errors These errors can be caught **before** running `azd up`: | Error | Cause | Resolution | |-------|-------|------------| | `Please run 'az login'` | Not authenticated | `az login` or `azd auth login` | | `No environment selected` | Missing azd environment | `azd env select <name>` or `azd env new <name> --no-prompt` | | `no default response for prompt 'Enter a unique environment name'` | No azd environment created, or missing `-e` flag | Run `azd env new <name> --no-prompt` OR use `azd init --from-code -e <name> --no-prompt` with the `-e` flag | | `no default response for prompt 'Enter a value for the 'environmentName'` | Environment variables not set | Run `azd env set AZURE_ENV_NAME <name>` | | `Service not found` | Service name mismatch | Check service name in azure.yaml | | `Invalid azure.yaml` | YAML syntax error | Fix YAML syntax | | `Project path does not exist` | Wrong service project path | Fix service project path in azure.yaml | | `Cannot connect to Docker daemon` | Docker not running | Start Docker Desktop | | `npm ci` fails with `missing: package-lock.json` | Dockerfile uses `npm ci` but `package-lock.json` not in build context | Run `npm install --package-lock-only` in the service directory before building | | `Could not find a part of the path 'infra\main.bicep'` | Missing infrastructure files | Generate infra/ folder before `azd up` | | `Invalid resource group location '<loc>'. The Resource group already exists in location '<other>'` | RG exists in different region | Check RG location first with `az group show`, use that region or new env name | | `expecting only '1' resource tagged with 'azd-service-name: web', but found '2'` | Multiple resources with same tag **in the same RG** | Delete duplicate or rename service | ## Static Web App Errors | Error | Cause | Fix | |-------|-------|-----| | `language 'html' is not supported` | Invalid language value | Omit `language` for pure static sites | | `language 'static' is not supported` | Invalid language value | Omit `language` for pure static sites | | `dist folder not found` | Wrong dist path or missing build | Check `dist` is relative to `project`; add `language: js` if build needed | | `LocationNotAvailableForResourceType` | SWA not in region | See [Region Availability](../../region-availability.md) for valid regions | ## SWA Path Validation Before deployment, verify: 1. `project` path exists and contains source files 2. For framework apps: `language: js` is set 3. `dist` is relative to `project` (not project root) 4. Bicep has `azd-service-name` tag matching service name ## Debug ```bash azd <command> --debug ``` -
README.md 5.4 KB
# AZD Validation Validation steps for Azure Developer CLI projects. ## Prerequisites - `azure.yaml` exists in project root - Infrastructure files exist: - For Bicep: `./infra/` contains Bicep files - For Terraform: `./infra/` contains `.tf` files and `azure.yaml` has `infra.provider: terraform` ## Validation Steps - [ ] 1. AZD Installation - [ ] 2. Schema Validation - [ ] 3. Environment Setup - [ ] 4. Authentication Check - [ ] 5. Subscription/Location Check - [ ] 6. Aspire Pre-Provisioning Checks - [ ] 7. Provision Preview - [ ] 8. Build Verification - [ ] 9. Docker Build Context Validation - [ ] 10. Package Validation - [ ] 11. Azure Policy Validation - [ ] 12. Aspire Post-Provisioning Checks ## Validation Details ### 1. AZD Installation Verify AZD is installed: ```bash azd version ``` **If not installed:** ``` mcp_azure_mcp_extension_cli_install(cli-type: "azd") ``` ### 2. Schema Validation Validate azure.yaml against official schema: ``` mcp_azure_mcp_azd(command: "validate_azure_yaml", parameters: { path: "./azure.yaml" }) ``` ### 3. Environment Setup Verify AZD environment exists and is configured. See [Environment Setup](environment.md) for detailed steps. ### 4. Authentication Check ```bash azd auth login --check-status ``` **If not logged in:** ```bash azd auth login ``` ### 5. Subscription/Location Check Check environment values: ```bash azd env get-values ``` **If AZURE_SUBSCRIPTION_ID or AZURE_LOCATION not set:** Use Azure MCP tools to list subscriptions: ``` mcp_azure_mcp_subscription_list ``` Use Azure MCP tools to list resource groups (check for conflicts): ``` mcp_azure_mcp_group_list subscription: <subscription-id> ``` Prompt user to confirm subscription and location before continuing. Refer to the region availability reference to select a region supported by all services in this template: - [Region availability](../../region-availability.md) ```bash azd env set AZURE_SUBSCRIPTION_ID <subscription-id> azd env set AZURE_LOCATION <location> ``` ### 6. Aspire Pre-Provisioning Checks **If this is a .NET Aspire project** (detected by `*.AppHost.csproj` or `Aspire.Hosting` package reference), run the **Pre-Provisioning** checks in [Aspire Validation](aspire.md) before continuing. **If not Aspire, skip this step.** ### 7. Provision Preview Validate IaC is ready (must complete without error): ```bash azd provision --preview --no-prompt ``` > 💡 **Note:** This works for both Bicep and Terraform. azd will automatically detect the provider from `azure.yaml` and run the appropriate validation (`bicep build` or `terraform plan`). **If `azd provision --preview` fails with `unsupported resource type`:** ⛔ **Stop deployment immediately.** Do NOT attempt to fix or work around the error. This error means the Aspire AppHost contains custom resource types that have no Azure deployment target (e.g., `HealthChecksUI`, custom child resources, or local-only integrations). These resources are intentionally designed for local development tooling and cannot be meaningfully deployed to Azure. **Required actions:** 1. ⛔ **Do NOT modify source code** to suppress the error (e.g., do not add `.ExcludeFromManifest()`). 2. ⛔ **Do NOT proceed with `azd provision` or `azd deploy`.** 3. ✅ Inform the user: "This application contains custom Aspire resource types (`unsupported resource type: <type-name>`) that cannot be deployed to Azure. The application is designed for local development only." 4. ✅ Record a blocker: "Deployment blocked — AppHost contains unsupported resource types with no Azure deployment target." > ⚠️ Adding `.ExcludeFromManifest()` to suppress the error may allow provisioning to proceed, but the resulting deployment will not represent the application's actual functionality and is not a valid deployment. ### 8. Build Verification Build the project and verify there are no errors. If the build fails, fix the issues and re-build until it succeeds. Do NOT proceed to packaging or deployment with build errors. ### 9. Docker Build Context Validation **If any service in `azure.yaml` uses a Dockerfile** (check the service's `project` path from `azure.yaml` for a `Dockerfile`), validate the build context before packaging: 1. Read each service's `Dockerfile` 2. If the Dockerfile contains `npm ci`, verify `package-lock.json` exists in the same directory 3. If `package-lock.json` is missing, generate it in the service's `project` path directory before proceeding: ```bash cd <service-project-path> npm install --package-lock-only ``` > ⚠️ **Warning:** `npm ci` will fail during Docker build if `package-lock.json` is missing. This check prevents Docker build failures during `azd package` and `azd up`. ### 10. Package Validation Confirm all services package successfully: ```bash azd package --no-prompt ``` ### 11. Azure Policy Validation See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. ### 12. Aspire Post-Provisioning Checks **If this is a .NET Aspire project**, run the **Post-Provisioning** checks in [Aspire Validation](aspire.md) before proceeding to deployment. **If not Aspire, skip this step.** ## References - [Environment Setup](environment.md) - [Aspire Validation](aspire.md) - [Error Handling](./errors.md) ## Next All checks pass → **azure-deploy**
-
-
bicep
-
errors.md 389 B
# Bicep Validation Errors | Error | Fix | |-------|-----| | `BCP035: Invalid type` | Check API version | | `BCP037: Not a member` | Check resource schema | | `BCP018: Expected character` | Fix syntax | | `Module not found` | Check relative paths | | `Template validation failed` | Review error details | ## Debug ```bash az bicep build --file ./infra/main.bicep 2>&1 ``` -
README.md 2.6 KB
# Bicep Validation Validation steps for standalone Bicep deployments. ## Prerequisites - `./infra/main.bicep` exists - `./infra/main.parameters.json` exists - Azure CLI authenticated ## Validation Steps - [ ] 1. Core Validation (CLI, auth, build, validate, what-if) — run [`validate-deployment` script](../scripts/validate-deployment.sh) - [ ] 2. Linting (optional) - [ ] 3. Azure Policy Validation ## Validation Details ### 1. Core Validation Script The core validation checks are a fixed, deterministic sequence — identical to the AZCLI recipe. Run the shared **validate-deployment** helper instead of executing and parsing each command by hand. It confirms the Azure CLI is installed and authenticated, compiles the Bicep template (`az bicep build`), validates it against the target scope (`az deployment ... validate`), and runs a what-if preview — printing a compact PASS/FAIL summary plus a what-if change count (Create/Modify/Delete). - Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh) - PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1) **Subscription scope:** ```bash ../scripts/validate-deployment.sh --scope sub --location <location> ``` ```powershell ../scripts/validate-deployment.ps1 -Scope sub -Location <location> ``` **Resource group scope:** ```bash ../scripts/validate-deployment.sh --scope group --resource-group <rg-name> ``` ```powershell ../scripts/validate-deployment.ps1 -Scope group -ResourceGroup <rg-name> ``` Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json` (skipped if absent). Pass `--subscription <id>` to target a specific subscription. **Interpreting results:** - `OVERALL: PASS` — all checks passed; record the summary in Section 7 (Validation Proof). - Any step `FAIL` — the script prints the failing command's error. If **Authenticated** fails, run `az login`. Otherwise see [Error handling](./errors.md). ### 2. Linting (optional) Use Bicep linter rules: ```bash az bicep lint --file ./infra/main.bicep ``` ### 3. Azure Policy Validation See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. ## Checklist | Check | Command | Pass | |-------|---------|------| | Core validation (CLI, auth, build, validate, what-if) | `validate-deployment` script | ☐ | | Policies validated | MCP Policy tool | ☐ | ## References - [Error handling](./errors.md) ## Next All checks pass → **azure-deploy**
-
-
scripts
-
validate-deployment.ps1 5.2 KB · in bundle
-
validate-deployment.sh 6 KB
#!/usr/bin/env bash # validate-deployment.sh # Runs the standard Azure CLI pre-deployment validation sequence for a Bicep # template and reports PASS/FAIL for each step. Shared by the AZCLI and Bicep # validation recipes. # # Steps (in order): # 1. az version - Azure CLI is installed # 2. az account show - authenticated to Azure # 3. az bicep build - template compiles cleanly # 4. az deployment ... validate - template validates against the target scope # 5. az deployment ... what-if - preview changes (with a Create/Modify/Delete summary) # # Usage: # ./validate-deployment.sh --scope sub --location <location> [options] # ./validate-deployment.sh --scope group --resource-group <rg-name> [options] # # Options: # --scope <sub|group> Deployment scope (required) # --location <location> Location (required when --scope sub) # --resource-group <name> Resource group (required when --scope group) # --template <path> Bicep template (default: ./infra/main.bicep) # --parameters <path> Parameters file (default: ./infra/main.parameters.json; # skipped automatically if the file does not exist) # --subscription <id> Subscription to target (optional) # # Examples: # ./validate-deployment.sh --scope sub --location eastus # ./validate-deployment.sh --scope group --resource-group my-rg \ # --template ./infra/main.bicep --parameters ./infra/main.parameters.json # # Exit codes: # 0 - every validation step passed # 1 - a validation step failed # 2 - usage / argument error (unknown or valueless option, missing required flag) set -uo pipefail # Ensure an option that consumes a value actually has one ($@ = remaining args). need_val() { [ "$#" -ge 2 ] || { echo "ERROR: $1 requires a value." >&2; exit 2; } } SCOPE="" LOCATION="" RESOURCE_GROUP="" TEMPLATE="./infra/main.bicep" PARAMETERS="./infra/main.parameters.json" SUBSCRIPTION="" while [ $# -gt 0 ]; do case "$1" in --scope) need_val "$@"; SCOPE="$2"; shift 2 ;; --location) need_val "$@"; LOCATION="$2"; shift 2 ;; --resource-group) need_val "$@"; RESOURCE_GROUP="$2"; shift 2 ;; --template) need_val "$@"; TEMPLATE="$2"; shift 2 ;; --parameters) need_val "$@"; PARAMETERS="$2"; shift 2 ;; --subscription) need_val "$@"; SUBSCRIPTION="$2"; shift 2 ;; -h|--help) grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "Unknown argument: $1" >&2 exit 2 ;; esac done # Validate arguments if [ "$SCOPE" != "sub" ] && [ "$SCOPE" != "group" ]; then echo "ERROR: --scope must be 'sub' or 'group'." >&2 exit 2 fi if [ "$SCOPE" = "sub" ] && [ -z "$LOCATION" ]; then echo "ERROR: --location is required when --scope is 'sub'." >&2 exit 2 fi if [ "$SCOPE" = "group" ] && [ -z "$RESOURCE_GROUP" ]; then echo "ERROR: --resource-group is required when --scope is 'group'." >&2 exit 2 fi # Build shared argument arrays SUB_ARGS=() [ -n "$SUBSCRIPTION" ] && SUB_ARGS=(--subscription "$SUBSCRIPTION") PARAM_ARGS=() if [ -f "$PARAMETERS" ]; then PARAM_ARGS=(--parameters "$PARAMETERS") else echo "NOTE: parameters file '$PARAMETERS' not found; validating without --parameters." fi if [ "$SCOPE" = "sub" ]; then SCOPE_TARGET_ARGS=(--location "$LOCATION") SCOPE_DESC="subscription (location: $LOCATION)" else SCOPE_TARGET_ARGS=(--resource-group "$RESOURCE_GROUP") SCOPE_DESC="resource group '$RESOURCE_GROUP'" fi # Track overall result (0 = all passed, 1 = at least one failure). OVERALL=0 echo "=== Azure deployment validation ===" echo "Template: $TEMPLATE" echo "Scope: $SCOPE_DESC" echo "" # Step 1: Azure CLI installed echo "--- Step 1: Azure CLI installed (az version) ---" if az version >/dev/null 2>&1; then echo "PASS: Azure CLI is installed." else echo "FAIL: Azure CLI not found. Install it, then re-run." # Nothing else can run without the CLI. echo "" echo "OVERALL: FAIL" exit 1 fi echo "" # Step 2: Authenticated echo "--- Step 2: Authenticated (az account show) ---" if ACCOUNT_NAME=$(az account show "${SUB_ARGS[@]}" --query name -o tsv 2>/dev/null); then echo "PASS: Authenticated (subscription: ${ACCOUNT_NAME:-unknown})." else echo "FAIL: Not logged in. Run 'az login' (and 'az account set --subscription <id>')." OVERALL=1 fi echo "" # Step 3: Bicep compilation echo "--- Step 3: Bicep compilation (az bicep build) ---" BUILD_OUTPUT=$(az bicep build --file "$TEMPLATE" 2>&1) BUILD_RC=$? if [ $BUILD_RC -eq 0 ]; then echo "PASS: Template compiles cleanly." else echo "FAIL: Bicep compilation errors:" echo "$BUILD_OUTPUT" OVERALL=1 fi echo "" # Step 4: Template validation echo "--- Step 4: Template validation (az deployment $SCOPE validate) ---" VALIDATE_OUTPUT=$(az deployment "$SCOPE" validate "${SCOPE_TARGET_ARGS[@]}" \ --template-file "$TEMPLATE" \ "${PARAM_ARGS[@]}" "${SUB_ARGS[@]}" 2>&1) VALIDATE_RC=$? if [ $VALIDATE_RC -eq 0 ]; then echo "PASS: Template validated against the target scope." else echo "FAIL: Template validation errors:" echo "$VALIDATE_OUTPUT" OVERALL=1 fi echo "" # Step 5: What-if preview echo "--- Step 5: What-if preview (az deployment $SCOPE what-if) ---" WHATIF_OUTPUT=$(az deployment "$SCOPE" what-if "${SCOPE_TARGET_ARGS[@]}" \ --template-file "$TEMPLATE" \ "${PARAM_ARGS[@]}" "${SUB_ARGS[@]}" 2>&1) WHATIF_RC=$? if [ $WHATIF_RC -eq 0 ]; then CREATE_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*+ ') MODIFY_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*~ ') DELETE_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*- ') echo "PASS: What-if completed. Changes -> Create: $CREATE_COUNT, Modify: $MODIFY_COUNT, Delete: $DELETE_COUNT" else echo "FAIL: What-if errors:" echo "$WHATIF_OUTPUT" OVERALL=1 fi echo "" # Overall result if [ $OVERALL -eq 0 ]; then echo "OVERALL: PASS" else echo "OVERALL: FAIL" fi exit $OVERALL
-
-
terraform
-
scripts
-
validate-terraform.ps1 6.8 KB · in bundle
-
validate-terraform.sh 6.6 KB
#!/usr/bin/env bash # validate-terraform.sh # Runs the Terraform pre-deployment validation preflight sequence and prints a # compact PASS/FAIL/SKIP summary plus the captured error text for any failed step. # # The script runs every check even if an earlier one fails, so you get a complete # verdict in a single call. It never fixes anything - it only runs and reports, so # you can jump straight to remediation for any failed step. # # Steps: terraform present, az present, authenticated, init, fmt -check, validate, # plan, state list, Go-style {{ .Env.* }} template-variable scan. # # Usage: # ./validate-terraform.sh [infra-dir] [subscription-id] # # Arguments: # infra-dir Path to the Terraform infra directory (default: ./infra). # subscription-id Optional subscription to select before checks. # # Examples: # ./validate-terraform.sh # Validate ./infra # ./validate-terraform.sh ./infra # Validate an explicit directory # ./validate-terraform.sh ./infra 00000000-0000-0000-0000-000000000000 # # Exit code: 0 when every non-skipped step passes, 1 when any step fails. set -uo pipefail INFRA_DIR="${1:-./infra}" SUBSCRIPTION_ID="${2:-}" # --- result tracking --------------------------------------------------------- STEP_NAMES=() STEP_STATUS=() # PASS | FAIL | SKIP STEP_ERRORS=() # captured error text (empty unless FAIL) record() { # record <name> <status> [error-text] # Results are collected here and rendered once in the summary at the end. STEP_NAMES+=("$1") STEP_STATUS+=("$2") STEP_ERRORS+=("${3:-}") } echo "Terraform validation preflight - infra dir: $INFRA_DIR" echo "" # --- 1. Terraform installed -------------------------------------------------- if command -v terraform >/dev/null 2>&1; then record "Terraform installed" "PASS" else record "Terraform installed" "FAIL" "terraform not found on PATH. Install: https://developer.hashicorp.com/terraform/install" fi # --- 2. Azure CLI installed -------------------------------------------------- if command -v az >/dev/null 2>&1; then record "Azure CLI installed" "PASS" else record "Azure CLI installed" "FAIL" "az not found on PATH. Install the Azure CLI: mcp_azure_mcp_extension_cli_install(cli-type: \"az\")" fi # --- 3. Authentication ------------------------------------------------------- if command -v az >/dev/null 2>&1; then if [ -n "$SUBSCRIPTION_ID" ]; then if SUB_OUT=$(az account set --subscription "$SUBSCRIPTION_ID" 2>&1); then record "Select subscription" "PASS" else record "Select subscription" "FAIL" "$SUB_OUT" fi fi if ACCOUNT_OUT=$(az account show -o none 2>&1); then record "Authenticated (az account show)" "PASS" else record "Authenticated (az account show)" "FAIL" "$ACCOUNT_OUT" fi else record "Authenticated (az account show)" "SKIP" "Azure CLI not installed" fi # --- infra dir presence gate ------------------------------------------------- HAVE_TF=false if command -v terraform >/dev/null 2>&1 && [ -d "$INFRA_DIR" ]; then HAVE_TF=true fi run_tf() { # run_tf <name> <terraform args...> local name="$1"; shift if [ "$HAVE_TF" != true ]; then record "$name" "SKIP" "terraform unavailable or infra dir '$INFRA_DIR' not found" return fi # Stream output to a temp file so large output (e.g. terraform plan) is not # held in memory; only read it back when the command fails. local tmp tmp=$(mktemp) if (cd "$INFRA_DIR" && terraform "$@") >"$tmp" 2>&1; then record "$name" "PASS" else record "$name" "FAIL" "$(cat "$tmp")" fi rm -f "$tmp" } # --- 4. Initialize ----------------------------------------------------------- run_tf "terraform init" init -input=false # --- 5. Format check --------------------------------------------------------- run_tf "terraform fmt -check" fmt -check -recursive # --- 6. Validate syntax ------------------------------------------------------ run_tf "terraform validate" validate # --- 7. Plan preview --------------------------------------------------------- run_tf "terraform plan" plan -input=false -out=tfplan # --- 8. State backend -------------------------------------------------------- run_tf "terraform state list" state list # --- 9. Go-style template-variable scan -------------------------------------- if [ -d "$INFRA_DIR" ]; then TEMPLATE_HITS=$(grep -rn '{{ *\.Env\.' "$INFRA_DIR" --include='*.tf' --include='*.tfvars.json' 2>/dev/null || true) if [ -n "$TEMPLATE_HITS" ]; then record "Template-variable scan ({{ .Env.* }})" "FAIL" \ "Found unresolved Go-style template variables - replace {{ .Env.VAR }} with \${VAR} (azd envsubst format): $TEMPLATE_HITS" else record "Template-variable scan ({{ .Env.* }})" "PASS" fi else record "Template-variable scan ({{ .Env.* }})" "SKIP" "infra dir '$INFRA_DIR' not found" fi # --- 10. main.tfvars.json JSON syntax ---------------------------------------- TFVARS="$INFRA_DIR/main.tfvars.json" if [ -f "$TFVARS" ]; then if command -v python3 >/dev/null 2>&1; then if JSON_ERR=$(python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$TFVARS" 2>&1); then record "main.tfvars.json is valid JSON" "PASS" else record "main.tfvars.json is valid JSON" "FAIL" "$JSON_ERR" fi elif command -v jq >/dev/null 2>&1; then if JSON_ERR=$(jq empty "$TFVARS" 2>&1); then record "main.tfvars.json is valid JSON" "PASS" else record "main.tfvars.json is valid JSON" "FAIL" "$JSON_ERR" fi else record "main.tfvars.json is valid JSON" "SKIP" "no JSON parser (python3/jq) available" fi else record "main.tfvars.json is valid JSON" "SKIP" "$TFVARS not found" fi # --- summary ----------------------------------------------------------------- echo "" echo "==================== SUMMARY ====================" printf '%-40s %s\n' "STEP" "RESULT" printf '%-40s %s\n' "----" "------" FAILED=0 for i in "${!STEP_NAMES[@]}"; do printf '%-40s %s\n' "${STEP_NAMES[$i]}" "${STEP_STATUS[$i]}" [ "${STEP_STATUS[$i]}" = "FAIL" ] && FAILED=$((FAILED + 1)) done echo "=================================================" if [ "$FAILED" -gt 0 ]; then echo "" echo "----- FAILURE DETAILS -----" for i in "${!STEP_NAMES[@]}"; do if [ "${STEP_STATUS[$i]}" = "FAIL" ]; then echo "" echo "### ${STEP_NAMES[$i]}" echo "${STEP_ERRORS[$i]}" fi done echo "" echo "RESULT: $FAILED step(s) failed. See remediation guidance in README.md." exit 1 fi echo "" echo "RESULT: All checks passed. Ready for azure-deploy." exit 0
-
-
errors.md 2.9 KB
# Terraform Validation Errors | Error | Fix | |-------|-----| | `Backend init failed` | Check storage account access | | `Provider version conflict` | Update required_providers | | `State lock failed` | Wait or force unlock | | `Validation failed` | Check terraform validate output | | `Error: Cycle:` | See [Cycle Errors](#cycle-errors) below | ## Cycle Errors `terraform validate` reports a cycle when two or more resources reference each other's attributes, creating a circular dependency. ### Common Pattern: CORS Cross-Reference Multi-service App Service deployments often introduce a cycle when the API's CORS configuration references the frontend hostname and the frontend's app settings reference the API hostname: ``` Error: Cycle: azurerm_linux_web_app.frontend, azurerm_linux_web_app.api ``` **Cause — circular attribute references:** ```hcl # API references frontend.default_hostname in CORS resource "azurerm_linux_web_app" "api" { site_config { cors { allowed_origins = ["https://${azurerm_linux_web_app.frontend.default_hostname}"] } } } # Frontend references api.default_hostname in app_settings resource "azurerm_linux_web_app" "frontend" { app_settings = { API_URL = "https://${azurerm_linux_web_app.api.default_hostname}" } } ``` ### Fix Strategies **Option A (recommended):** Use a Terraform variable for the frontend origin so CORS is restrictive by default and the cycle is broken. Define the variable with a sensible default and pass the real frontend URL after the first deployment: ```hcl variable "frontend_origin" { type = string description = "Frontend origin for API CORS. Set after first deployment." default = "" } resource "azurerm_linux_web_app" "api" { site_config { cors { allowed_origins = var.frontend_origin != "" ? [var.frontend_origin] : ["*"] support_credentials = var.frontend_origin != "" ? true : false } } } ``` > ⚠️ **Warning:** If using `["*"]` as a temporary bootstrap value, you **must** set `frontend_origin` to the actual URL (e.g., `https://app-web-*.azurewebsites.net`) and re-run `terraform apply` in the same deployment session before reporting success. Do not leave wildcard CORS in a completed deployment. **Option B:** Use `azurerm_app_service_custom_hostname_binding` or a `null_resource` with a `local-exec` provisioner to configure CORS after both resources are created, breaking the dependency chain. **Option C:** Use `lifecycle { ignore_changes = [site_config[0].cors] }` on the API resource and configure CORS via a separate `azurerm_web_app_active_slot` or post-deployment script. ### After Fixing 1. Run `terraform fmt -recursive` to fix formatting 2. Re-run `terraform validate` to confirm the cycle is resolved 3. Run `terraform plan` to verify the configuration is correct ## Debug ```bash TF_LOG=DEBUG terraform plan ``` -
README.md 4.1 KB
# Terraform Validation Validation steps for Terraform deployments. ## Prerequisites - `./infra/main.tf` exists - State backend accessible ## Run the preflight script Run the pre-built validation script instead of executing each check by hand. It runs the full deterministic preflight sequence in one call and prints a compact **PASS / FAIL / SKIP** summary plus captured error text for any failed step — jump straight to remediation without re-parsing raw command output. | Script | Purpose | |--------|---------| | [`scripts/validate-terraform.sh`](scripts/validate-terraform.sh) | Bash preflight runner | | [`scripts/validate-terraform.ps1`](scripts/validate-terraform.ps1) | PowerShell preflight runner | The script runs, in order: Terraform installed → Azure CLI installed → authenticated (`az account show`) → `terraform init` → `fmt -check` → `validate` → `plan` → `state list` → Go-style `{{ .Env.* }}` template-variable scan → `main.tfvars.json` JSON-syntax check. A subscription-selection step is added when a subscription id is supplied. It runs **every** check even if an earlier one fails, and exits non-zero when any step fails. **Usage:** ```bash ./scripts/validate-terraform.sh [infra-dir] [subscription-id] # infra-dir defaults to ./infra ``` ```powershell .\scripts\validate-terraform.ps1 [-InfraDir <path>] [-SubscriptionId <id>] ``` **Examples:** ```bash ./scripts/validate-terraform.sh # validate ./infra ./scripts/validate-terraform.sh ./infra 00000000-0000-0000-0000-000000000000 ``` ```powershell .\scripts\validate-terraform.ps1 -InfraDir ./infra ``` **Reading the output:** the summary table lists every step as `PASS`, `FAIL`, or `SKIP` (skipped when a prerequisite such as Terraform or the infra directory is missing). Each `FAIL` is expanded in a **FAILURE DETAILS** section with the captured error text. Fix failed steps using the guidance below, then re-run the script. ## Remediation The script only **runs and reports** — fixing failures is manual. Guidance per step: ### Terraform / Azure CLI not installed - Terraform: see https://developer.hashicorp.com/terraform/install - Azure CLI: `mcp_azure_mcp_extension_cli_install(cli-type: "az")` ### Not authenticated / wrong subscription ```bash az login az account set --subscription <subscription-id> ``` ### Format check failed ```bash terraform fmt -recursive ``` ### Init / validate / plan / state failures Read the captured error text in the script output, then consult [Error handling](./errors.md). ### Azure Policy Validation The script does not cover policy checks. See [Policy Validation Guide](../../policy-validation.md) for retrieving and validating Azure policies for your subscription. ### Template Variable Resolution (AZD+Terraform) > ⚠️ **CRITICAL for azd+Terraform projects.** azd substitutes `${VAR}` references in > `main.tfvars.json` via envsubst, but does NOT interpolate Go-style template variables > (`{{ .Env.* }}`). Unresolved Go-style template strings passed to Terraform cause > cascading deployment failures, state conflicts, and timeouts. When the template-variable scan reports `FAIL`: 1. **Fix the syntax** in `main.tfvars.json` — replace `{{ .Env.VAR }}` with `${VAR}`: ```json { "environment_name": "${AZURE_ENV_NAME}", "location": "${AZURE_LOCATION}" } ``` 2. For additional variables, use **`TF_VAR_*` environment variables**: ```bash azd env set TF_VAR_environment_name "$(azd env get-value AZURE_ENV_NAME)" ``` 3. **Verify** that `variables.tf` declares all required variables. 4. **Re-run** the script to confirm `terraform validate` / `plan` and the scan now pass. > Prefer putting static defaults in `variables.tf` `default` values. Using `terraform.tfvars` > (HCL) for static defaults is acceptable if your team prefers it; this restriction is > specifically about avoiding Go-style template expressions in `.tfvars.json` files. ## References - [Error handling](./errors.md) ## Next All checks pass → **azure-deploy**
-
-
README.md 384 B
# Recipes Validation recipes for different infrastructure approaches. | Recipe | When to Use | |--------|-------------| | [AZD](azd/README.md) | Projects using Azure Developer CLI | | [AZCLI](azcli/README.md) | Projects using Azure CLI scripts | | [Bicep](bicep/README.md) | Projects using Bicep templates | | [Terraform](terraform/README.md) | Projects using Terraform |
-
-
scripts
-
scan-aspire-functions-secrets.ps1 3 KB · in bundle
-
scan-aspire-functions-secrets.sh 3.3 KB
#!/usr/bin/env bash # scan-aspire-functions-secrets.sh # Aspire + Azure Functions secret-storage pre-provisioning scan. # # Decides whether the AzureWebJobsSecretStorageType=Files fix is required by # scanning C# source for the Aspire Functions builder call and the setting. # # Logic: # 1. Find *.cs files that call AddAzureFunctionsProject. # 2. For each, check whether the same file already sets AzureWebJobsSecretStorageType. # 3. Files with the call but missing the setting -> fix required. # # Usage: # ./scan-aspire-functions-secrets.sh [directory] # # Examples: # ./scan-aspire-functions-secrets.sh # Scan current directory # ./scan-aspire-functions-secrets.sh ./src # Scan a specific directory # # Output: a single verdict — NOT APPLICABLE, ALREADY CONFIGURED, or FIX REQUIRED # (with the matching file(s) and line(s)). Exit code is 0 for every verdict; # a non-zero exit only indicates a usage/environment error. set -euo pipefail ROOT="${1:-.}" if [ ! -d "$ROOT" ]; then echo "ERROR: '$ROOT' is not a directory." >&2 exit 2 fi CALL="AddAzureFunctionsProject" SETTING="AzureWebJobsSecretStorageType" # file_contains <fixed-string> <file> # Returns 0 if the file contains the literal string, 1 if not. # A grep read error (exit code 2) is fatal: this is a critical pre-provision # scan, so a file we cannot read must not be silently reported as "no match". file_contains() { local pattern="$1" file="$2" rc if grep -Fq -- "$pattern" "$file"; then return 0 fi rc=$? if [ "$rc" -eq 2 ]; then echo "ERROR: failed to read '$file' while scanning for '$pattern'." >&2 exit 3 fi return 1 } # first_line <fixed-string> <file> # Prints the 1-based line number of the first literal match (or nothing). first_line() { grep -Fn -- "$1" "$2" | head -n1 | cut -d: -f1 } # Collect *.cs files that reference AddAzureFunctionsProject into an array. # find ... -print0 + read -d '' keeps paths intact even if they contain # newlines or spaces (genuinely NUL-safe, unlike a newline-joined string). matches=() while IFS= read -r -d '' file; do if file_contains "$CALL" "$file"; then matches+=("$file") fi done < <(find "$ROOT" -type f -name "*.cs" -print0) if [ "${#matches[@]}" -eq 0 ]; then echo "VERDICT: NOT APPLICABLE" echo "No '$CALL' call found in any *.cs file under '$ROOT'." echo "The Functions secret-storage check does not apply — skip it." exit 0 fi # Partition matching files by whether they already configure the setting. needs_fix=() configured=() for file in "${matches[@]}"; do if file_contains "$SETTING" "$file"; then configured+=("$file") else needs_fix+=("$file") fi done if [ "${#needs_fix[@]}" -eq 0 ]; then echo "VERDICT: ALREADY CONFIGURED" echo "Every file that calls '$CALL' already sets '$SETTING':" for file in "${configured[@]}"; do echo " - $file (line $(first_line "$SETTING" "$file"))" done echo "No change required." exit 0 fi echo "VERDICT: FIX REQUIRED" echo "The following file(s) call '$CALL' but do NOT set '$SETTING':" for file in "${needs_fix[@]}"; do echo " - $file (line $(first_line "$CALL" "$file"))" done echo "" echo "Add .WithEnvironment(\"$SETTING\", \"Files\") to the AddAzureFunctionsProject" echo "builder chain in each file above BEFORE running 'azd provision'." exit 0 -
workflow.ps1 5.2 KB · in bundle
-
workflow.sh 5.7 KB
#!/usr/bin/env bash # workflow.sh # Walks the agent through the azure-validate workflow, one step at a time. # # Usage: # ./workflow.sh --workspace-path <path> [--completed-step <step>] # # Options: # --workspace-path <path> Path to the workspace being validated (required). # --completed-step <step> The workflow step the agent just completed. Omit # on the first call to start the workflow. The # script records the value in # .azure/validate-status.json and returns the next # action to take, along with the value to pass as # --completed-step on the next call. # # Exit codes: # 0 - next action emitted (or workflow complete) # 2 - usage / argument error (missing workspace path or invalid step) set -uo pipefail # Valid workflow steps, in order. VALID_STEPS=(None LoadPlan AddValidationSteps RunValidation BuildVerification \ StaticRoleVerification RecordProof ResolveErrors UpdateStatus) # Ensure an option that consumes a value actually has one ($@ = remaining args). need_val() { [ "$#" -ge 2 ] || { echo "ERROR: $1 requires a value." >&2; exit 2; } } WORKSPACE_PATH="" COMPLETED_STEP="" while [ $# -gt 0 ]; do case "$1" in --workspace-path) need_val "$@"; WORKSPACE_PATH="$2"; shift 2 ;; --completed-step) need_val "$@"; COMPLETED_STEP="$2"; shift 2 ;; -h|--help) grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "Unknown argument: $1" >&2 exit 2 ;; esac done if [ -z "$WORKSPACE_PATH" ]; then echo "ERROR: --workspace-path is required." >&2 exit 2 fi if [ ! -d "$WORKSPACE_PATH" ]; then echo "Error: --workspace-path '$WORKSPACE_PATH' does not exist or is not a directory." >&2 exit 2 fi # Resolve the step the agent just completed (case-insensitive). # Omitting --completed-step signals the start of the workflow (None). STEP="None" if [ -n "$COMPLETED_STEP" ]; then STEP="" for valid in "${VALID_STEPS[@]}"; do if [ "$(printf '%s' "$COMPLETED_STEP" | tr '[:upper:]' '[:lower:]')" = \ "$(printf '%s' "$valid" | tr '[:upper:]' '[:lower:]')" ]; then STEP="$valid" break fi done if [ -z "$STEP" ]; then printf 'Error: '\''--completed-step %s'\'' is not a valid step. Valid values: %s\n' \ "$COMPLETED_STEP" "$(printf '%s, ' "${VALID_STEPS[@]}" | sed 's/, $//')" >&2 exit 2 fi fi # Record progress in .azure/validate-status.json (creating it if needed). AZURE_DIR="$WORKSPACE_PATH/.azure" mkdir -p "$AZURE_DIR" VALIDATE_STATUS_PATH="$AZURE_DIR/validate-status.json" printf '{\n "completedStep": "%s"\n}\n' "$STEP" > "$VALIDATE_STATUS_PATH" # Emit the next action based on the step just completed. case "$STEP" in None) # Step 1: Load Plan echo "Action: Read \`.azure/deployment-plan.md\` for recipe and configuration. If missing, run azure-prepare first, then come back to workflow.sh." echo "Next: re-run workflow.sh with --completed-step LoadPlan after completing the action." echo "Reference: \`.azure/deployment-plan.md" ;; LoadPlan) # Step 2: Add Validation Steps echo "Action: Copy the recipe's \`Validation Steps\` into \`.azure/deployment-plan.md\` as children of \`All validation checks pass\`." echo "Next: re-run workflow.sh with --completed-step AddValidationSteps after completing the action." echo "Reference: references/recipes/README.md, \`.azure/deployment-plan.md" ;; AddValidationSteps) # Step 3: Run Validation echo "Action: Execute the recipe-specific validation commands." echo "Next: re-run workflow.sh with --completed-step RunValidation after completing the action." echo "Reference: references/recipes/README.md" ;; RunValidation) # Step 4: Build Verification echo "Action: Build the project and fix any errors before proceeding." echo "Next: re-run workflow.sh with --completed-step BuildVerification after completing the action." echo "Reference: See the recipe for build details." ;; BuildVerification) # Step 5: Static Role Verification echo "Action: Review the Bicep/Terraform for correct RBAC role assignments in code." echo "Next: re-run workflow.sh with --completed-step StaticRoleVerification after completing the action." echo "Reference: references/role-verification.md" ;; StaticRoleVerification) # Step 6: Record Proof echo "Action: Populate **Section 7: Validation Proof** in the plan with the commands run and their results." echo "Next: re-run workflow.sh with --completed-step RecordProof after completing the action." echo "Reference: \`.azure/deployment-plan.md" ;; RecordProof) # Step 7: Resolve Errors echo "Action: Fix any validation failures before proceeding." echo "Next: re-run workflow.sh with --completed-step ResolveErrors after completing the action." echo "Reference: See the recipe's errors.md." ;; ResolveErrors) # Step 8: Update Status echo "Action: Only after ALL checks pass, set the plan status to \`Validated\`." echo "Next: re-run workflow.sh with --completed-step UpdateStatus after completing the action." echo "Reference: \`.azure/deployment-plan.md" ;; UpdateStatus) # Step 9: Deploy (workflow complete) echo "Action: The azure-validate workflow is complete. If the user explicitly requested deployment, invoke azure-deploy. Otherwise STOP and report the validation results." ;; esac exit 0
-
-
aspire-functions-secrets.md 3.7 KB
# Aspire + Azure Functions: Secret Storage Validation > ⚠️ **Pre-provisioning check** — Run this BEFORE `azd provision`. ## When This Applies This check is required when **all** of these are true: | Condition | How to detect | |-----------|--------------| | .NET Aspire project | `*.AppHost.csproj` exists or `Aspire.Hosting` package reference | | Azure Functions component | `AddAzureFunctionsProject` call in `AppHost.cs` or `Program.cs` | | Identity-based storage | `WithHostStorage` call (Aspire default) | ## Detection Run the scan script — it finds `*.cs` files that call `AddAzureFunctionsProject`, checks whether each one already sets `AzureWebJobsSecretStorageType`, and prints a single verdict so you don't have to parse raw grep output. **Bash** — [`scripts/scan-aspire-functions-secrets.sh`](scripts/scan-aspire-functions-secrets.sh): ```bash ./scripts/scan-aspire-functions-secrets.sh [directory] # directory defaults to . ``` **PowerShell** — [`scripts/scan-aspire-functions-secrets.ps1`](scripts/scan-aspire-functions-secrets.ps1): ```powershell .\scripts\scan-aspire-functions-secrets.ps1 [-Path <directory>] # -Path defaults to . ``` The script prints one of three verdicts: | Verdict | Meaning | Action | |---------|---------|--------| | `NOT APPLICABLE` | No `AddAzureFunctionsProject` call found | Skip this check | | `ALREADY CONFIGURED` | Every matching file already sets `AzureWebJobsSecretStorageType` | No change required | | `FIX REQUIRED` | Matching file(s) call `AddAzureFunctionsProject` but omit the setting (file/line listed) | Apply the **Fix** below to each listed file | ## Fix Add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the Azure Functions project builder chain in the AppHost source file that contains the `AddAzureFunctionsProject` call (often `Program.cs` in the `*.AppHost` project). ### Before ```csharp var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions") .WithHostStorage(storage) .WithReference(queues); ``` ### After ```csharp var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions") .WithHostStorage(storage) .WithEnvironment("AzureWebJobsSecretStorageType", "Files") .WithReference(queues); ``` > 💡 **Tip:** Place `.WithEnvironment(...)` immediately after `.WithHostStorage(...)` for clarity. ## Why This Is Required Azure Functions needs storage for managing host secrets/keys (function keys, host keys, master key). By default, it stores them as blobs in the `AzureWebJobsStorage` account. When Aspire configures identity-based storage access (via `WithHostStorage`), it sets URI-based environment variables like `AzureWebJobsStorage__blobServiceUri` instead of a connection string. The Functions runtime's secret manager does **not** support these identity-based URIs — it requires either a connection string or SAS token. Setting `AzureWebJobsSecretStorageType=Files` switches the Functions host to file-system-based key storage, bypassing the blob storage dependency for secrets. ## Error Without This Setting ``` System.InvalidOperationException: Secret initialization from Blob storage failed due to missing both an Azure Storage connection string and a SAS connection URI. For Blob Storage, please provide at least one of these. ``` ## When This Check Does NOT Apply | Scenario | Why | |----------|-----| | Aspire project without Azure Functions | No Functions secret manager involved | | Standalone Azure Functions (not Aspire) | Uses connection string by default | | Functions with explicit connection string | `AzureWebJobsStorage` is a full connection string, not identity-based | | `AzureWebJobsSecretStorageType` already set | Configuration is already present | -
global-rules.md 1.2 KB
# Global Rules > **MANDATORY** — These rules apply to ALL skills. Violations are unacceptable. ## Rule 1: Destructive Actions Require User Confirmation ⛔ **ALWAYS use `ask_user`** before ANY destructive action. ### What is Destructive? | Category | Examples | |----------|----------| | **Delete** | `az group delete`, `azd down`, `rm -rf`, delete resource | | **Overwrite** | Replace existing files, overwrite config, reset settings | | **Irreversible** | Purge Key Vault, delete storage account, drop database | | **Cost Impact** | Provision expensive resources, scale up significantly | | **Security** | Expose secrets, change access policies, modify RBAC | ### How to Confirm ``` ask_user( question: "This will permanently delete resource group 'rg-myapp'. Continue?", choices: ["Yes, delete it", "No, cancel"] ) ``` ### No Exceptions - Do NOT assume user wants to delete/overwrite - Do NOT proceed based on "the user asked to deploy" (deploy ≠ delete old) - Do NOT batch destructive actions without individual confirmation --- ## Rule 2: Never Assume Subscription or Location ⛔ **ALWAYS use `ask_user`** to confirm: - Azure subscription (show actual name and ID) - Azure region/location -
policy-validation.md 1.5 KB
# Azure Policy Validation ## How to Validate Policies ### 1. Get Subscription ID Retrieve your current Azure subscription ID: ```bash az account show --query id -o tsv ``` ### 2. Validate Policies Call the Azure MCP Policy tool to retrieve policies for your subscription: ``` mcp_azure_mcp_policy(command: "list", parameters: { subscription_id: "<subscription-id>" }) ``` Replace `<subscription-id>` with the actual subscription ID obtained from step 1. ## Review Policy Compliance When validating Azure policies for your subscription: - **Check for policy violations** — Identify any resources or configurations that don't comply with assigned policies - **Verify organizational compliance** — Ensure the planned deployment meets all organizational policy requirements - **Address policy conflicts** — Resolve any policy issues before proceeding to deployment ## Common Policy Issues | Issue | Resolution | |-------|------------| | Non-compliant resource SKUs | Update resource SKUs to comply with allowed values | | Missing required tags | Add required tags to resources in your infrastructure code | | Disallowed resource types | Replace with allowed alternatives or request policy exception | | Location restrictions | Deploy to allowed regions only | | Network security violations | Update NSG rules, firewall settings, or virtual network configurations | ## Next Steps Only proceed to deployment after all policy violations are resolved and compliance is confirmed. -
region-availability.md 3 KB
# Azure Region Availability Reference > **AUTHORITATIVE SOURCE** — Consult this file BEFORE recommending any region. > > Official reference: https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/table ## How to Use 1. Check if your architecture includes any **limited availability** services below 2. If yes → refer to the table or use the MCP tool to list supported regions with sufficient quota for that service, and only offer regions that support ALL services 3. If all services are "available everywhere" → offer common regions ## MCP Tools Used | Tool | Purpose | |------|---------| | `mcp_azure_mcp_quota` | Check Azure region availability and quota by setting `command` to `quota_usage_check` or `quota_region_availability_list` | --- ## Services with LIMITED Region Availability ### Azure Static Web Apps (SWA) ⚠️ **NOT available in many common regions** | ✅ Available | ❌ NOT Available (will FAIL) | |-------------|------------------------------| | `westus2` | `eastus` | | `centralus` | `northeurope` | | `eastus2` | `southeastasia` | | `westeurope` | `uksouth` | | `eastasia` | `canadacentral` | | | `australiaeast` | | | `westus3` | --- ### Azure OpenAI ⚠️ **Very limited — varies by model** | Region | GPT-4o | GPT-4 | GPT-3.5 | Embeddings | |--------|:------:|:-----:|:-------:|:----------:| | `eastus` | ✅ | ✅ | ✅ | ✅ | | `eastus2` | ✅ | ✅ | ✅ | ✅ | | `westus` | ⚠️ | ⚠️ | ✅ | ✅ | | `westus3` | ✅ | ⚠️ | ✅ | ✅ | | `southcentralus` | ✅ | ✅ | ✅ | ✅ | | `swedencentral` | ✅ | ✅ | ✅ | ✅ | | `westeurope` | ⚠️ | ✅ | ✅ | ✅ | > Check https://learn.microsoft.com/azure/ai-services/openai/concepts/models for current model availability. --- ### Azure Kubernetes Service (AKS) It has limited quota in some regions, to get available regions with enough quota, use `mcp_azure_mcp_quota` tool. --- ### Azure Database for PostgreSQL It has limited quota in some regions, to get available regions with enough quota, use `mcp_azure_mcp_quota` tool. --- ## Services Available in Most Regions These services are available in all major Azure regions — no special consideration needed: - **Container Apps** - **Azure Functions** - **App Service** - **Azure SQL Database** - **Cosmos DB** - **Key Vault** - **Storage Account** - **Service Bus** - **Event Grid** - **Application Insights / Log Analytics** --- ## Common Architecture Patterns | Pattern | Recommended Regions | |---------|---------------------| | SWA only | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` | | SWA + backend services | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` | | Container Apps (no SWA) | `eastus`, `eastus2`, `westus2`, `centralus`, `westeurope` | | With Azure OpenAI (GPT-4o/4/3.5 + embeddings) | `eastus`, `eastus2`, `swedencentral` | | SWA + Azure OpenAI (GPT-4o/4/3.5 + embeddings) | `eastus2` (only region with full SWA + model overlap) | --- **Last updated:** 2026-03-02 -
role-verification.md 3.9 KB
# Role Assignment Verification Verify that all RBAC role assignments in the generated infrastructure are correct and sufficient before deployment. Incorrect or missing roles are a common cause of runtime failures. ## When to Verify After build verification (step 4) and **before** recording proof (step 6). Role issues surface as cryptic auth errors during deployment — catching them here saves debugging time. ## Verification Checklist Review every resource-to-identity relationship in the generated Bicep/Terraform: | Check | How | |-------|-----| | **Every service identity has roles** | Each app with a managed identity must have at least one role assignment | | **Roles match data operations** | Use service-specific **data-plane** roles for data access (see mapping table below); use generic Reader/Contributor/Owner only for management-plane operations | | **Scope is least privilege** | Roles scoped to specific resources, not resource groups or subscriptions | | **No missing roles** | Cross-check app code operations against assigned roles (see table below) | | **Local dev identity has roles** | If testing locally, the user's identity needs equivalent roles via `az login` | ## Common Service-to-Role Mapping | Service Operation | Required Role | Common Mistake | |-------------------|---------------|----------------| | Read blobs | Storage Blob Data Reader | Using generic Reader (no data access) | | Read + write blobs | Storage Blob Data Contributor | Missing write permission | | Generate SAS via user delegation | Storage Blob Delegator + Data Reader/Contributor | Forgetting Delegator role | | Read Key Vault secrets | Key Vault Secrets User | Using Key Vault Reader (no secret access) | | Read + write Cosmos DB | Cosmos DB Built-in Data Contributor | Using generic Contributor | | Send Service Bus messages | Azure Service Bus Data Sender | Using generic Contributor | | Read queues | Storage Queue Data Reader | Using Blob role for queues | ## How to Verify (Static Code Review) Review the generated Bicep/Terraform files directly — do **not** query live Azure state here. For each role assignment resource in your infrastructure code: 1. Identify the **principal** (which managed identity) 2. Identify the **role** (which role definition) 3. Identify the **scope** (which target resource) 4. Cross-check against the app code to confirm the role grants the required data-plane access > 💡 **Tip:** Search your Bicep for `Microsoft.Authorization/roleAssignments` or your Terraform for `azurerm_role_assignment` to find all role assignments. > ⚠️ **Live role verification** (querying Azure for actually provisioned roles) is handled by **azure-deploy** step 8 as a post-deployment check. This step is a static code review only. ## Decision Tree ``` For each app identity in the generated infrastructure: ├── Has role assignments? │ ├── No → Add required role assignments to Bicep/Terraform │ └── Yes → Check each role: │ ├── Role matches code operations? → ✅ OK │ ├── Role too broad? → Narrow to least privilege │ └── Role insufficient? → Upgrade or add missing role │ For local testing: ├── User identity has equivalent roles? │ ├── No → Grant roles via CLI or inform user │ └── Yes → ✅ Ready for functional verification ``` > ⚠️ **Warning:** Generic roles like `Contributor` or `Reader` do **not** include data-plane access. For example, `Contributor` on a Storage Account cannot read blobs — you need `Storage Blob Data Contributor`. This is the most common RBAC mistake. ## Record in Plan After role verification, update `.azure/deployment-plan.md`: ```markdown ## Role Assignment Verification - Status: Verified / Issues Found - Identities checked: <list of app identities> - Roles confirmed: <list of role assignments> - Issues: <any missing or incorrect roles fixed> ```
-
-
SKILL.md 3.5 KB
--- name: azure-validate description: "Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks, verify configuration, check if ready to deploy, validate azure.yaml, validate Bicep, test before deploying, troubleshoot deployment errors, validate Azure Functions, validate function app, validate serverless deployment, verify RBAC roles, check role assignments, review managed identity permissions, what-if analysis, validate Container Apps deployment." license: MIT metadata: author: Microsoft version: "1.2.2" --- # Azure Validate > **AUTHORITATIVE GUIDANCE** — Follow these instructions exactly unless they contradict security policies given to you. > **⛔ STOP — PREREQUISITE CHECK REQUIRED** > > Before proceeding, verify this prerequisite is met: > > **azure-prepare** was invoked and completed → `.azure/deployment-plan.md` exists with status `Approved` or later > > If the plan is missing, **STOP IMMEDIATELY** and invoke **azure-prepare** first. > > The complete workflow ensures success: > > `azure-prepare` → `azure-validate` → `azure-deploy` ## Triggers - Check if app is ready to deploy - Validate azure.yaml or Bicep - Run preflight checks - Troubleshoot deployment errors ## Rules 1. Run after azure-prepare, before azure-deploy 2. All checks must pass—do not deploy with failures 3. ⛔ **Destructive actions require `ask_user`** — [global-rules](references/global-rules.md) ## Steps Run the workflow script and follow its instructions. It walks you through each validation step one at a time, recording progress in `.azure/validate-status.json`. Use [references/scripts/workflow.ps1](references/scripts/workflow.ps1) on Windows or [references/scripts/workflow.sh](references/scripts/workflow.sh) on macOS/Linux. Start by calling the script **without** the completed-step argument: ```bash pwsh references/scripts/workflow.ps1 -WorkspacePath <workspace-path> # macOS/Linux: bash references/scripts/workflow.sh --workspace-path <workspace-path> ``` Each run prints the next action and the value to pass next. Perform the action, then re-run with that value (`-CompletedStep <value>` for pwsh, `--completed-step <value>` for bash). Repeat until it reports the azure-validate workflow is complete. The steps reference recipe details in [references/recipes/README.md](references/recipes/README.md) and role checks in [references/role-verification.md](references/role-verification.md). > **⛔ VALIDATION AUTHORITY** > > This skill is the officially verified way to set plan status to `Validated`. You MUST follow the script's instructions to completion before setting status to `Validated`. > Do NOT set status to `Validated` without doing so. --- > **⚠️ NEXT STEP — DEPENDS ON USER INTENT** > > After ALL validations pass, check whether the user asked to deploy: > - **If the user explicitly requested deployment**, you **MUST** invoke **azure-deploy** to execute it. Do NOT run `azd up`, `azd deploy`, or any deployment commands directly — let azure-deploy handle execution. > - **If the user only asked to validate or prepare** (not deploy), STOP after recording proof and setting status to `Validated`. Report the validation results and do NOT invoke azure-deploy. > > If any validation failed, fix the issues and re-run azure-validate before proceeding.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.