Skip to content

Commit b3e14a6

Browse files
committed
Add env var verification script for Azure Functions
🤖 Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent 9f446ae commit b3e14a6

4 files changed

Lines changed: 328 additions & 13 deletions

File tree

.claude/skills/azure-functions/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Invoke the skill with `/azure-functions` followed by an optional command:
6161
Utility scripts included with this skill:
6262

6363
- **Find-NuGetConfig.ps1** - Search for `nuget.config` file by walking up directory hierarchy. Used to verify sample apps have access to local NuGet feed before deployment.
64+
- **Test-EnvVars.ps1** - Verify Datadog instrumentation environment variables on an Azure Function App. Checks app state, detects platform, validates required/recommended vars. Never displays DD_API_KEY value.
6465

6566
## Related Scripts (tracer/tools/)
6667

.claude/skills/azure-functions/SKILL.md

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ The sample app should use a floating version like `3.38.0-dev.*` in its package
115115

116116
### 2. Deploy Function
117117

118-
**IMPORTANT**: Before deploying, verify that a `nuget.config` file exists in the sample app directory or a parent directory. This file is required for `dotnet restore` to resolve the locally-built `Datadog.AzureFunctions` package from the local NuGet feed.
118+
**IMPORTANT**: Before deploying, verify prerequisites:
119119

120120
**Verify nuget.config exists**:
121121
```powershell
@@ -127,6 +127,15 @@ if (-not $nugetConfig) {
127127
Write-Host "Found nuget.config at: $nugetConfig"
128128
```
129129

130+
**Verify environment variables are configured**:
131+
```powershell
132+
$envCheck = ./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>"
133+
if (-not $envCheck.AllRequiredPresent) {
134+
Write-Error "Required environment variables are missing. Run '/azure-functions configure' first."
135+
exit 1
136+
}
137+
```
138+
130139
Use the `Deploy-AzureFunction.ps1` script to automate deployment, wait, and trigger:
131140

132141
```powershell
@@ -312,10 +321,18 @@ After deployment and testing:
312321
**General guidance**: For environment variable configuration issues, see [environment-variables.md](environment-variables.md) for complete reference on required, recommended, and debugging variables.
313322

314323
### Function Not Responding
315-
```bash
316-
# Check deployment status
317-
az functionapp show --name <app-name> --resource-group <resource-group>
318324

325+
**First, check if the app is running**:
326+
```powershell
327+
$envCheck = ./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>"
328+
if ($envCheck.State -ne "Running") {
329+
Write-Host "App is '$($envCheck.State)' — starting it..."
330+
az functionapp start --name <app-name> --resource-group <resource-group>
331+
}
332+
```
333+
334+
If the app is running but not responding:
335+
```bash
319336
# Restart function app
320337
az functionapp restart --name <app-name> --resource-group <resource-group>
321338
```
@@ -331,18 +348,18 @@ grep "Assembly metadata" LogFiles/datadog/dotnet-tracer-managed-dotnet-*.log
331348
```
332349

333350
### Traces Not Appearing in Datadog
334-
```bash
335-
# Verify DD_API_KEY is set (check existence only, never retrieve value)
336-
az functionapp config appsettings list \
337-
--name <app-name> \
338-
--resource-group <resource-group> \
339-
--query "[?name=='DD_API_KEY'].name" -o tsv
340351

341-
# Check worker initialization in logs
352+
**Verify all required environment variables** (including DD_API_KEY, profiler paths, etc.):
353+
```powershell
354+
./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>" -IncludeRecommended
355+
```
356+
357+
If all env vars pass, check worker initialization in logs:
358+
```bash
342359
grep "Datadog Tracer initialized" LogFiles/datadog/dotnet-tracer-managed-dotnet-*.log
343360
```
344361

345-
**Environment variables**: Verify all required environment variables are configured correctly. See [environment-variables.md](environment-variables.md) for complete reference.
362+
**Complete reference**: See [environment-variables.md](environment-variables.md) for all available variables.
346363

347364
### Separate Traces (Parenting Issue)
348365
1. Get trace ID from host logs at execution timestamp
@@ -458,7 +475,7 @@ When invoked with `/azure-functions configure [app-name]`:
458475
If invoked without arguments (`/azure-functions`), guide the user through:
459476

460477
1. **Understand the goal**: What are they testing? (New feature, bug fix, trace verification, initial setup)
461-
2. **Check configuration**: Ask if environment variables are configured (offer to run `/azure-functions configure`)
478+
2. **Check configuration**: Run `Test-EnvVars.ps1` to verify environment variables. If issues are found, offer to run `/azure-functions configure`
462479
3. **Verify .csproj**: Check that `Datadog.AzureFunctions.csproj` uses PackageReference (not ProjectReference) for local testing (see step 1 above)
463480
4. **Build**: Run Build-AzureFunctionsNuget.ps1
464481
5. **Select app**: Which test app to deploy to?
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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+
}

.claude/skills/azure-functions/scripts-reference.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,45 @@ Quick reference scripts and commands for Azure Functions development workflow.
1111

1212
**Note**: These scripts use PowerShell-specific cmdlets (e.g., `Expand-Archive`, `Invoke-RestMethod`) that cannot be easily replicated in bash. Always prefer `pwsh` over `powershell.exe` when both are available.
1313

14+
### Test-EnvVars.ps1
15+
16+
Verifies Datadog instrumentation environment variables on an Azure Function App.
17+
18+
**Location**: `.claude/skills/azure-functions/Test-EnvVars.ps1`
19+
20+
**Basic usage**:
21+
```powershell
22+
# Check required variables only
23+
./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>"
24+
25+
# Include recommended variables
26+
./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>" -IncludeRecommended
27+
```
28+
29+
**Parameters**:
30+
- `-AppName` (required) - Azure Function App name
31+
- `-ResourceGroup` (required) - Azure resource group
32+
- `-IncludeRecommended` (switch) - Also validate recommended variables (feature disables, etc.)
33+
34+
**What it checks**:
35+
1. Function app state (Running, Stopped, etc.)
36+
2. Platform detection (Linux vs Windows)
37+
3. Required variables: `CORECLR_ENABLE_PROFILING`, `CORECLR_PROFILER`, `DD_DOTNET_TRACER_HOME`, `DD_API_KEY`, `DOTNET_STARTUP_HOOKS`, plus platform-specific profiler path(s)
38+
4. Recommended variables (with `-IncludeRecommended`): `DD_APPSEC_ENABLED`, `DD_CIVISIBILITY_ENABLED`, `DD_REMOTE_CONFIGURATION_ENABLED`, `DD_AGENT_FEATURE_POLLING_ENABLED`, `DD_TRACE_Process_ENABLED`
39+
40+
**Output**: `[PSCustomObject]` with `AppName`, `Platform`, `State`, `RequiredVars`, `RecommendedVars`, `AllRequiredPresent`, `Issues`
41+
42+
**Security**: `DD_API_KEY` existence is checked but its value is **never** retrieved or displayed.
43+
44+
**Pipeline usage** (pre-deployment check):
45+
```powershell
46+
$envCheck = ./.claude/skills/azure-functions/Test-EnvVars.ps1 -AppName "<app-name>" -ResourceGroup "<resource-group>"
47+
if (-not $envCheck.AllRequiredPresent) {
48+
Write-Error "Required environment variables are missing — configure them first"
49+
exit 1
50+
}
51+
```
52+
1453
### Find-NuGetConfig.ps1
1554

1655
Searches for `nuget.config` file by walking up the directory hierarchy from a starting path.

0 commit comments

Comments
 (0)