azd-core

Common reusable Go modules for building Azure Developer CLI (azd) extensions and tooling.
Overview
azd-core provides shared utilities extracted from the Azure Developer CLI to support building azd extensions, custom CLI tools, and automation scripts. The goal is to enable developers to create azd-compatible tools without duplicating common logic or pulling in the entire azd runtime.
This library includes:
- URL Validation: RFC-compliant HTTP/HTTPS URL validation and parsing
- Environment Management: Environment variable resolution, pattern extraction, and Key Vault integration
- File System Utilities: Atomic writes, JSON handling, secure file operations
- Path Management: Tool discovery, PATH manipulation, installation suggestions
- Process Utilities: Cross-platform process detection and management
- Shell Detection: Script type detection from extensions, shebangs, and OS defaults
- Browser Launching: Secure cross-platform URL opening
- Security Validation: Path traversal prevention, input sanitization, permission checks
Installation
go get github.com/jongio/azd-core
Or add specific packages to your go.mod:
go get github.com/jongio/azd-core/urlutil
go get github.com/jongio/azd-core/testutil
go get github.com/jongio/azd-core/cliout
go get github.com/jongio/azd-core/env
go get github.com/jongio/azd-core/keyvault
go get github.com/jongio/azd-core/fileutil
go get github.com/jongio/azd-core/pathutil
go get github.com/jongio/azd-core/browser
go get github.com/jongio/azd-core/security
go get github.com/jongio/azd-core/procutil
go get github.com/jongio/azd-core/shellutil
Documentation
Full API documentation is available at pkg.go.dev/github.com/jongio/azd-core.
Extension Development:
Migration Guides:
Packages
urlutil
URL validation and parsing utilities with RFC-compliant validation.
Key Functions:
Validate - Comprehensive HTTP/HTTPS URL validation using net/url.Parse
ValidateHTTPSOnly - Enforce HTTPS-only for production (allows localhost HTTP)
Parse - Parse and normalize URLs with validation
NormalizeScheme - Ensure URL has http:// or https:// prefix
Validation Rules:
- Protocol must be http:// or https:// (rejects ftp://, file://, javascript://, etc.)
- URL must have a valid host/domain (rejects "http://", "https://")
- URL must not exceed 2048 characters (RFC 2616 practical limit)
- Uses
net/url.Parse for RFC 3986 compliant parsing
- Whitespace is trimmed before validation
Security Features:
- Prevents protocol injection (javascript:, file:, data: URLs)
- Validates host presence to prevent malformed URLs
- Length limits prevent DoS via extremely long URLs
- HTTPS enforcement for production with localhost exception
Example:
import "github.com/jongio/azd-core/urlutil"
// Validate custom URL from configuration
if err := urlutil.Validate(customURL); err != nil {
return fmt.Errorf("invalid custom URL: %w", err)
}
// Enforce HTTPS for production endpoints (allows localhost HTTP)
if err := urlutil.ValidateHTTPSOnly(apiEndpoint); err != nil {
return fmt.Errorf("production endpoint must use HTTPS: %w", err)
}
// Parse and normalize URL
parsed, err := urlutil.Parse(userProvidedURL)
if err != nil {
return err
}
fmt.Printf("Accessing: %s://%s\n", parsed.Scheme, parsed.Host)
// Add default scheme if missing
normalized := urlutil.NormalizeScheme("example.com", "https")
// Returns: "https://example.com"
testutil
Common testing utilities for writing reliable tests in azd extensions.
Key Functions:
CaptureOutput - Capture stdout during function execution for testing CLI commands
FindTestData - Locate test fixture directories with flexible path searching
TempDir - Create temporary directories with automatic cleanup via t.Cleanup()
Contains - Convenience helper for string containment checks
Features:
- Proper test line reporting via t.Helper() in all functions
- Automatic cleanup of temporary resources
- Cross-platform path handling
- Reliable stdout capture with goroutine-based reading
Example:
import "github.com/jongio/azd-core/testutil"
func TestCLICommand(t *testing.T) {
// Capture command output
output := testutil.CaptureOutput(t, func() error {
return runCommand()
})
if !testutil.Contains(output, "success") {
t.Error("expected success message")
}
}
func TestWithFixtures(t *testing.T) {
// Find test data directory
fixturesDir := testutil.FindTestData(t, "tests", "fixtures")
// Create temporary directory for outputs
tmpDir := testutil.TempDir(t)
// Automatically cleaned up after test
}
cliout
Structured CLI output formatting with cross-platform terminal support and multiple output formats.
Key Functions:
Success / Error / Warning / Info - Colored status messages with icons
Header / Section - Formatted section headers
Table - Simple table rendering with automatic column width calculation
ProgressBar - Visual progress indicators
Confirm - Interactive yes/no prompts (non-interactive in JSON mode)
Print - Hybrid output (JSON or formatted text)
Output Formats:
FormatDefault - Human-readable text with ANSI colors and Unicode symbols
FormatJSON - Structured JSON for automation and scripting
Example:
import "github.com/jongio/azd-core/cliout"
// Set output format
if err := cliout.SetFormat("json"); err != nil {
log.Fatal(err)
}
// Print status messages
cliout.Success("Deployment completed successfully")
cliout.Error("Failed to connect: %s", err)
cliout.Warning("This feature is deprecated")
cliout.Info("Processing %d items", count)
// Create tables
headers := []string{"Name", "Status", "Port"}
rows := []cliout.TableRow{
{"Name": "web", "Status": "running", "Port": "8080"},
{"Name": "api", "Status": "stopped", "Port": "3000"},
}
cliout.Table(headers, rows)
// Hybrid output (JSON mode or formatted)
data := map[string]interface{}{"status": "success", "count": 42}
cliout.Print(data, func() {
cliout.Success("Processed %d items", 42)
})
// Interactive prompts
if cliout.Confirm("Do you want to continue?") {
// User confirmed (always true in JSON mode)
}
// Orchestration mode for subcommands
cliout.SetOrchestrated(true)
// Now CommandHeader() calls are skipped
env
Environment variable utilities for converting between maps and slices, resolving references, and applying transformations.
Key Functions:
ResolveMap / ResolveSlice - Resolve Key Vault references in environment variables
MapToSlice / SliceToMap - Convert between map and slice formats
HasKeyVaultReferences - Detect Key Vault references in environment data
FilterByPrefix / FilterByPrefixSlice - Filter environment variables by prefix (case-insensitive)
ExtractPattern - Extract environment variables matching prefix/suffix with key transformation
NormalizeServiceName - Convert environment variable naming to service naming (MY_API → my-api)
Pattern Extraction Features:
- Case-insensitive prefix/suffix matching
- Optional prefix/suffix trimming from result keys
- Custom key transformation functions
- Value validation with callback functions
- Useful for extracting service URLs, Azure variables, custom domain configs
Example:
import "github.com/jongio/azd-core/env"
// Filter by prefix (case-insensitive)
envVars := map[string]string{
"AZURE_TENANT_ID": "xyz",
"AZURE_CLIENT_ID": "abc",
"DATABASE_URL": "postgres://...",
}
azureVars := env.FilterByPrefix(envVars, "AZURE_")
// Returns: {"AZURE_TENANT_ID": "xyz", "AZURE_CLIENT_ID": "abc"}
// Extract service URLs with normalization
serviceEnv := map[string]string{
"SERVICE_MY_API_URL": "https://api.example.com",
"SERVICE_WEB_APP_URL": "https://web.example.com",
"SERVICE_DB_HOST": "db.example.com",
}
urls, _ := env.ExtractPattern(serviceEnv, env.PatternOptions{
Prefix: "SERVICE_",
Suffix: "_URL",
TrimPrefix: true,
TrimSuffix: true,
KeyTransform: env.NormalizeServiceName, // MY_API → my-api
})
// Returns: {"my-api": "https://api.example.com", "web-app": "https://web.example.com"}
Key Vault Resolution:
keyvault
Azure Key Vault reference detection and resolution for environment variables.
Supported Formats:
@Microsoft.KeyVault(SecretUri=https://...)
@Microsoft.KeyVault(VaultName=...;SecretName=...;SecretVersion=...)
akvs://<subscription-id>/<vault-name>/<secret-name>[/<version>]
Features:
- Uses
azidentity.DefaultAzureCredential for authentication
- Thread-safe client caching
- Configurable error handling (fail-fast or graceful degradation)
- SSRF protection and validation
fileutil
File system utilities with atomic operations, JSON handling, and secure file detection.
Key Functions:
AtomicWriteJSON / AtomicWriteFile - Write files atomically with retry logic
ReadJSON - Read JSON with graceful missing file handling
EnsureDir - Create directories with secure permissions (0750)
FileExists / FileExistsAny / FilesExistAll - File existence checks
HasFileWithExt / HasAnyFileWithExts - Extension-based file detection
ContainsText / ContainsTextInFile - Search file contents
Features:
- Atomic writes prevent partial/corrupt files
- Retry logic for transient filesystem errors
- Secure permissions (directories: 0750, files: 0644)
- Path traversal protection via
security.ValidatePath
pathutil
PATH environment variable management and tool discovery utilities.
Key Functions:
RefreshPATH - Refresh PATH from system (Windows registry, Unix environment)
FindToolInPath - Search PATH for executables (auto .exe handling on Windows)
SearchToolInSystemPath - Search common installation directories
GetInstallSuggestion - Get installation URLs for 22+ popular tools
Features:
- Cross-platform PATH refresh (Windows PowerShell registry read, Unix environment)
- Automatic .exe extension handling on Windows
- Common install directory search (Program Files, /usr/local/bin, Homebrew, etc.)
- Installation suggestions for npm, python, docker, azd, and more
browser
Cross-platform browser launching with URL validation and timeout support.
Key Functions:
Launch - Open URL in system default browser (non-blocking)
ResolveTarget - Resolve browser target (default, system, none)
ValidTargets / IsValid - Target validation
GetTargetDisplayName / FormatValidTargets - Display formatting
Features:
- Cross-platform support (Windows cmd/start, macOS open, Linux xdg-open)
- URL validation (http/https only for security)
- Non-blocking launch with configurable timeout
- Context-based cancellation
- Graceful error handling (warnings only, non-critical)
security
Security validation utilities for path traversal prevention, input sanitization, and permission checks.
Key Functions:
ValidatePath - Prevent path traversal attacks (detects .., resolves symlinks)
ValidateServiceName - Validate service names (DNS-safe, container-safe)
ValidatePackageManager - Allowlist-based package manager validation
SanitizeScriptName - Detect shell metacharacters
IsContainerEnvironment - Detect Codespaces, Dev Containers, Docker, Kubernetes
ValidateFilePermissions - Detect world-writable files (Unix only)
Features:
- Path traversal attack prevention
- Symbolic link resolution and validation
- Service name validation (alphanumeric start, DNS label limits)
- Shell metacharacter detection
- Container environment detection
- World-writable file detection (security warning)
procutil
Cross-platform process detection utilities using gopsutil for reliable cross-platform behavior.
Key Functions:
IsProcessRunning - Check if process with given PID is running
Features:
- Cross-platform support (Windows, Linux, macOS, BSD, Solaris, AIX)
- Reliable Windows process detection (no stale PID issues)
- Uses platform-native APIs (Windows: OpenProcess, Linux: /proc, macOS: sysctl)
- Powered by github.com/shirou/gopsutil/v4
- Uses Signal(0) on Unix for accurate detection
- Windows fallback with documented limitations (stale PID may return true)
- Invalid PID handling (≤0 returns false)
shellutil
Shell detection from file extensions, shebangs, and OS defaults.
Key Functions:
DetectShell - Auto-detect shell from extension, shebang, or OS default
ReadShebang - Parse shebang line to extract interpreter
Shell Constants:
ShellBash, ShellSh, ShellZsh - Unix shells
ShellPwsh, ShellPowerShell - PowerShell variants
ShellCmd - Windows Command Prompt
Features:
- Extension detection (.ps1 → pwsh, .sh → bash, .cmd → cmd, etc.)
- Shebang parsing (#!/bin/bash, #!/usr/bin/env python3, etc.)
- OS-specific defaults (Windows: cmd, Unix: bash)
- Graceful error handling (falls back to OS default)
Usage Examples
Resolve Key Vault References in Environment
package main
import (
"context"
"os"
"github.com/jongio/azd-core/env"
"github.com/jongio/azd-core/keyvault"
)
func main() {
// Create resolver
resolver, err := keyvault.NewKeyVaultResolver()
if err != nil {
panic(err)
}
// Resolve from environment map
envMap := map[string]string{
"DATABASE_PASSWORD": "@Microsoft.KeyVault(VaultName=myvault;SecretName=db-pass)",
"API_ENDPOINT": "https://api.example.com",
}
resolved, warnings, err := env.ResolveMap(
context.Background(),
envMap,
resolver,
keyvault.ResolveEnvironmentOptions{},
)
if err != nil {
panic(err)
}
// Handle warnings
for _, w := range warnings {
os.Stderr.WriteString("warning: " + w.Err.Error() + "\n")
}
// Use resolved environment
os.Setenv("DATABASE_PASSWORD", resolved["DATABASE_PASSWORD"])
}
Atomic File Writing
import "github.com/jongio/azd-core/fileutil"
// Write JSON atomically (prevents partial/corrupt files)
data := map[string]interface{}{
"version": "1.0",
"config": map[string]string{"key": "value"},
}
err := fileutil.AtomicWriteJSON("config.json", data)
import (
"fmt"
"github.com/jongio/azd-core/pathutil"
)
// Find a tool in PATH
if nodePath := pathutil.FindToolInPath("node"); nodePath != "" {
fmt.Printf("Node.js found at: %s\n", nodePath)
} else {
fmt.Println(pathutil.GetInstallSuggestion("node"))
}
// Search common system directories
if dockerPath := pathutil.SearchToolInSystemPath("docker"); dockerPath != "" {
fmt.Printf("Docker found at: %s\n", dockerPath)
}
Secure Path Validation
import "github.com/jongio/azd-core/security"
// Validate user-provided path (prevents path traversal)
if err := security.ValidatePath(userPath); err != nil {
return fmt.Errorf("invalid path: %w", err)
}
// Validate service name (DNS-safe, container-safe)
if err := security.ValidateServiceName(name, false); err != nil {
return fmt.Errorf("invalid service name: %w", err)
}
Shell Detection
import "github.com/jongio/azd-core/shellutil"
// Auto-detect shell from script
shell := shellutil.DetectShell("deploy.sh") // Returns "bash"
shell = shellutil.DetectShell("setup.ps1") // Returns "pwsh" or "powershell"
shell = shellutil.DetectShell("build.cmd") // Returns "cmd"
// Read shebang to detect interpreter
if shebang := shellutil.ReadShebang("script.py"); shebang != "" {
fmt.Printf("Interpreter: %s\n", shebang) // "python3"
}
Browser Launch
import (
"github.com/jongio/azd-core/browser"
"time"
)
// Open URL in default browser
err := browser.Launch(browser.LaunchOptions{
URL: "https://example.com",
Target: browser.TargetDefault,
Timeout: 5 * time.Second,
})
Process Detection
import "github.com/jongio/azd-core/procutil"
// Check if process is running
if procutil.IsProcessRunning(pid) {
fmt.Println("Process is running")
}
Authentication
The keyvault package uses azidentity.DefaultAzureCredential, supporting:
- Environment variables (
AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
- Managed identity (Azure VM, App Service, Container Apps, etc.)
- Azure CLI (
az login)
- Azure PowerShell
- Interactive browser authentication
No global state is maintained, and client caching is thread-safe.
Testing
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
Tests are offline-only and use mocks for Azure SDK interactions.
Contributing
See CONTRIBUTING.md for guidelines on contributing to this project.
Security
See SECURITY.md for information on reporting security vulnerabilities.
License
This project is licensed under the MIT License. See LICENSE.