|
| 1 | +#!/usr/bin/env pwsh |
| 2 | +<# |
| 3 | +.SYNOPSIS |
| 4 | + Verifies Datadog instrumentation environment variables on an Azure Function App. |
| 5 | +
|
| 6 | +.DESCRIPTION |
| 7 | + Checks that required (and optionally recommended) environment variables are configured |
| 8 | + correctly for Datadog instrumentation. Detects platform (Linux/Windows) and validates |
| 9 | + platform-specific profiler paths. Also checks function app state. |
| 10 | +
|
| 11 | + SECURITY: DD_API_KEY existence is checked but its value is NEVER retrieved or displayed. |
| 12 | +
|
| 13 | +.PARAMETER AppName |
| 14 | + The Azure Function App name (required). |
| 15 | +
|
| 16 | +.PARAMETER ResourceGroup |
| 17 | + The Azure resource group containing the app (required). |
| 18 | +
|
| 19 | +.PARAMETER IncludeRecommended |
| 20 | + Also validate recommended variables (feature disables, etc.). |
| 21 | +
|
| 22 | +.OUTPUTS |
| 23 | + PSCustomObject with AppName, Platform, State, RequiredVars, RecommendedVars, |
| 24 | + AllRequiredPresent, and Issues. |
| 25 | +
|
| 26 | +.EXAMPLE |
| 27 | + ./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "my-func" -ResourceGroup "my-rg" |
| 28 | +
|
| 29 | +.EXAMPLE |
| 30 | + ./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "my-func" -ResourceGroup "my-rg" -IncludeRecommended |
| 31 | +#> |
| 32 | + |
| 33 | +param( |
| 34 | + [Parameter(Mandatory = $true)] |
| 35 | + [string]$AppName, |
| 36 | + |
| 37 | + [Parameter(Mandatory = $true)] |
| 38 | + [string]$ResourceGroup, |
| 39 | + |
| 40 | + [switch]$IncludeRecommended |
| 41 | +) |
| 42 | + |
| 43 | +$ErrorActionPreference = "Stop" |
| 44 | + |
| 45 | +# --- Helper: colored console output --- |
| 46 | +function Write-Result { |
| 47 | + param( |
| 48 | + [string]$Label, |
| 49 | + [string]$Status, # PASS, FAIL, WARN, INFO |
| 50 | + [string]$Detail = "" |
| 51 | + ) |
| 52 | + |
| 53 | + $color = switch ($Status) { |
| 54 | + "PASS" { "Green" } |
| 55 | + "FAIL" { "Red" } |
| 56 | + "WARN" { "Yellow" } |
| 57 | + "INFO" { "Cyan" } |
| 58 | + default { "White" } |
| 59 | + } |
| 60 | + |
| 61 | + $prefix = "[$Status]" |
| 62 | + $message = "$prefix $Label" |
| 63 | + if ($Detail) { $message += " - $Detail" } |
| 64 | + Write-Host $message -ForegroundColor $color |
| 65 | +} |
| 66 | + |
| 67 | +# --- 1. Check function app state and detect platform --- |
| 68 | +Write-Host "" |
| 69 | +Write-Host "=== Checking Function App: $AppName ===" -ForegroundColor Cyan |
| 70 | +Write-Host "" |
| 71 | + |
| 72 | +try { |
| 73 | + $appInfo = az functionapp show ` |
| 74 | + --name $AppName ` |
| 75 | + --resource-group $ResourceGroup ` |
| 76 | + --query "{state:state, kind:kind}" ` |
| 77 | + -o json 2>&1 |
| 78 | + |
| 79 | + if ($LASTEXITCODE -ne 0) { |
| 80 | + Write-Error "Failed to query function app '$AppName' in resource group '$ResourceGroup': $appInfo" |
| 81 | + exit 1 |
| 82 | + } |
| 83 | + |
| 84 | + $appInfo = $appInfo | ConvertFrom-Json |
| 85 | +} |
| 86 | +catch { |
| 87 | + Write-Error "Failed to query function app: $_" |
| 88 | + exit 1 |
| 89 | +} |
| 90 | + |
| 91 | +$appState = $appInfo.state |
| 92 | +$appKind = $appInfo.kind |
| 93 | + |
| 94 | +# Detect platform from "kind" field (e.g., "functionapp,linux" or "functionapp") |
| 95 | +$isLinuxApp = $appKind -match "linux" |
| 96 | +$platform = if ($isLinuxApp) { "linux" } else { "windows" } |
| 97 | + |
| 98 | +Write-Result "App State" $(if ($appState -eq "Running") { "PASS" } else { "FAIL" }) $appState |
| 99 | +Write-Result "Platform" "INFO" "$platform (kind: $appKind)" |
| 100 | +Write-Host "" |
| 101 | + |
| 102 | +# --- 2. Fetch all app settings in one call --- |
| 103 | +# Use --query to get name/value pairs but EXCLUDE DD_API_KEY value |
| 104 | +try { |
| 105 | + $settingsJson = az functionapp config appsettings list ` |
| 106 | + --name $AppName ` |
| 107 | + --resource-group $ResourceGroup ` |
| 108 | + -o json 2>&1 |
| 109 | + |
| 110 | + if ($LASTEXITCODE -ne 0) { |
| 111 | + Write-Error "Failed to fetch app settings: $settingsJson" |
| 112 | + exit 1 |
| 113 | + } |
| 114 | + |
| 115 | + $allSettings = $settingsJson | ConvertFrom-Json |
| 116 | +} |
| 117 | +catch { |
| 118 | + Write-Error "Failed to fetch app settings: $_" |
| 119 | + exit 1 |
| 120 | +} |
| 121 | + |
| 122 | +# Build a hashtable for quick lookup, but redact DD_API_KEY |
| 123 | +$settingsMap = @{} |
| 124 | +foreach ($s in $allSettings) { |
| 125 | + if ($s.name -eq "DD_API_KEY") { |
| 126 | + $settingsMap[$s.name] = "(set)" |
| 127 | + } |
| 128 | + else { |
| 129 | + $settingsMap[$s.name] = $s.value |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +# --- 3. Define required and recommended variables --- |
| 134 | + |
| 135 | +# Common required vars (platform-independent) |
| 136 | +$requiredVars = [ordered]@{ |
| 137 | + "CORECLR_ENABLE_PROFILING" = @{ ExpectedValue = "1"; Description = "Enables CLR profiling API" } |
| 138 | + "CORECLR_PROFILER" = @{ ExpectedValue = "{846F5F1C-F9AE-4B07-969E-05C26BC060D8}"; Description = "Datadog profiler GUID" } |
| 139 | + "DD_DOTNET_TRACER_HOME" = @{ ExpectedValue = $null; Description = "Tracer managed assemblies directory" } |
| 140 | + "DD_API_KEY" = @{ ExpectedValue = $null; Description = "Datadog API key" } |
| 141 | + "DOTNET_STARTUP_HOOKS" = @{ ExpectedValue = $null; Description = "Serverless compat startup hook" } |
| 142 | +} |
| 143 | + |
| 144 | +# Platform-specific profiler path vars |
| 145 | +if ($isLinuxApp) { |
| 146 | + $requiredVars["CORECLR_PROFILER_PATH"] = @{ |
| 147 | + ExpectedValue = "/home/site/wwwroot/datadog/linux-x64/Datadog.Trace.ClrProfiler.Native.so" |
| 148 | + Description = "Native profiler path (Linux)" |
| 149 | + } |
| 150 | +} |
| 151 | +else { |
| 152 | + $requiredVars["CORECLR_PROFILER_PATH_32"] = @{ |
| 153 | + ExpectedValue = 'C:\home\site\wwwroot\datadog\win-x86\Datadog.Trace.ClrProfiler.Native.dll' |
| 154 | + Description = "Native profiler path (Windows 32-bit)" |
| 155 | + } |
| 156 | + $requiredVars["CORECLR_PROFILER_PATH_64"] = @{ |
| 157 | + ExpectedValue = 'C:\home\site\wwwroot\datadog\win-x64\Datadog.Trace.ClrProfiler.Native.dll' |
| 158 | + Description = "Native profiler path (Windows 64-bit)" |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +$recommendedVars = [ordered]@{ |
| 163 | + "DD_APPSEC_ENABLED" = @{ ExpectedValue = "false"; Description = "Disable AppSec (unsupported in Functions)" } |
| 164 | + "DD_CIVISIBILITY_ENABLED" = @{ ExpectedValue = "false"; Description = "Disable CI Visibility (unsupported in Functions)" } |
| 165 | + "DD_REMOTE_CONFIGURATION_ENABLED" = @{ ExpectedValue = "false"; Description = "Disable Remote Configuration" } |
| 166 | + "DD_AGENT_FEATURE_POLLING_ENABLED" = @{ ExpectedValue = "false"; Description = "Disable Agent feature polling" } |
| 167 | + "DD_TRACE_Process_ENABLED" = @{ ExpectedValue = "false"; Description = "Disable Process instrumentation (reduces noise)" } |
| 168 | +} |
| 169 | + |
| 170 | +# --- 4. Validate required variables --- |
| 171 | +Write-Host "--- Required Variables ---" -ForegroundColor Cyan |
| 172 | + |
| 173 | +$issues = @() |
| 174 | +$requiredResults = [ordered]@{} |
| 175 | + |
| 176 | +foreach ($varName in $requiredVars.Keys) { |
| 177 | + $spec = $requiredVars[$varName] |
| 178 | + $actualValue = $settingsMap[$varName] |
| 179 | + |
| 180 | + if ($null -eq $actualValue -or $actualValue -eq "") { |
| 181 | + Write-Result $varName "FAIL" "NOT SET - $($spec.Description)" |
| 182 | + $requiredResults[$varName] = @{ Status = "FAIL"; Value = "(not set)"; Expected = $spec.ExpectedValue } |
| 183 | + $issues += "$varName is not set" |
| 184 | + } |
| 185 | + elseif ($varName -eq "DD_API_KEY") { |
| 186 | + # Never compare value, just confirm existence |
| 187 | + Write-Result $varName "PASS" "(set)" |
| 188 | + $requiredResults[$varName] = @{ Status = "PASS"; Value = "(set)"; Expected = $null } |
| 189 | + } |
| 190 | + elseif ($null -ne $spec.ExpectedValue -and $actualValue -ne $spec.ExpectedValue) { |
| 191 | + Write-Result $varName "FAIL" "Expected '$($spec.ExpectedValue)', got '$actualValue'" |
| 192 | + $requiredResults[$varName] = @{ Status = "FAIL"; Value = $actualValue; Expected = $spec.ExpectedValue } |
| 193 | + $issues += "$varName has incorrect value (expected '$($spec.ExpectedValue)', got '$actualValue')" |
| 194 | + } |
| 195 | + else { |
| 196 | + $displayValue = if ($spec.ExpectedValue) { $actualValue } else { $actualValue } |
| 197 | + Write-Result $varName "PASS" $displayValue |
| 198 | + $requiredResults[$varName] = @{ Status = "PASS"; Value = $actualValue; Expected = $spec.ExpectedValue } |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +$allRequiredPresent = ($issues.Count -eq 0) |
| 203 | + |
| 204 | +# --- 5. Validate recommended variables (if requested) --- |
| 205 | +$recommendedResults = [ordered]@{} |
| 206 | + |
| 207 | +if ($IncludeRecommended) { |
| 208 | + Write-Host "" |
| 209 | + Write-Host "--- Recommended Variables ---" -ForegroundColor Cyan |
| 210 | + |
| 211 | + foreach ($varName in $recommendedVars.Keys) { |
| 212 | + $spec = $recommendedVars[$varName] |
| 213 | + $actualValue = $settingsMap[$varName] |
| 214 | + |
| 215 | + if ($null -eq $actualValue -or $actualValue -eq "") { |
| 216 | + Write-Result $varName "WARN" "NOT SET - $($spec.Description)" |
| 217 | + $recommendedResults[$varName] = @{ Status = "WARN"; Value = "(not set)"; Expected = $spec.ExpectedValue } |
| 218 | + } |
| 219 | + elseif ($null -ne $spec.ExpectedValue -and $actualValue -ne $spec.ExpectedValue) { |
| 220 | + Write-Result $varName "WARN" "Expected '$($spec.ExpectedValue)', got '$actualValue'" |
| 221 | + $recommendedResults[$varName] = @{ Status = "WARN"; Value = $actualValue; Expected = $spec.ExpectedValue } |
| 222 | + } |
| 223 | + else { |
| 224 | + Write-Result $varName "PASS" $actualValue |
| 225 | + $recommendedResults[$varName] = @{ Status = "PASS"; Value = $actualValue; Expected = $spec.ExpectedValue } |
| 226 | + } |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +# --- 6. Summary --- |
| 231 | +Write-Host "" |
| 232 | +if ($appState -ne "Running") { |
| 233 | + Write-Host "WARNING: Function app is '$appState' (not Running). Start it before testing." -ForegroundColor Yellow |
| 234 | + $issues += "Function app state is '$appState'" |
| 235 | +} |
| 236 | + |
| 237 | +if ($allRequiredPresent) { |
| 238 | + Write-Host "All required variables are configured correctly." -ForegroundColor Green |
| 239 | +} |
| 240 | +else { |
| 241 | + Write-Host "ISSUES FOUND: $($issues.Count) problem(s) detected." -ForegroundColor Red |
| 242 | + foreach ($issue in $issues) { |
| 243 | + Write-Host " - $issue" -ForegroundColor Red |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +Write-Host "" |
| 248 | + |
| 249 | +# --- 7. Return structured result --- |
| 250 | +return [PSCustomObject]@{ |
| 251 | + AppName = $AppName |
| 252 | + Platform = $platform |
| 253 | + State = $appState |
| 254 | + RequiredVars = $requiredResults |
| 255 | + RecommendedVars = $recommendedResults |
| 256 | + AllRequiredPresent = $allRequiredPresent |
| 257 | + Issues = $issues |
| 258 | +} |
0 commit comments