diff --git a/.github/plugins/azure-skills/.claude-plugin/plugin.json b/.github/plugins/azure-skills/.claude-plugin/plugin.json index 62993cc0..78398949 100644 --- a/.github/plugins/azure-skills/.claude-plugin/plugin.json +++ b/.github/plugins/azure-skills/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "azure", "description": "Microsoft Azure MCP and Skills integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Claude Code.", - "version": "1.1.98", + "version": "1.1.104", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/azure-skills/.cursor-plugin/plugin.json b/.github/plugins/azure-skills/.cursor-plugin/plugin.json index 492f43c0..d47a1e9c 100644 --- a/.github/plugins/azure-skills/.cursor-plugin/plugin.json +++ b/.github/plugins/azure-skills/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "azure", "description": "Microsoft Azure MCP and Skills integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Cursor.", - "version": "1.1.98", + "version": "1.1.104", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/azure-skills/.plugin/plugin.json b/.github/plugins/azure-skills/.plugin/plugin.json index 1678fe96..eb0ef6e2 100644 --- a/.github/plugins/azure-skills/.plugin/plugin.json +++ b/.github/plugins/azure-skills/.plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "azure", "description": "Microsoft Azure MCP and Skills integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from your development environment.", - "version": "1.1.98", + "version": "1.1.104", "author": { "name": "Microsoft", "url": "https://www.microsoft.com" diff --git a/.github/plugins/azure-skills/CHANGELOG.md b/.github/plugins/azure-skills/CHANGELOG.md index da33bc5e..45efb0c5 100644 --- a/.github/plugins/azure-skills/CHANGELOG.md +++ b/.github/plugins/azure-skills/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 1.1.104 + +- feat: set azd user agent env ([#2882](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2882)) + +## 1.1.103 + +- fix: split prompt-agent tool references into tools/prompt-agent/ ([#2857](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2857)) + +## 1.1.102 + +- feat: replace azure-validate AZCLI/Bicep validation steps with a shared script ([#2856](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2856)) + +## 1.1.101 + +- feat: replace azure-validate Aspire Functions secret-storage scan with a script ([#2854](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2854)) + +## 1.1.100 + +- feat: replace azure-validate Aspire ACA env-var setup with a script ([#2855](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2855)) + +## 1.1.99 + +- feat: script-ify Terraform validation preflight sequence (azure-validate) ([#2862](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2862)) + ## 1.1.98 - fix: improve canvas-entry gate logic and add plugin-contributed extension detection ([#2866](https://github.com/microsoft/GitHub-Copilot-for-Azure/pull/2866)) diff --git a/.github/plugins/azure-skills/skills/azure-validate/SKILL.md b/.github/plugins/azure-skills/skills/azure-validate/SKILL.md index e852edd0..0d9525b0 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/SKILL.md +++ b/.github/plugins/azure-skills/skills/azure-validate/SKILL.md @@ -4,7 +4,7 @@ description: "Pre-deployment validation for Azure readiness. Run deep checks on license: MIT metadata: author: Microsoft - version: "1.1.2" + version: "1.1.6" --- # Azure Validate diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/aspire-functions-secrets.md b/.github/plugins/azure-skills/skills/azure-validate/references/aspire-functions-secrets.md index c0d47a39..529bafd2 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/references/aspire-functions-secrets.md +++ b/.github/plugins/azure-skills/skills/azure-validate/references/aspire-functions-secrets.md @@ -14,33 +14,25 @@ This check is required when **all** of these are true: ## Detection -Search for `AddAzureFunctionsProject` in the AppHost source file(s): +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 -grep -rn "AddAzureFunctionsProject" . --include="*.cs" +./scripts/scan-aspire-functions-secrets.sh [directory] # directory defaults to . ``` -**PowerShell:** +**PowerShell** — [`scripts/scan-aspire-functions-secrets.ps1`](scripts/scan-aspire-functions-secrets.ps1): ```powershell -Get-ChildItem -Recurse -Filter "*.cs" | Select-String "AddAzureFunctionsProject" -List +.\scripts\scan-aspire-functions-secrets.ps1 [-Path ] # -Path defaults to . ``` -If found, check whether `AzureWebJobsSecretStorageType` is already configured in those same file(s): +The script prints one of three verdicts: -```bash -# Check only the AppHost file(s) that contain AddAzureFunctionsProject -find . -name "*.cs" -path "*AppHost*" -print0 | xargs -0 grep -l "AddAzureFunctionsProject" 2>/dev/null | xargs grep -l "AzureWebJobsSecretStorageType" -``` - -**PowerShell:** -```powershell -Get-ChildItem -Recurse -Filter "*.cs" | - Where-Object { $_.FullName -match "AppHost" } | - Select-String "AddAzureFunctionsProject" -List | - ForEach-Object { Select-String "AzureWebJobsSecretStorageType" -Path $_.Path } -``` - -**If `AddAzureFunctionsProject` is present but `AzureWebJobsSecretStorageType` is NOT configured in the same file → fix is required.** +| 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 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azcli/README.md b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azcli/README.md index b2553f38..e6f198f5 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azcli/README.md +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azcli/README.md @@ -9,84 +9,54 @@ Validation steps for Azure CLI deployments. ## Validation Steps -- [ ] 1. Azure CLI Installation -- [ ] 2. Authentication -- [ ] 3. Bicep Compilation -- [ ] 4. Template Validation -- [ ] 5. What-If Preview -- [ ] 6. Docker Build (if containerized) -- [ ] 7. Azure Policy Validation +- [ ] 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. Azure CLI Installation +### 1. Core Validation Script -Verify Azure CLI is installed: +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 -az version -``` - -**If not installed:** -``` -mcp_azure_mcp_extension_cli_install(cli-type: "az") -``` - -### 2. Authentication +- Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh) +- PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1) -```bash -az account show -``` +**Subscription scope:** -**If not logged in:** ```bash -az login +../scripts/validate-deployment.sh --scope sub --location ``` - -**Set subscription:** -```bash -az account set --subscription +```powershell +../scripts/validate-deployment.ps1 -Scope sub -Location ``` -### 3. Bicep Compilation +**Resource group scope:** ```bash -az bicep build --file ./infra/main.bicep +../scripts/validate-deployment.sh --scope group --resource-group ``` - -### 4. Template Validation - -```bash -# Subscription scope -az deployment sub validate \ - --location \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json - -# Resource group scope -az deployment group validate \ - --resource-group \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json +```powershell +../scripts/validate-deployment.ps1 -Scope group -ResourceGroup ``` -### 5. What-If Preview +Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json` +(skipped if absent). Pass `--subscription ` to target a specific subscription. -```bash -# Subscription scope -az deployment sub what-if \ - --location \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json - -# Resource group scope -az deployment group what-if \ - --resource-group \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json -``` +**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 `. + - **Azure CLI installed** fails → install via `mcp_azure_mcp_extension_cli_install(cli-type: "az")`. + - Otherwise see [Error handling](./errors.md). -### 6. Docker Build (if containerized) +### 2. Docker Build (if containerized) **Before building**, validate the Docker build context: @@ -105,7 +75,7 @@ npm install --package-lock-only docker build -t :test ./src/ ``` -### 7. Azure Policy Validation +### 3. Azure Policy Validation See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/aspire.md b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/aspire.md index badf392f..d470410b 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/aspire.md +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/aspire.md @@ -34,33 +34,14 @@ See [Aspire Functions Secrets Reference](../../aspire-functions-secrets.md) for 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. -**Check if environment variables are set:** +**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 -azd env get-values | grep -E "AZURE_CONTAINER_REGISTRY_ENDPOINT|AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID|MANAGED_IDENTITY_CLIENT_ID" -``` - -**If any are missing, set them now BEFORE running `azd deploy`:** - -```bash -# Get resource group name -RG_NAME=$(azd env get-values | grep AZURE_RESOURCE_GROUP | cut -d'=' -f2 | tr -d '"') - -# Set required variables -azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT $(az acr list --resource-group "$RG_NAME" --query "[0].loginServer" -o tsv) -azd env set AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID $(az identity list --resource-group "$RG_NAME" --query "[0].id" -o tsv) -azd env set MANAGED_IDENTITY_CLIENT_ID $(az identity list --resource-group "$RG_NAME" --query "[0].clientId" -o tsv) +./scripts/set-aspire-aca-env.sh # or: -e ``` **PowerShell:** ```powershell -# Get resource group name -$rgName = (azd env get-values | Select-String 'AZURE_RESOURCE_GROUP').Line.Split('=')[1].Trim('"') - -# Set required variables -azd env set AZURE_CONTAINER_REGISTRY_ENDPOINT (az acr list --resource-group $rgName --query "[0].loginServer" -o tsv) -azd env set AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID (az identity list --resource-group $rgName --query "[0].id" -o tsv) -azd env set MANAGED_IDENTITY_CLIENT_ID (az identity list --resource-group $rgName --query "[0].clientId" -o tsv) +./scripts/set-aspire-aca-env.ps1 # or: -Environment ``` - -**Why this is needed:** Aspire's "limited mode" generates infrastructure in-memory. While `azd provision` creates all necessary Azure resources (Container Registry, Managed Identity, Container Apps Environment), it doesn't populate the environment variables that reference those resources. The `azd deploy` phase requires these variables to authenticate with the container registry and configure managed identity bindings. diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.ps1 b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.ps1 new file mode 100644 index 00000000..e40bf9a1 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.ps1 @@ -0,0 +1,102 @@ +<# +.SYNOPSIS + Set the Container Apps environment variables that Aspire "limited mode" leaves unpopulated. + +.DESCRIPTION + 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`. + + 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 + +.PARAMETER Environment + Optional azd environment name (forwarded to `azd env` calls). + Defaults to the current/default azd environment. + +.EXAMPLE + ./set-aspire-aca-env.ps1 + +.EXAMPLE + ./set-aspire-aca-env.ps1 -Environment my-azd-env +#> + +param( + [string]$Environment +) + +# Shared `-e ` argument list for azd calls (empty when no env name given). +$azdEnvArgs = @() +if ($Environment) { + $azdEnvArgs = @('-e', $Environment) +} + +# Capture azd environment values and verify the call succeeded. PowerShell does not treat a +# native command's non-zero exit code as a terminating error, so check $LASTEXITCODE explicitly +# rather than relying on $ErrorActionPreference. +$azdOutput = azd env get-values @azdEnvArgs +if ($LASTEXITCODE -ne 0) { + Write-Error "'azd env get-values' failed with exit code $LASTEXITCODE. Run 'azd provision' before this script so the azd environment is available." + exit 1 +} + +# Load azd environment values into a hashtable. +$azdValues = @{} +foreach ($line in $azdOutput) { + if (-not $line) { continue } + $name, $value = $line.Split('=', 2) + $azdValues[$name] = $value.Trim('"') +} + +$rgName = $azdValues['AZURE_RESOURCE_GROUP'] +if (-not $rgName) { + Write-Error "AZURE_RESOURCE_GROUP is not set in the azd environment. Run 'azd provision' before this script so the resource group is available." + exit 1 +} + +function Set-IfMissing { + param( + [string]$VarName, + [string]$Description, + [scriptblock]$ValueCommand + ) + + $existing = $azdValues[$VarName] + if ($existing) { + Write-Host "${VarName}: already present ($existing)" + return + } + + $value = (& $ValueCommand) + if (-not $value) { + Write-Error "Could not resolve $VarName ($Description) in resource group '$rgName'. Confirm 'azd provision' completed and the resource exists." + exit 1 + } + + azd env set @azdEnvArgs $VarName $value + Write-Host "${VarName}: set to $value" +} + +Write-Host "Resource group: $rgName" + +Set-IfMissing ` + -VarName 'AZURE_CONTAINER_REGISTRY_ENDPOINT' ` + -Description 'container registry login server' ` + -ValueCommand { az acr list --resource-group $rgName --query "[0].loginServer" -o tsv } + +Set-IfMissing ` + -VarName 'AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID' ` + -Description 'managed identity resource id' ` + -ValueCommand { az identity list --resource-group $rgName --query "[0].id" -o tsv } + +Set-IfMissing ` + -VarName 'MANAGED_IDENTITY_CLIENT_ID' ` + -Description 'managed identity client id' ` + -ValueCommand { az identity list --resource-group $rgName --query "[0].clientId" -o tsv } + +Write-Host "Aspire Container Apps environment variables are ready for 'azd deploy'." diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.sh b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.sh new file mode 100644 index 00000000..a0c0b669 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/azd/scripts/set-aspire-aca-env.sh @@ -0,0 +1,106 @@ +#!/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 ] +# +# -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 ]" >&2 + exit 1 + ;; + esac +done + +# Build the shared `-e ` 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 — 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 +# 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'." diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/bicep/README.md b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/bicep/README.md index 3789938e..7d28edbb 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/bicep/README.md +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/bicep/README.md @@ -10,64 +10,52 @@ Validation steps for standalone Bicep deployments. ## Validation Steps -- [ ] 1. Bicep Compilation -- [ ] 2. Template Validation -- [ ] 3. What-If Preview -- [ ] 4. Authentication -- [ ] 5. Linting (optional) -- [ ] 6. Azure Policy Validation +- [ ] 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. Bicep Compilation +### 1. Core Validation Script -```bash -az bicep build --file ./infra/main.bicep -``` +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). -**Pass:** No output (compiles cleanly) -**Fail:** Shows line numbers and errors +- Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh) +- PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1) -### 2. Template Validation +**Subscription scope:** ```bash -# Subscription scope -az deployment sub validate \ - --location \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json - -# Resource group scope -az deployment group validate \ - --resource-group \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json +../scripts/validate-deployment.sh --scope sub --location +``` +```powershell +../scripts/validate-deployment.ps1 -Scope sub -Location ``` -### 3. What-If Preview +**Resource group scope:** ```bash -az deployment sub what-if \ - --location \ - --template-file ./infra/main.bicep \ - --parameters ./infra/main.parameters.json -``` - -**Expected output:** +../scripts/validate-deployment.sh --scope group --resource-group ``` -Resource and property changes are indicated with these symbols: - + Create - ~ Modify - - Delete +```powershell +../scripts/validate-deployment.ps1 -Scope group -ResourceGroup ``` -### 4. Authentication +Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json` +(skipped if absent). Pass `--subscription ` to target a specific subscription. -```bash -az account show -``` +**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). -### 5. Linting (optional) +### 2. Linting (optional) Use Bicep linter rules: @@ -75,7 +63,7 @@ Use Bicep linter rules: az bicep lint --file ./infra/main.bicep ``` -### 6. Azure Policy Validation +### 3. Azure Policy Validation See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. @@ -83,10 +71,7 @@ See [Policy Validation Guide](../../policy-validation.md) for instructions on re | Check | Command | Pass | |-------|---------|------| -| Bicep compiles | `az bicep build` | ☐ | -| Template valid | `az deployment validate` | ☐ | -| What-if passes | `az deployment what-if` | ☐ | -| Auth valid | `az account show` | ☐ | +| Core validation (CLI, auth, build, validate, what-if) | `validate-deployment` script | ☐ | | Policies validated | MCP Policy tool | ☐ | ## References diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.ps1 b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.ps1 new file mode 100644 index 00000000..f91cbe7c --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.ps1 @@ -0,0 +1,155 @@ +<# +.SYNOPSIS + 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. +.DESCRIPTION + Executes, 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 (Create/Modify/Delete summary) + Emits per-step PASS/FAIL lines and an OVERALL result. + Exit codes: 0 = every step passed; 1 = a validation step failed; + 2 = usage / argument error. +.PARAMETER Scope + Deployment scope: 'sub' or 'group' (required). +.PARAMETER Location + Location (required when -Scope sub). +.PARAMETER ResourceGroup + Resource group name (required when -Scope group). +.PARAMETER Template + Bicep template path. Default: ./infra/main.bicep +.PARAMETER Parameters + Parameters file path. Default: ./infra/main.parameters.json + (skipped automatically if the file does not exist). +.PARAMETER Subscription + Subscription to target (optional). +.EXAMPLE + .\validate-deployment.ps1 -Scope sub -Location eastus +.EXAMPLE + .\validate-deployment.ps1 -Scope group -ResourceGroup my-rg ` + -Template ./infra/main.bicep -Parameters ./infra/main.parameters.json +#> +param( + [ValidateSet("sub", "group")][string]$Scope, + [string]$Location, + [string]$ResourceGroup, + [string]$Template = "./infra/main.bicep", + [string]$Parameters = "./infra/main.parameters.json", + [string]$Subscription +) + +# Validate arguments +if (-not $Scope) { + Write-Error "-Scope is required and must be 'sub' or 'group'." + exit 2 +} +if ($Scope -eq "sub" -and -not $Location) { + Write-Error "-Location is required when -Scope is 'sub'." + exit 2 +} +if ($Scope -eq "group" -and -not $ResourceGroup) { + Write-Error "-ResourceGroup is required when -Scope is 'group'." + exit 2 +} + +# Build shared argument arrays +$subArgs = @() +if ($Subscription) { $subArgs = @("--subscription", $Subscription) } + +$paramArgs = @() +if (Test-Path $Parameters) { + $paramArgs = @("--parameters", $Parameters) +} else { + Write-Host "NOTE: parameters file '$Parameters' not found; validating without --parameters." +} + +if ($Scope -eq "sub") { + $scopeTargetArgs = @("--location", $Location) + $scopeDesc = "subscription (location: $Location)" +} else { + $scopeTargetArgs = @("--resource-group", $ResourceGroup) + $scopeDesc = "resource group '$ResourceGroup'" +} + +# Track overall result (0 = all passed, 1 = at least one failure). +$overall = 0 + +Write-Host "=== Azure deployment validation ===" +Write-Host "Template: $Template" +Write-Host "Scope: $scopeDesc" +Write-Host "" + +# Step 1: Azure CLI installed +Write-Host "--- Step 1: Azure CLI installed (az version) ---" +az version *> $null +if ($LASTEXITCODE -eq 0) { + Write-Host "PASS: Azure CLI is installed." +} else { + Write-Host "FAIL: Azure CLI not found. Install it, then re-run." + Write-Host "" + Write-Host "OVERALL: FAIL" + exit 1 +} +Write-Host "" + +# Step 2: Authenticated +Write-Host "--- Step 2: Authenticated (az account show) ---" +$accountName = az account show @subArgs --query name -o tsv 2>$null +if ($LASTEXITCODE -eq 0) { + Write-Host "PASS: Authenticated (subscription: $accountName)." +} else { + Write-Host "FAIL: Not logged in. Run 'az login' (and 'az account set --subscription ')." + $overall = 1 +} +Write-Host "" + +# Step 3: Bicep compilation +Write-Host "--- Step 3: Bicep compilation (az bicep build) ---" +$buildOutput = az bicep build --file $Template 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Host "PASS: Template compiles cleanly." +} else { + Write-Host "FAIL: Bicep compilation errors:" + Write-Host ($buildOutput | Out-String) + $overall = 1 +} +Write-Host "" + +# Step 4: Template validation +Write-Host "--- Step 4: Template validation (az deployment $Scope validate) ---" +$validateOutput = az deployment $Scope validate @scopeTargetArgs --template-file $Template @paramArgs @subArgs 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Host "PASS: Template validated against the target scope." +} else { + Write-Host "FAIL: Template validation errors:" + Write-Host ($validateOutput | Out-String) + $overall = 1 +} +Write-Host "" + +# Step 5: What-if preview +Write-Host "--- Step 5: What-if preview (az deployment $Scope what-if) ---" +$whatifOutput = az deployment $Scope what-if @scopeTargetArgs --template-file $Template @paramArgs @subArgs 2>&1 +if ($LASTEXITCODE -eq 0) { + $lines = $whatifOutput | Out-String -Stream + $createCount = ($lines | Where-Object { $_ -match '^\s*\+ ' }).Count + $modifyCount = ($lines | Where-Object { $_ -match '^\s*~ ' }).Count + $deleteCount = ($lines | Where-Object { $_ -match '^\s*- ' }).Count + Write-Host "PASS: What-if completed. Changes -> Create: $createCount, Modify: $modifyCount, Delete: $deleteCount" +} else { + Write-Host "FAIL: What-if errors:" + Write-Host ($whatifOutput | Out-String) + $overall = 1 +} +Write-Host "" + +# Overall result (drives the exit code) +if ($overall -eq 0) { + Write-Host "OVERALL: PASS" +} else { + Write-Host "OVERALL: FAIL" +} +exit $overall diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.sh b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.sh new file mode 100755 index 00000000..595406c9 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/scripts/validate-deployment.sh @@ -0,0 +1,184 @@ +#!/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 [options] +# ./validate-deployment.sh --scope group --resource-group [options] +# +# Options: +# --scope Deployment scope (required) +# --location Location (required when --scope sub) +# --resource-group Resource group (required when --scope group) +# --template Bicep template (default: ./infra/main.bicep) +# --parameters Parameters file (default: ./infra/main.parameters.json; +# skipped automatically if the file does not exist) +# --subscription 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 ')." + 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 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/README.md b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/README.md index 392af5cc..b2321974 100644 --- a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/README.md +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/README.md @@ -7,135 +7,105 @@ Validation steps for Terraform deployments. - `./infra/main.tf` exists - State backend accessible -## Validation Steps +## Run the preflight script -- [ ] 1. Terraform Installation -- [ ] 2. Azure CLI Installation -- [ ] 3. Authentication -- [ ] 4. Initialize -- [ ] 5. Format Check -- [ ] 6. Validate Syntax -- [ ] 7. Plan Preview -- [ ] 8. State Backend -- [ ] 9. Azure Policy Validation -- [ ] 10. Template Variable Resolution Check (AZD+Terraform) +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. -## Validation Details +| Script | Purpose | +|--------|---------| +| [`scripts/validate-terraform.sh`](scripts/validate-terraform.sh) | Bash preflight runner | +| [`scripts/validate-terraform.ps1`](scripts/validate-terraform.ps1) | PowerShell preflight runner | -### 1. Terraform Installation +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. -Verify Terraform is installed: +**Usage:** ```bash -terraform version +./scripts/validate-terraform.sh [infra-dir] [subscription-id] # infra-dir defaults to ./infra ``` - -**If not installed:** See https://developer.hashicorp.com/terraform/install - -### 2. Azure CLI Installation - -Verify Azure CLI is installed: - -```bash -az version -``` - -**If not installed:** -``` -mcp_azure_mcp_extension_cli_install(cli-type: "az") +```powershell +.\scripts\validate-terraform.ps1 [-InfraDir ] [-SubscriptionId ] ``` -### 3. Authentication +**Examples:** ```bash -az account show +./scripts/validate-terraform.sh # validate ./infra +./scripts/validate-terraform.sh ./infra 00000000-0000-0000-0000-000000000000 ``` - -**If not logged in:** -```bash -az login -az account set --subscription +```powershell +.\scripts\validate-terraform.ps1 -InfraDir ./infra ``` -### 4. Initialize +**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. -```bash -cd infra -terraform init -``` +## Remediation -### 5. Format Check +The script only **runs and reports** — fixing failures is manual. Guidance per step: -```bash -terraform fmt -check -recursive -``` +### Terraform / Azure CLI not installed -**Fix if needed:** -```bash -terraform fmt -recursive -``` +- Terraform: see https://developer.hashicorp.com/terraform/install +- Azure CLI: `mcp_azure_mcp_extension_cli_install(cli-type: "az")` -### 6. Validate Syntax +### Not authenticated / wrong subscription ```bash -terraform validate +az login +az account set --subscription ``` -### 7. Plan Preview +### Format check failed ```bash -terraform plan -out=tfplan +terraform fmt -recursive ``` -### 8. State Backend +### Init / validate / plan / state failures -Verify state is accessible: +Read the captured error text in the script output, then consult +[Error handling](./errors.md). -```bash -terraform state list -``` +### Azure Policy Validation -### 9. 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. -See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription. - -### 10. Template Variable Resolution Check (AZD+Terraform) +### 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. -**Check for Go-style template variables:** - -```bash -# Check for Go-style template variables in Terraform files -grep -rn '{{ *\.Env\.' infra/ --include='*.tf' --include='*.tfvars.json' || echo "OK: No Go-style template variables found" - -# Check main.tfvars.json uses correct ${VAR} syntax -if test -f infra/main.tfvars.json; then - grep -n '{{ *\.Env\.' infra/main.tfvars.json && echo "WARNING: Use \${VAR} syntax instead of {{ .Env.* }}" || echo "OK: main.tfvars.json syntax is correct" -fi -``` +When the template-variable scan reports `FAIL`: -**If Go-style template variables are found:** 1. **Fix the syntax** in `main.tfvars.json` — replace `{{ .Env.VAR }}` with `${VAR}`: ```json - { - "environment_name": "${AZURE_ENV_NAME}", - "location": "${AZURE_LOCATION}" - } + { "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** `terraform validate` and `terraform plan` to confirm +3. **Verify** that `variables.tf` declares all required variables. +4. **Re-run** the script to confirm `terraform validate` / `plan` and the scan now pass. -**If `.tfvars.json` uses wrong syntax:** -- Replace Go-style `{{ .Env.* }}` with `${VAR}` (azd's envsubst format) -- 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. +> 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 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.ps1 b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.ps1 new file mode 100644 index 00000000..05af2714 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.ps1 @@ -0,0 +1,180 @@ +<# +.SYNOPSIS + Runs the Terraform pre-deployment validation preflight sequence and prints a + compact PASS/FAIL/SKIP summary plus captured error text for any failed step. +.DESCRIPTION + 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. +.PARAMETER InfraDir + Path to the Terraform infra directory (default: ./infra). +.PARAMETER SubscriptionId + Optional subscription to select before checks. +.EXAMPLE + .\validate-terraform.ps1 + # Validate ./infra +.EXAMPLE + .\validate-terraform.ps1 -InfraDir ./infra -SubscriptionId 00000000-0000-0000-0000-000000000000 + # Validate an explicit directory against a specific subscription +.NOTES + Exit code: 0 when every non-skipped step passes, 1 when any step fails. +#> +param( + [string]$InfraDir = "./infra", + [string]$SubscriptionId +) + +$ErrorActionPreference = "Continue" + +$steps = [System.Collections.Generic.List[object]]::new() + +function Add-Result { + param([string]$Name, [string]$Status, [string]$ErrorText = "") + # Results are collected here and rendered once in the summary at the end. + $steps.Add([pscustomobject]@{ Name = $Name; Status = $Status; Error = $ErrorText }) +} + +function Test-Command { + param([string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +Write-Host "Terraform validation preflight - infra dir: $InfraDir" +Write-Host "" + +# --- 1. Terraform installed -------------------------------------------------- +if (Test-Command "terraform") { + Add-Result "Terraform installed" "PASS" +} else { + Add-Result "Terraform installed" "FAIL" "terraform not found on PATH. Install: https://developer.hashicorp.com/terraform/install" +} + +# --- 2. Azure CLI installed -------------------------------------------------- +$hasAz = Test-Command "az" +if ($hasAz) { + Add-Result "Azure CLI installed" "PASS" +} else { + Add-Result "Azure CLI installed" "FAIL" "az not found on PATH. Install the Azure CLI: mcp_azure_mcp_extension_cli_install(cli-type: `"az`")" +} + +# --- 3. Authentication ------------------------------------------------------- +if ($hasAz) { + if ($SubscriptionId) { + $subOut = az account set --subscription $SubscriptionId 2>&1 + if ($LASTEXITCODE -eq 0) { + Add-Result "Select subscription" "PASS" + } else { + Add-Result "Select subscription" "FAIL" ($subOut | Out-String).Trim() + } + } + $accountOut = az account show -o none 2>&1 + if ($LASTEXITCODE -eq 0) { + Add-Result "Authenticated (az account show)" "PASS" + } else { + Add-Result "Authenticated (az account show)" "FAIL" ($accountOut | Out-String).Trim() + } +} else { + Add-Result "Authenticated (az account show)" "SKIP" "Azure CLI not installed" +} + +# --- infra dir presence gate ------------------------------------------------- +$haveTf = (Test-Command "terraform") -and (Test-Path -Path $InfraDir -PathType Container) + +function Invoke-Tf { + param([string]$Name, [string[]]$Args) + if (-not $haveTf) { + Add-Result $Name "SKIP" "terraform unavailable or infra dir '$InfraDir' not found" + return + } + Push-Location $InfraDir + try { + # 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. + $tmp = New-TemporaryFile + & terraform @Args *> $tmp.FullName + if ($LASTEXITCODE -eq 0) { + Add-Result $Name "PASS" + } else { + $content = Get-Content -Raw $tmp.FullName + if ($null -eq $content) { $content = "" } + Add-Result $Name "FAIL" $content.Trim() + } + Remove-Item $tmp.FullName -ErrorAction SilentlyContinue + } finally { + Pop-Location + } +} + +# --- 4. Initialize ----------------------------------------------------------- +Invoke-Tf "terraform init" @("init", "-input=false") + +# --- 5. Format check --------------------------------------------------------- +Invoke-Tf "terraform fmt -check" @("fmt", "-check", "-recursive") + +# --- 6. Validate syntax ------------------------------------------------------ +Invoke-Tf "terraform validate" @("validate") + +# --- 7. Plan preview --------------------------------------------------------- +Invoke-Tf "terraform plan" @("plan", "-input=false", "-out=tfplan") + +# --- 8. State backend -------------------------------------------------------- +Invoke-Tf "terraform state list" @("state", "list") + +# --- 9. Go-style template-variable scan -------------------------------------- +if (Test-Path -Path $InfraDir -PathType Container) { + $hits = Get-ChildItem -Path $InfraDir -Recurse -Include "*.tf", "*.tfvars.json" -ErrorAction SilentlyContinue | + Select-String -Pattern '{{ *\.Env\.' | + ForEach-Object { "{0}:{1}:{2}" -f $_.Path, $_.LineNumber, $_.Line.Trim() } + if ($hits) { + $detail = "Found unresolved Go-style template variables - replace {{ .Env.VAR }} with `${VAR} (azd envsubst format):`n" + ($hits -join "`n") + Add-Result "Template-variable scan ({{ .Env.* }})" "FAIL" $detail + } else { + Add-Result "Template-variable scan ({{ .Env.* }})" "PASS" + } +} else { + Add-Result "Template-variable scan ({{ .Env.* }})" "SKIP" "infra dir '$InfraDir' not found" +} + +# --- 10. main.tfvars.json JSON syntax ---------------------------------------- +$tfvars = Join-Path $InfraDir "main.tfvars.json" +if (Test-Path -Path $tfvars -PathType Leaf) { + try { + Get-Content -Raw $tfvars | ConvertFrom-Json -ErrorAction Stop | Out-Null + Add-Result "main.tfvars.json is valid JSON" "PASS" + } catch { + Add-Result "main.tfvars.json is valid JSON" "FAIL" $_.Exception.Message + } +} else { + Add-Result "main.tfvars.json is valid JSON" "SKIP" "$tfvars not found" +} + +# --- summary ----------------------------------------------------------------- +Write-Host "" +Write-Host "==================== SUMMARY ====================" +"{0,-40} {1}" -f "STEP", "RESULT" | Write-Host +"{0,-40} {1}" -f "----", "------" | Write-Host +foreach ($s in $steps) { + "{0,-40} {1}" -f $s.Name, $s.Status | Write-Host +} +Write-Host "=================================================" + +$failed = @($steps | Where-Object { $_.Status -eq "FAIL" }) +if ($failed.Count -gt 0) { + Write-Host "" + Write-Host "----- FAILURE DETAILS -----" + foreach ($s in $failed) { + Write-Host "" + Write-Host "### $($s.Name)" + Write-Host $s.Error + } + Write-Host "" + Write-Host "RESULT: $($failed.Count) step(s) failed. See remediation guidance in README.md." + exit 1 +} + +Write-Host "" +Write-Host "RESULT: All checks passed. Ready for azure-deploy." +exit 0 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.sh b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.sh new file mode 100755 index 00000000..43abfbd8 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/recipes/terraform/scripts/validate-terraform.sh @@ -0,0 +1,185 @@ +#!/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 [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 + 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 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.ps1 b/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.ps1 new file mode 100644 index 00000000..946bf237 --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.ps1 @@ -0,0 +1,84 @@ +<# +.SYNOPSIS + Aspire + Azure Functions secret-storage pre-provisioning scan. +.DESCRIPTION + 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. + + Prints 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. +.PARAMETER Path + Directory to scan. Defaults to the current directory. +.EXAMPLE + .\scan-aspire-functions-secrets.ps1 + # Scan the current directory +.EXAMPLE + .\scan-aspire-functions-secrets.ps1 -Path ./src + # Scan a specific directory +#> +param( + [string]$Path = "." +) + +# Leave $ErrorActionPreference at its default ("Continue") so that Write-Error +# below is non-terminating and the explicit `exit` codes are honored. + +if (-not (Test-Path -LiteralPath $Path -PathType Container)) { + Write-Error "'$Path' is not a directory." + exit 2 +} + +$call = "AddAzureFunctionsProject" +$setting = "AzureWebJobsSecretStorageType" + +# Collect *.cs files that reference AddAzureFunctionsProject. +$matches = Get-ChildItem -Path $Path -Recurse -File -Filter "*.cs" | + Where-Object { Select-String -Path $_.FullName -SimpleMatch -Pattern $call -Quiet } + +if (-not $matches) { + Write-Output "VERDICT: NOT APPLICABLE" + Write-Output "No '$call' call found in any *.cs file under '$Path'." + Write-Output "The Functions secret-storage check does not apply - skip it." + exit 0 +} + +# Partition matching files by whether they already configure the setting. +$needsFix = @() +$configured = @() +foreach ($file in $matches) { + if (Select-String -Path $file.FullName -SimpleMatch -Pattern $setting -Quiet) { + $configured += $file + } else { + $needsFix += $file + } +} + +if ($needsFix.Count -eq 0) { + Write-Output "VERDICT: ALREADY CONFIGURED" + Write-Output "Every file that calls '$call' already sets '$setting':" + foreach ($file in $configured) { + $line = (Select-String -Path $file.FullName -SimpleMatch -Pattern $setting | + Select-Object -First 1).LineNumber + Write-Output " - $($file.FullName) (line $line)" + } + Write-Output "No change required." + exit 0 +} + +Write-Output "VERDICT: FIX REQUIRED" +Write-Output "The following file(s) call '$call' but do NOT set '$setting':" +foreach ($file in $needsFix) { + $line = (Select-String -Path $file.FullName -SimpleMatch -Pattern $call | + Select-Object -First 1).LineNumber + Write-Output " - $($file.FullName) (line $line)" +} +Write-Output "" +Write-Output "Add .WithEnvironment(`"$setting`", `"Files`") to the AddAzureFunctionsProject" +Write-Output "builder chain in each file above BEFORE running 'azd provision'." +exit 0 diff --git a/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.sh b/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.sh new file mode 100644 index 00000000..6c657f8c --- /dev/null +++ b/.github/plugins/azure-skills/skills/azure-validate/references/scripts/scan-aspire-functions-secrets.sh @@ -0,0 +1,105 @@ +#!/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 +# 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 +# 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 diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/SKILL.md b/.github/plugins/azure-skills/skills/microsoft-foundry/SKILL.md index 9d50974c..a9603f47 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/SKILL.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/SKILL.md @@ -4,7 +4,7 @@ description: "Deploy, evaluate, fine-tune, and manage Foundry agents end-to-end license: MIT metadata: author: Microsoft - version: "1.1.46" + version: "1.1.48" --- # Microsoft Foundry Skill @@ -19,7 +19,7 @@ Before using Foundry MCP operations, call the Azure MCP `foundry` tool and inspe > **MANDATORY: Before executing ANY workflow-specific steps, you MUST read the corresponding sub-skill document.** Do not call workflow-specific MCP tools for a workflow without reading its skill document. This applies even if you already know the MCP tool parameters — the skill document contains required workflow steps, pre-checks, and validation logic that must be followed. This rule applies on every new user message that triggers a different workflow, even if the skill is already loaded. -Before executing Foundry-specific azd commands, read [azd-guidance](foundry-agent/azd-guidance/azd-guidance.md) first. Then read any applicable workflow-specific sub-skill. Direct questions about the Foundry azd CLI can use `azd-guidance` independently. +> **MANDATORY: Before executing ANY azd command, you MUST read [azd-guidance](foundry-agent/azd-guidance/azd-guidance.md) and strictly follow the shared rules defined in it, especially the `AZURE_DEV_USER_AGENT` setting rules.** This skill includes specialized sub-skills for specific workflows. **Use these instead of the main skill when they match your task:** @@ -44,7 +44,7 @@ This skill includes specialized sub-skills for specific workflows. **Use these i | **quota** | Managing quotas and capacity for Microsoft Foundry resources. Use when checking quota usage, troubleshooting deployment failures due to insufficient quota, requesting quota increases, or planning capacity. | [quota/quota.md](quota/quota.md) | | **rbac** | Managing RBAC permissions, role assignments, managed identities, and service principals for Microsoft Foundry resources. Use for access control, auditing permissions, and CI/CD setup. | [rbac/rbac.md](rbac/rbac.md) | | **finetuning** | Fine-tune models on Azure AI Foundry — SFT distillation, DPO preference optimization, RFT with graders and tool calling. Dataset preparation, grader calibration, training, checkpoint selection, deployment, evaluation. Use for: fine-tune, SFT, DPO, RFT, training data, grader, distillation, fine-tuned model, large file upload. | [finetuning/SKILL.md](finetuning/SKILL.md) | -| **azd-guidance** | Provide shared azd knowledge and guidance for managing Foundry agents. Read this first for agent lifecycle workflows and use it to answer Foundry-specific azd CLI questions. | [azd-guidance](foundry-agent/azd-guidance/azd-guidance.md) | +| **azd-guidance** | Provide shared azd knowledge and guidance for managing Foundry agents. Read this first for any workflows related to azd. | [azd-guidance](foundry-agent/azd-guidance/azd-guidance.md) | > 💡 **Tip:** For a complete onboarding flow: `project/create` (public) or `private-network` (VNet isolation) → `models/deploy-model` → agent workflows (`create` → `deploy` → `invoke`). diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/azd-guidance/azd-guidance.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/azd-guidance/azd-guidance.md index 55ec2d2e..4ede7dd8 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/azd-guidance/azd-guidance.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/azd-guidance/azd-guidance.md @@ -4,6 +4,18 @@ Read this skill before running any Foundry agent workflow that uses azd. Also us ## Shared Rules -1. If an azd command or flag is unclear, run the relevant `azd ... --help` command and follow its output. -2. Unless the user explicitly asks to open a client, run `azd ai agent run --no-client`. -3. If the needed azd guidance is not covered here or remains unclear, read [azd ai CLI Reference](references/azd-ai-cli.md). +1. Always set `AZURE_DEV_USER_AGENT=microsoft_foundry_skill` when running azd commands, for example: + + ```bash + AZURE_DEV_USER_AGENT=microsoft_foundry_skill azd ai agent init + AZURE_DEV_USER_AGENT=microsoft_foundry_skill azd ai agent run --no-client + AZURE_DEV_USER_AGENT=microsoft_foundry_skill azd provision + AZURE_DEV_USER_AGENT=microsoft_foundry_skill azd deploy + AZURE_DEV_USER_AGENT=microsoft_foundry_skill azd ai agent invoke + ``` + +Set it inline only (as shown above). Never persist it into code or committed config (for example, `azd env set`, `.env`, or `azure.yaml`). It is a local-development-only setting. + +2. If an azd command or flag is unclear, run the relevant `azd ... --help` command and follow its output. +3. Unless the user explicitly asks to open a client, run `azd ai agent run --no-client`. +4. If the needed azd guidance is not covered here or remains unclear, read [azd ai CLI Reference](references/azd-ai-cli.md). diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/create-prompt.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/create-prompt.md index f1b0a336..b6657fb8 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/create-prompt.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/create-prompt.md @@ -57,7 +57,7 @@ For a **workflow**: If MCP tools are unavailable, use the `azure-ai-projects` SDK: - See [SDK Operations](references/sdk-operations.md) for create, list, update, delete code samples -- See [Agent Tools](references/agent-tools.md) for adding tools to agents +- See [Agent Tools](references/tools/prompt-agent/agent-tools.md) for adding tools to agents ### Step 4: Add Tools (Optional) @@ -65,16 +65,16 @@ If MCP tools are unavailable, use the `azure-ai-projects` SDK: | Tool Category | Reference | |---------------|-----------| -| Code Interpreter, Function Calling | [Simple Tools](references/agent-tools.md) | -| File Search (requires vector store) | [File Search](references/tool-file-search.md) | -| Web Search (default, no setup needed) | [Web Search](references/tool-web-search.md) | -| Bing Grounding (explicit request only) | [Bing Grounding](references/tool-bing-grounding.md) | -| Azure AI Search (private data) | [Azure AI Search](references/tool-azure-ai-search.md) | -| MCP Servers | [MCP Tool](references/tool-mcp.md) | -| Memory (persistent across sessions) | [Memory](references/tool-memory.md) | +| Code Interpreter, Function Calling | [Simple Tools](references/tools/prompt-agent/agent-tools.md) | +| File Search (requires vector store) | [File Search](references/tools/prompt-agent/tool-file-search.md) | +| Web Search (default, no setup needed) | [Web Search](references/tools/prompt-agent/tool-web-search.md) | +| Bing Grounding (explicit request only) | [Bing Grounding](references/tools/prompt-agent/tool-bing-grounding.md) | +| Azure AI Search (private data) | [Azure AI Search](references/tools/prompt-agent/tool-azure-ai-search.md) | +| MCP Servers | [MCP Tool](references/tools/prompt-agent/tool-mcp.md) | +| Memory (persistent across sessions) | [Memory](references/tools/prompt-agent/tool-memory.md) | | Connections (for tools that need them) | [Project Connections](../../project/connections.md) | -> ⚠️ **Web Search Default:** Use `WebSearchPreviewTool` for web search. Only use `BingGroundingAgentTool` when the user explicitly requests Bing Grounding. +> ⚠️ **Web Search Default:** Use `WebSearchPreviewTool` for web search. Only use `BingGroundingTool` when the user explicitly requests Bing Grounding. ## Error Handling diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/agent-tools.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/agent-tools.md deleted file mode 100644 index 9924f4eb..00000000 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/agent-tools.md +++ /dev/null @@ -1,52 +0,0 @@ -# Agent Tools - -This file is the **index** for every tool an agent can use. For each tool, it points to a dedicated reference file, and — where the tool is also available through a [toolbox](use-toolbox-in-hosted-agent.md) — lists the toolbox `type` value. - -Two delivery paths exist: - -- **Prompt agent** — the agent definition declares tool classes directly (`CodeInterpreterTool`, `MCPTool`, …). Use the SDK class column and the per-tool reference. -- **Hosted agent via toolbox** — the agent connects to a single MCP endpoint that exposes all tools declared in a toolbox version. Use the `type` column and see [use-toolbox-in-hosted-agent.md](use-toolbox-in-hosted-agent.md). For wiring the underlying project connection (catalog tile or generic remote MCP), see [foundry-tool-catalog.md](foundry-tool-catalog.md). - -> 💡 **Authoritative tool shapes:** the source-of-truth for every tool's wire shape is the **Foundry Agents typespec** on the `main` branch of [`Azure/azure-rest-api-specs`](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/cognitiveservices). When in doubt about a field name, default, or new tool type that isn't yet documented here, load the typespec directly — it's updated as tools are added/changed. - -## Tool Summary - -| Tool | Prompt-agent SDK class | Toolbox `type` | Connection? | Reference | -|------|------------------------|----------------|-------------|-----------| -| Code Interpreter | `CodeInterpreterTool` | `code_interpreter` | No | [tool-code-interpreter.md](tool-code-interpreter.md) | -| Function calling (client-side) | `FunctionTool` | — (client-side only) | No | [tool-function-calling.md](tool-function-calling.md) | -| File Search | `FileSearchTool` | `file_search` | No (vector store required) | [tool-file-search.md](tool-file-search.md) | -| Web Search (preview) | `WebSearchPreviewTool` | `web_search` (with optional `web_search.custom_search_configuration` for Bing Custom Search) | No (basic Bing); **Yes** for Grounding with Bing Custom Search — the connection scopes grounding to specific domains | [tool-web-search.md](tool-web-search.md) | -| Bing Grounding | `BingGroundingAgentTool` | — (N/A in toolbox; the toolbox path uses `web_search` with `web_search.custom_search_configuration`) | Yes (Bing) — prompt-agent path only | [tool-bing-grounding.md](tool-bing-grounding.md) | -| Azure AI Search | `AzureAISearchAgentTool` | `azure_ai_search` | Yes (Search) | [tool-azure-ai-search.md](tool-azure-ai-search.md) | -| MCP server (remote) | `MCPTool` | `mcp` | Optional (none / static key / project MI / OAuth) | [tool-mcp.md](tool-mcp.md); toolbox attach via [foundry-tool-catalog.md](foundry-tool-catalog.md) | -| OpenAPI tool | (n/a as a single class) | `openapi` | Conditional — `connection` auth requires `project_connection_id`; **`managed_identity` auth does NOT** (the project MI is used directly with an `audience`) | [tool-openapi.md](tool-openapi.md) | -| Agent-to-Agent (A2A) | (n/a as a single class) | `a2a_preview` | Optional | [tool-a2a.md](tool-a2a.md) | -| Agent Memory | `MemorySearchTool` | — (separate memory store) | Yes (project MI + embedding model) | [tool-memory.md](tool-memory.md) | -| **Work IQ (preview)** | (n/a — server-side only) | `work_iq_preview` | Yes (Work IQ BYO-Entra-app OAuth connection) | [tool-work-iq.md](tool-work-iq.md) | -| **Fabric IQ (preview)** | (n/a — server-side only) | `fabric_iq_preview` | Yes (Fabric IQ Entra-app OAuth or managed-OAuth connection) | [tool-fabric-iq.md](tool-fabric-iq.md) | -| **Tool Search (preview)** | (n/a — toolbox-side configuration directive) | `toolbox_search_preview` | No | [tool-tool-search.md](tool-tool-search.md) | - -> ⚠️ **Default for web search:** Use `WebSearchPreviewTool` (`type: web_search`) unless the user explicitly requests Bing Grounding or Bing Custom Search. - -> Combine multiple tools on one agent or one toolbox version. The model decides which to invoke. For multi-tool toolbox limits (at most one unnamed tool per type, unique `server_label` per MCP tool) see [toolbox-reference.md](toolbox-reference.md#multi-tool-toolbox-constraint). - -## How to use this index - -When you need details for a specific tool, **load that tool's reference file directly** — each one is self-contained (shape, requirements, references). Don't try to keep all tools in context at once. - -For the toolbox runtime contract (endpoint, auth, MCP protocol, citation patterns, troubleshooting) see [toolbox-reference.md](toolbox-reference.md). For wiring a toolbox into a hosted agent (env vars, samples, tracing) see [use-toolbox-in-hosted-agent.md](use-toolbox-in-hosted-agent.md). - -## Adjacent (not a `type` in a toolbox version) - -- **Agent Memory** — use the `MemorySearchTool` SDK class on prompt agents; for hosted agents, configure the memory store via the project (separate from the toolbox). See [tool-memory.md](tool-memory.md). -- **Routines (preview)** — not a tool; an agent **trigger** (`schedule` / `timer` / `github_issue` / `custom`) that invokes an existing agent. Event-based routines are powered by the same **Connector Namespace** that backs catalog-MCP / managed-MCP connectors. See the [public Routines docs](https://learn.microsoft.com/azure/foundry/agents/how-to/use-routines). - -## References - -- **[Foundry Agents typespec (`main`)](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/cognitiveservices)** — authoritative tool shapes -- [Tool Catalog](https://learn.microsoft.com/azure/foundry/agents/concepts/tool-catalog) -- [Toolbox (preview)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox) -- [use-toolbox-in-hosted-agent.md](use-toolbox-in-hosted-agent.md) — wiring a toolbox into a hosted agent -- [toolbox-reference.md](toolbox-reference.md) — toolbox runtime contract -- [foundry-tool-catalog.md](foundry-tool-catalog.md) — project connections for remote tools diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/foundry-tool-catalog.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/foundry-tool-catalog.md index d39e0069..1648bc31 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/foundry-tool-catalog.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/foundry-tool-catalog.md @@ -7,7 +7,7 @@ Reference for wiring a **remote tool** (catalog tile or generic MCP server) into Three catalog backends cooperate: the **asset-gallery** index discovers connectors, the Logic Apps **managedApis** GET supplies OAuth metadata, and the Logic Apps **apiOperations** GET supplies the operation list and input schemas. Skip these calls only for fully BYO `generic_mcp` servers — every catalog-MCP or connector-namespace flow needs all three. > 📘 For the toolbox MCP endpoint, protocol, and testing, see [toolbox-reference.md](toolbox-reference.md). -> 📘 For prompt-agent MCP wiring (without a toolbox), see [tool-mcp.md](tool-mcp.md). +> 📘 For prompt-agent MCP wiring (without a toolbox), see [tool-mcp.md](tools/prompt-agent/tool-mcp.md). ## When to use this reference @@ -703,7 +703,7 @@ The response body for `/mcp` is plain JSON (no SSE `data:` framing) despite the - [Toolbox (preview)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox) - [Private tools catalog](https://learn.microsoft.com/azure/foundry/agents/concepts/tool-catalog#private-tools-catalog) - [Cognitive Services projects REST API](https://learn.microsoft.com/rest/api/aiservices/) -- [tool-mcp.md](tool-mcp.md) — prompt-agent MCP wiring (no toolbox) +- [tool-mcp.md](tools/prompt-agent/tool-mcp.md) — prompt-agent MCP wiring (no toolbox) - [toolbox-reference.md](toolbox-reference.md) — MCP endpoint, auth, testing, troubleshooting -- [agent-tools.md](agent-tools.md) — the agent-tools index +- [agent-tools.md](tools/prompt-agent/agent-tools.md) — the agent-tools index - [use-toolbox-in-hosted-agent.md](use-toolbox-in-hosted-agent.md) — wiring a toolbox into a hosted agent diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-tool-search.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-tool-search.md index b40d0cea..e2aebbf7 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-tool-search.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-tool-search.md @@ -35,5 +35,5 @@ For toolboxes containing many tools, replace the full tool list passed to the mo For full fields, pinning recipes, the verify-with-`tool_search` flow, and best practices, see [Tool Search tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/tool-search). -- [agent-tools.md](agent-tools.md) — tool index +- [agent-tools.md](tools/prompt-agent/agent-tools.md) — tool index - [use-toolbox-in-hosted-agent.md § Recommendation: enable Tool Search](use-toolbox-in-hosted-agent.md#-recommendation-enable-tool-search) diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/agent-tools.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/agent-tools.md new file mode 100644 index 00000000..b201df47 --- /dev/null +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/agent-tools.md @@ -0,0 +1,51 @@ +# Agent Tools + +This file is the **index** for every tool an agent can use. For each tool, it points to a dedicated reference file, and — where the tool is also available through a [toolbox](../../use-toolbox-in-hosted-agent.md) — lists the toolbox `type` value. + +Two delivery paths exist: + +- **Prompt agent** — the agent definition declares tool classes directly (`CodeInterpreterTool`, `MCPTool`, …). Use the SDK class column and the per-tool reference. +- **Hosted agent via toolbox** — the agent connects to a single MCP endpoint that exposes all tools declared in a toolbox version. Use the `type` column and see [use-toolbox-in-hosted-agent.md](../../use-toolbox-in-hosted-agent.md). For wiring the underlying project connection (catalog tile or generic remote MCP), see [foundry-tool-catalog.md](../../foundry-tool-catalog.md). + +> 💡 **Authoritative tool shapes:** the source-of-truth for every tool's wire shape is the **Foundry Agents typespec** on the `main` branch of [`Azure/azure-rest-api-specs`](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/cognitiveservices). When in doubt about a field name, default, or new tool type that isn't yet documented here, load the typespec directly — it's updated as tools are added/changed. + +## Tool Summary + +| Tool | Prompt-agent SDK class | Connection? | Reference | +|------|------------------------|-------------|-----------| +| Code Interpreter | `CodeInterpreterTool` | No | [tool-code-interpreter.md](tool-code-interpreter.md) | +| Function calling (client-side) | `FunctionTool` | No | [tool-function-calling.md](tool-function-calling.md) | +| File Search | `FileSearchTool` | No (vector store required) | [tool-file-search.md](tool-file-search.md) | +| Web Search (preview) | `WebSearchPreviewTool` | No (basic Bing); **Yes** for Grounding with Bing Custom Search — the connection scopes grounding to specific domains | [tool-web-search.md](tool-web-search.md) | +| Bing Grounding | `BingGroundingTool` | Yes (Bing) | [tool-bing-grounding.md](tool-bing-grounding.md) | +| Azure AI Search | `AzureAISearchTool` | Yes (Search) | [tool-azure-ai-search.md](tool-azure-ai-search.md) | +| MCP server (remote) | `MCPTool` | Optional (none / static key / project MI / OAuth) | [tool-mcp.md](tool-mcp.md); toolbox attach via [foundry-tool-catalog.md](../../foundry-tool-catalog.md) | +| OpenAPI tool | (n/a as a single class) | Conditional — `connection` auth requires `project_connection_id`; **`managed_identity` auth does NOT** (the project MI is used directly with an `audience`) | [tool-openapi.md](tool-openapi.md) | +| Agent-to-Agent (A2A) | (n/a as a single class) | Optional | [tool-a2a.md](tool-a2a.md) | +| Agent Memory | `MemorySearchPreviewTool` | Yes (project MI + embedding model) | [tool-memory.md](tool-memory.md) | +| **Work IQ (preview)** | `WorkIQPreviewTool` | Yes (Work IQ BYO-Entra-app OAuth connection) | [tool-work-iq.md](tool-work-iq.md) | +| **Fabric IQ (preview)** | `FabricIQPreviewTool` | Yes (Fabric IQ Entra-app OAuth or managed-OAuth connection) | [tool-fabric-iq.md](tool-fabric-iq.md) | + +> ⚠️ **Default for web search:** Use `WebSearchPreviewTool` (`type: web_search`) unless the user explicitly requests Bing Grounding or Bing Custom Search. + +> Combine multiple tools on one agent or one toolbox version. The model decides which to invoke. For multi-tool toolbox limits (at most one unnamed tool per type, unique `server_label` per MCP tool) see [toolbox-reference.md](../../toolbox-reference.md#multi-tool-toolbox-constraint). + +## How to use this index + +When you need details for a specific tool, **load that tool's reference file directly** — each one is self-contained (shape, requirements, references). Don't try to keep all tools in context at once. + +For the toolbox runtime contract (endpoint, auth, MCP protocol, citation patterns, troubleshooting) see [toolbox-reference.md](../../toolbox-reference.md). For wiring a toolbox into a hosted agent (env vars, samples, tracing) see [use-toolbox-in-hosted-agent.md](../../use-toolbox-in-hosted-agent.md). + +## Adjacent (not a `type` in a toolbox version) + +- **Agent Memory** — use the `MemorySearchPreviewTool` SDK class on prompt agents; for hosted agents, configure the memory store via the project (separate from the toolbox). See [tool-memory.md](tool-memory.md). +- **Routines (preview)** — not a tool; an agent **trigger** (`schedule` / `timer` / `github_issue` / `custom`) that invokes an existing agent. Event-based routines are powered by the same **Connector Namespace** that backs catalog-MCP / managed-MCP connectors. See the [public Routines docs](https://learn.microsoft.com/azure/foundry/agents/how-to/use-routines). + +## References + +- **[Foundry Agents typespec (`main`)](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/cognitiveservices)** — authoritative tool shapes +- [Tool Catalog](https://learn.microsoft.com/azure/foundry/agents/concepts/tool-catalog) +- [Toolbox (preview)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox) +- [use-toolbox-in-hosted-agent.md](../../use-toolbox-in-hosted-agent.md) — wiring a toolbox into a hosted agent +- [toolbox-reference.md](../../toolbox-reference.md) — toolbox runtime contract +- [foundry-tool-catalog.md](../../foundry-tool-catalog.md) — project connections for remote tools diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-a2a.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-a2a.md new file mode 100644 index 00000000..fc72d037 --- /dev/null +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-a2a.md @@ -0,0 +1,22 @@ +# Tool — Agent-to-Agent (A2A, preview) + +Call another Foundry agent as if it were a tool. Useful for composing specialist agents into an orchestrator. + +## Toolbox shape + +```json +{ + "type": "a2a_preview", + "name": "", + "description": "", + "base_url": "", + "project_connection_id": "" +} +``` + +Auth is either anonymous (for the same project) or via a project connection that holds credentials for the remote agent's host. + +## References + +- [A2A tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/agent-to-agent) +- [agent-tools.md](agent-tools.md) — tool index diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-azure-ai-search.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-azure-ai-search.md similarity index 92% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-azure-ai-search.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-azure-ai-search.md index 213ec14e..d0b12497 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-azure-ai-search.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-azure-ai-search.md @@ -9,7 +9,7 @@ Ground agent responses with data from an Azure AI Search vector index. Requires - One or more `Collection(Edm.Single)` vector fields (searchable) - At least one retrievable text field with content for citations - A retrievable field with source URL for citation links -- A [project connection](../../../project/connections.md) between your Foundry project and search service +- A [project connection](../../../../../project/connections.md) between your Foundry project and search service - `azure-ai-projects` package (`pip install azure-ai-projects --pre`) ## Required RBAC Roles @@ -25,7 +25,7 @@ For **keyless authentication** (recommended), assign these roles to the **Foundr ## Connection Setup -A project connection between your Foundry project and the Azure AI Search resource is required. See [Project Connections](../../../project/connections.md) for connection management via Foundry MCP tools. +A project connection between your Foundry project and the Azure AI Search resource is required. See [Project Connections](../../../../../project/connections.md) for connection management via Foundry MCP tools. ## Query Types @@ -66,4 +66,4 @@ A project connection between your Foundry project and the Azure AI Search resour - [Azure AI Search tool documentation](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/azure-ai-search?view=foundry) - [Tool Catalog](https://learn.microsoft.com/azure/ai-foundry/agents/concepts/tool-catalog?view=foundry) -- [Project Connections](../../../project/connections.md) +- [Project Connections](../../../../../project/connections.md) diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-bing-grounding.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-bing-grounding.md similarity index 93% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-bing-grounding.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-bing-grounding.md index 59b1fd9e..82d9c2ad 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-bing-grounding.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-bing-grounding.md @@ -16,13 +16,13 @@ Access real-time web information via Bing Search. Unlike the [Web Search tool](t - A [Grounding with Bing Search resource](https://portal.azure.com/#create/Microsoft.BingGroundingSearch) in Azure portal - `Contributor` or `Owner` role at subscription/RG level to create Bing resource and get keys - `Foundry Project Manager` role on the project to create a connection -- A project connection configured with the Bing resource key — see [connections](../../../project/connections.md) +- A project connection configured with the Bing resource key — see [connections](../../../../../project/connections.md) ## Setup 1. Register the Bing provider: `az provider register --namespace 'Microsoft.Bing'` 2. Create a Grounding with Bing Search resource in the Azure portal -3. Create a project connection with the Bing resource key — see [connections](../../../project/connections.md) +3. Create a project connection with the Bing resource key — see [connections](../../../../../project/connections.md) 4. Set `BING_PROJECT_CONNECTION_NAME` environment variable ## Important Disclosures @@ -46,5 +46,5 @@ Access real-time web information via Bing Search. Unlike the [Web Search tool](t - [Bing Grounding tool documentation](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/bing-grounding?view=foundry) - [Tool Catalog](https://learn.microsoft.com/azure/ai-foundry/agents/concepts/tool-catalog?view=foundry) - [Grounding with Bing Terms](https://www.microsoft.com/bing/apis/grounding-legal-enterprise) -- [Connections Guide](../../../project/connections.md) +- [Connections Guide](../../../../../project/connections.md) - [Web Search Tool (default)](tool-web-search.md) diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-code-interpreter.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-code-interpreter.md similarity index 91% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-code-interpreter.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-code-interpreter.md index 33cc0593..23e1d2c9 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-code-interpreter.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-code-interpreter.md @@ -22,4 +22,4 @@ No other fields. Only one `code_interpreter` per toolbox version (unnamed tool). - [Code Interpreter tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/code-interpreter) - [agent-tools.md](agent-tools.md) — tool index -- [toolbox-reference.md](toolbox-reference.md) — endpoint, auth, and MCP protocol details +- [toolbox-reference.md](../../toolbox-reference.md) — endpoint, auth, and MCP protocol details diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-fabric-iq.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-fabric-iq.md similarity index 95% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-fabric-iq.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-fabric-iq.md index 04ea56fa..c5381c28 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-fabric-iq.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-fabric-iq.md @@ -34,4 +34,4 @@ Connect an agent to Microsoft Fabric data — Ontology, Fabric data agents, and For the full Entra app setup, connection-creation walkthrough, and troubleshooting, see [Fabric IQ tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/fabric-iq). - [agent-tools.md](agent-tools.md) — tool index -- [foundry-tool-catalog.md](foundry-tool-catalog.md) — connection shape for Fabric IQ +- [foundry-tool-catalog.md](../../foundry-tool-catalog.md) — connection shape for Fabric IQ diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-file-search.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-file-search.md similarity index 100% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-file-search.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-file-search.md diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-function-calling.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-function-calling.md similarity index 100% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-function-calling.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-function-calling.md diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-mcp.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-mcp.md similarity index 91% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-mcp.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-mcp.md index 0a70e593..30b0fa3f 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-mcp.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-mcp.md @@ -5,14 +5,14 @@ Connect agents to remote MCP servers to extend capabilities with external tools ## Prerequisites - A remote MCP server endpoint (e.g., `https://api.githubcopilot.com/mcp`) -- For authenticated servers: a [project connection](../../../project/connections.md) storing credentials +- For authenticated servers: a [project connection](../../../../../project/connections.md) storing credentials - RBAC: **Contributor** or **Owner** role on the Foundry project ## Authenticated Server Connections For authenticated MCP servers, create an `api_key` project connection to store credentials. Unauthenticated servers (public endpoints) don't need a connection — omit `project_connection_id`. -See [Project Connections](../../../project/connections.md) for connection management via Foundry MCP tools. +See [Project Connections](../../../../../project/connections.md) for connection management via Foundry MCP tools. ## MCPTool Parameters @@ -63,4 +63,4 @@ Agent Service only accepts **remote** MCP endpoints. To use a local server, depl - [MCP tool documentation](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/mcp?view=foundry) - [Tool Catalog](https://learn.microsoft.com/azure/ai-foundry/agents/concepts/tool-catalog?view=foundry) -- [Project Connections](../../../project/connections.md) +- [Project Connections](../../../../../project/connections.md) diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-memory.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-memory.md similarity index 99% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-memory.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-memory.md index 32537ed0..b2f76189 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-memory.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-memory.md @@ -98,7 +98,7 @@ The `scope` parameter partitions memory per user: | Auth/authorization error | Identity or managed identity lacks required roles | Verify roles in Authorization section; refresh access token for REST | | Memories don't appear after conversation | Updates are debounced or still processing | Increase wait time or call update API with `update_delay=0` | | Memory search returns no results | Scope mismatch between update and search | Use same scope value for storing and retrieving memories | -| Agent response ignores stored memory | Agent not configured with memory search tool | Confirm agent definition includes `MemorySearchTool` with correct store name | +| Agent response ignores stored memory | Agent not configured with memory search tool | Confirm agent definition includes `MemorySearchPreviewTool` with correct store name | | No embedding model available | Embedding deployment missing | Deploy an embedding model — see Check Embedding Model section | ## References diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-openapi.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-openapi.md similarity index 87% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-openapi.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-openapi.md index 22926e0c..3f5a8779 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-openapi.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-openapi.md @@ -22,10 +22,10 @@ Expose a REST API to the agent by attaching its OpenAPI 3.x spec. The platform p ## Multi-entry rules -Multiple `openapi` entries are allowed in one toolbox **only if** each entry's spec defines a distinct `info.title` (the title is the implicit identifier). See [toolbox-reference.md § Multi-Tool Toolbox Constraint](toolbox-reference.md#multi-tool-toolbox-constraint). +Multiple `openapi` entries are allowed in one toolbox **only if** each entry's spec defines a distinct `info.title` (the title is the implicit identifier). See [toolbox-reference.md § Multi-Tool Toolbox Constraint](../../toolbox-reference.md#multi-tool-toolbox-constraint). ## References - [OpenAPI tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/openapi) - [agent-tools.md](agent-tools.md) — tool index -- [foundry-tool-catalog.md](foundry-tool-catalog.md) — project connections for the `connection` auth path +- [foundry-tool-catalog.md](../../foundry-tool-catalog.md) — project connections for the `connection` auth path diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-web-search.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-web-search.md similarity index 100% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-web-search.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-web-search.md diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-work-iq.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-work-iq.md similarity index 86% rename from .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-work-iq.md rename to .github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-work-iq.md index d7beefd5..44ea781c 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tool-work-iq.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/tools/prompt-agent/tool-work-iq.md @@ -2,7 +2,7 @@ Connect an agent to the user's Microsoft 365 work context — email, meetings, files, chats — through **Work IQ**. Work IQ runs as an A2A peer; every request runs in the context of the signed-in user and honors all Microsoft 365 permissions and sensitivity labels. -> 🚦 **Toolbox creation gate:** before creating a toolbox/connection, you MUST read the boundary rules in [create-hosted.md → Toolbox creation boundary](../create-hosted.md#toolbox-creation-boundary) and follow them, then continue with the rest of this file. +> 🚦 **Toolbox creation gate:** before creating a toolbox/connection, you MUST read the boundary rules in [create-hosted.md → Toolbox creation boundary](../../../create-hosted.md#toolbox-creation-boundary) and follow them, then continue with the rest of this file. ## Toolbox shape @@ -26,4 +26,4 @@ Connect an agent to the user's Microsoft 365 work context — email, meetings, f For the full Entra app setup, ARM connection-creation payload (`category: RemoteA2A`), and troubleshooting, see [Work IQ tool documentation](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/work-iq). - [agent-tools.md](agent-tools.md) — tool index -- [foundry-tool-catalog.md](foundry-tool-catalog.md) — RemoteA2A connection shape +- [foundry-tool-catalog.md](../../foundry-tool-catalog.md) — RemoteA2A connection shape diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md index 7fefc460..a498d1bb 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/references/use-toolbox-in-hosted-agent.md @@ -8,7 +8,7 @@ Hosted agents access Foundry-managed tools through a **Toolbox MCP endpoint**. U > > 📘 For wiring a remote tool (catalog tile or generic MCP server) into a project connection that a toolbox can attach to, see [foundry-tool-catalog.md](foundry-tool-catalog.md). > -> 📘 For the full list of supported tool types and their per-type fields, see [agent-tools.md](agent-tools.md) and the per-tool `tool-*.md` files. +> 📘 For the supported tool types and their per-type fields, see the table below and the public [Toolbox docs (Configure tools)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox#configure-tools). > 💡 **This skill is scoped to *consuming* an existing toolbox from agent code** — endpoint resolution, env-var contract, payload shape gathered before agent runtime, verification, and tracing. **Toolbox and connection CRUD belongs in [Foundry Toolkit (VS Code)](https://code.visualstudio.com/docs/intelligentapps/tool-catalog) or the [Foundry Portal](https://ai.azure.com/)** — those surfaces give you tool browsing, metadata, connection wizards, and validation. Use the imperative `azd ai` CLI only for *operational* tasks (retarget the default version, smoke-test an endpoint). @@ -56,24 +56,24 @@ Once the user supplies the toolbox name/endpoint — either an existing one or a ## Available tool types -The full set is documented in [agent-tools.md](agent-tools.md) and — authoritatively — in the public [Toolbox docs (Configure tools)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox#configure-tools). At time of writing the supported `type` values are: - -| `type` | Tool | Connection required? | Detail | -|---|---|---|---| -| `mcp` | Remote MCP server (third-party via catalog, BYO OAuth, or generic) | Optional (none / static key / project MI / OAuth) | [tool-mcp.md](tool-mcp.md) | -| `web_search` | Web search (basic Bing; optional `web_search.custom_search_configuration` for Bing Custom Search to scope grounding to specific domains) | No (basic); Yes for Custom Search | [tool-web-search.md](tool-web-search.md) | -| `azure_ai_search` | Azure AI Search index | Yes (Search service connection) | [tool-azure-ai-search.md](tool-azure-ai-search.md) | -| `code_interpreter` | Sandboxed Python execution | No | [tool-code-interpreter.md](tool-code-interpreter.md) | -| `file_search` | Vector-store-backed retrieval over uploaded files | No (vector store is part of the toolbox) | [tool-file-search.md](tool-file-search.md) | -| `openapi` | REST API exposed via an OpenAPI 3.x spec | Conditional (`connection` requires `project_connection_id`; `managed_identity` does not — uses project MI + `audience`) | [tool-openapi.md](tool-openapi.md) | -| `a2a_preview` | Call another Foundry agent as a tool | Optional | [tool-a2a.md](tool-a2a.md) | -| `work_iq_preview` | Microsoft 365 work context (mail / meetings / files / chats) via Work IQ | Yes (Work IQ `RemoteA2A` OAuth connection; BYO Entra app; M365 Copilot license per user) | [tool-work-iq.md](tool-work-iq.md) | -| `fabric_iq_preview` | Microsoft Fabric data (Ontology / Fabric data agent / Power BI semantic model) | Yes (Fabric IQ OAuth connection; tenant admin consent) | [tool-fabric-iq.md](tool-fabric-iq.md) | -| `toolbox_search_preview` | **Tool Search** — a directive (not a tool) that swaps `tools/list` for `tool_search` + `call_tool` meta-tools | No | [tool-tool-search.md](tool-tool-search.md) | +The full set is documented — authoritatively — in the public [Toolbox docs (Configure tools)](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox#configure-tools). At time of writing the supported `type` values are: + +| `type` | Tool | Connection required? | +|---|---|---| +| `mcp` | Remote MCP server (third-party via catalog, BYO OAuth, or generic) | Optional (none / static key / project MI / OAuth) | +| `web_search` | Web search (basic Bing; optional `web_search.custom_search_configuration` for Bing Custom Search to scope grounding to specific domains) | No (basic); Yes for Custom Search | +| `azure_ai_search` | Azure AI Search index | Yes (Search service connection) | +| `code_interpreter` | Sandboxed Python execution | No | +| `file_search` | Vector-store-backed retrieval over uploaded files | No (vector store is part of the toolbox) | +| `openapi` | REST API exposed via an OpenAPI 3.x spec | Conditional (`connection` requires `project_connection_id`; `managed_identity` does not — uses project MI + `audience`) | +| `a2a_preview` | Call another Foundry agent as a tool | Optional | +| `work_iq_preview` | Microsoft 365 work context (mail / meetings / files / chats) via Work IQ | Yes (Work IQ `RemoteA2A` OAuth connection; BYO Entra app; M365 Copilot license per user) | +| `fabric_iq_preview` | Microsoft Fabric data (Ontology / Fabric data agent / Power BI semantic model) | Yes (Fabric IQ OAuth connection; tenant admin consent) | +| `toolbox_search_preview` | **Tool Search** — a directive (not a tool) that swaps `tools/list` for `tool_search` + `call_tool` meta-tools | No | **Adjacent (not a `type` in a toolbox version):** -- **Agent Memory** — use the `MemorySearchTool` SDK class on prompt agents; for hosted agents, configure the memory store via the project (separate from the toolbox). See [tool-memory.md](tool-memory.md). +- **Agent Memory** — for hosted agents, configure the memory store at the **project** level (separate from the toolbox); it is not a toolbox `type` and is not wired through agent code. See the public [Memory docs](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/memory-usage?view=foundry). - **Routines (preview)** — not a tool; an agent **trigger** (`schedule` / `timer` / `github_issue` / `custom`) that invokes an existing agent. See the [public Routines docs](https://learn.microsoft.com/azure/foundry/agents/how-to/use-routines). ## Information to Gather Before Building a Toolbox Payload diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.ps1 b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.ps1 index bc886a00..35c44438 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.ps1 +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.ps1 @@ -12,6 +12,10 @@ #> $ErrorActionPreference = "Stop" + +# Tag azd telemetry +$env:AZURE_DEV_USER_AGENT = "microsoft_foundry_skill" + $actionRequired = $false function Note-Ok { param([string]$m) Write-Output "[OK] $m" } diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.sh b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.sh index 152fcbaa..33fd9e2b 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.sh +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/foundry-agent/create/scripts/verify-environment.sh @@ -12,6 +12,9 @@ set -uo pipefail +# Tag azd telemetry +export AZURE_DEV_USER_AGENT="microsoft_foundry_skill" + ACTION_REQUIRED=0 note_ok() { echo "[OK] $1"; } diff --git a/.github/plugins/azure-skills/skills/microsoft-foundry/references/sdk/foundry-sdk-py.md b/.github/plugins/azure-skills/skills/microsoft-foundry/references/sdk/foundry-sdk-py.md index 39e08606..9e53dacc 100644 --- a/.github/plugins/azure-skills/skills/microsoft-foundry/references/sdk/foundry-sdk-py.md +++ b/.github/plugins/azure-skills/skills/microsoft-foundry/references/sdk/foundry-sdk-py.md @@ -150,7 +150,7 @@ agent = project_client.agents.create_version( ) ``` -> 💡 **Tip:** `WebSearchPreviewTool` requires no external resource or connection. For Bing Grounding (which requires a dedicated Bing resource and project connection), see [Bing Grounding reference](../../foundry-agent/create/references/tool-bing-grounding.md). +> 💡 **Tip:** `WebSearchPreviewTool` requires no external resource or connection. For Bing Grounding (which requires a dedicated Bing resource and project connection), see [Bing Grounding reference](../../foundry-agent/create/references/tools/prompt-agent/tool-bing-grounding.md). ### Interacting with Agents