Common reusable Go modules for building Azure Developer CLI (azd) extensions and tooling.
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
- Copilot Skill Installation: Version-aware installation of agentskills.io SKILL.md files
- Browser Launching: Secure cross-platform URL opening
- Security Validation: Path traversal prevention, input sanitization, permission checks
- Extension Manifests: Checks that catch extension.yaml keys azd silently ignores
go get github.com/jongio/azd-coreOr add specific packages to your go.mod:
go get github.com/jongio/azd-core/auth
go get github.com/jongio/azd-core/browser
go get github.com/jongio/azd-core/cache
go get github.com/jongio/azd-core/cliout
go get github.com/jongio/azd-core/cmdutil
go get github.com/jongio/azd-core/copilotskills
go get github.com/jongio/azd-core/editor
go get github.com/jongio/azd-core/env
go get github.com/jongio/azd-core/fileutil
go get github.com/jongio/azd-core/healthcheck
go get github.com/jongio/azd-core/httpclient
go get github.com/jongio/azd-core/keyvault
go get github.com/jongio/azd-core/logutil
go get github.com/jongio/azd-core/manifest
go get github.com/jongio/azd-core/notify
go get github.com/jongio/azd-core/pathutil
go get github.com/jongio/azd-core/progress
go get github.com/jongio/azd-core/projecttype
go get github.com/jongio/azd-core/registry
go get github.com/jongio/azd-core/security
go get github.com/jongio/azd-core/testutil
go get github.com/jongio/azd-core/urlutil
go get github.com/jongio/azd-core/version
go get github.com/jongio/azd-core/yamlutilFull API documentation is available at pkg.go.dev/github.com/jongio/azd-core.
Extension Development:
- Extension Patterns Guide - Comprehensive patterns and best practices for building azd extensions
Migration Guides:
- Migrating to azd-core v0.6.0 - The
azdextSDK rebase: removed packages, changed signatures, and behavior changes - URL Validation and Environment Patterns Migration - Migrate from custom validation to azd-core utilities
URL validation and parsing utilities with RFC-compliant validation.
Key Functions:
Validate- Comprehensive HTTP/HTTPS URL validation usingnet/url.ParseValidateHTTPSOnly- Enforce HTTPS-only for production (allows localhost HTTP)Parse- Parse and normalize URLs with validationNormalizeScheme- 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.Parsefor 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"Common testing utilities for writing reliable tests in azd extensions.
Key Functions:
CaptureOutput- Capture stdout during function execution for testing CLI commandsFindTestData- Locate test fixture directories with flexible path searchingTempDir- 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
}Structured CLI output formatting with cross-platform terminal support and multiple output formats.
Key Functions:
Success/Error/Warning/Info- Colored status messages with iconsHeader/Section- Formatted section headersTable- Simple table rendering, delegated toazdext.Output(honors JSON mode)ProgressBar- Visual progress indicatorsConfirm- Interactive yes/no prompts. Declines automatically when prompting is impossible (redirected stdin or stdout,AZD_NO_PROMPT, CI, AI agent host); assumes yes in JSON modePrint- Hybrid output (JSON or formatted text)
Color: enabled only when azdext.DetectInteractive().CanColorize() reports the terminal can support it, which honors FORCE_COLOR=1 first, then any non-empty NO_COLOR, then whether stdout is a terminal. ForceColor() and NoColor() override the detection.
Output Formats:
FormatDefault- Human-readable text with ANSI colors and Unicode symbolsFormatJSON- 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 skippedEnvironment variable utilities for converting between maps and slices, resolving references, and applying transformations.
Key Functions:
ResolveMap/ResolveSlice- Resolve Key Vault references in environment variablesMapToSlice/SliceToMap- Convert between map and slice formatsHasKeyVaultReferences- Detect Key Vault references in environment dataFilterByPrefix/FilterByPrefixSlice- Filter environment variables by prefix (case-insensitive)ExtractPattern- Extract environment variables matching prefix/suffix with key transformationNormalizeServiceName- 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:
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>]
Reference parsing, client construction, per-vault client caching, and secret
retrieval come from azdext.KeyVaultResolver. This package adds the KEY=VALUE
environment slice API and support for the versioned akvs:// form, which
azdext does not parse on its own.
Features:
NewKeyVaultResolverusesazidentity.DefaultAzureCredentialNewKeyVaultResolverWithCredentialaccepts anazdext.TokenProvider, a sovereign cloud vault suffix, or an injected secret client for tests- Thread-safe per-vault client caching
- Configurable error handling (fail-fast or graceful degradation)
- Vault host allowlist covering the public, China, US Government, Germany, and
Managed HSM endpoints, so a
SecretUricannot point at an arbitrary host - Failures are
*azdext.KeyVaultResolveError, carrying aReasonthat separates a malformed reference from a missing secret, an access denial, or a service error
File system utilities with atomic operations, JSON handling, and secure file detection.
Key Functions:
AtomicWriteJSON/AtomicWriteFile- Write files atomically with retry logicReadJSON- Read JSON with graceful missing file handlingEnsureDir- Create directories with secure permissions (0750)FileExists/FileExistsAny/FilesExistAll- File existence checksHasFileWithExt/HasAnyFileWithExts- Extension-based file detectionContainsText/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
PATH environment variable management and tool discovery utilities.
PATH lookup itself lives in
azdext.LookupTool, which honorsPATHEXTon Windows and therefore resolves.cmdshims such asnpm,pnpm,az, andfunc.pathutilkeeps only the parts the SDK has no equivalent for.
Key Functions:
RefreshPATH- Refresh PATH from system (Windows registry, Unix environment)SearchToolInSystemPath- Search common installation directoriesGetInstallSuggestion- Get installation URLs for 22+ popular tools
Features:
- Cross-platform PATH refresh (Windows PowerShell registry read, Unix environment)
- Common install directory search (Program Files, /usr/local/bin, Homebrew, etc.)
- Installation suggestions for npm, python, docker, azd, and more
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 validationGetTargetDisplayName/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 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 validationValidateScriptName- Reject shell metacharacters and path traversalIsContainerEnvironment- Detect Codespaces, Dev Containers, Docker, KubernetesValidateFilePermissions- 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)
Installs agentskills.io-compliant SKILL.md files from an embedded filesystem to ~/.copilot/skills/{name}/.
Key Functions:
Install- Write embedded skill files to~/.copilot/skills/{name}/with version-based skip logic
Features:
- Version-based skip: reads
.versionfile and skips if it matches (no unnecessary I/O) - Atomic file writes via
fileutil.AtomicWriteFile - Name validation per agentskills.io spec (lowercase, hyphens, digits only)
- Walks embedded
embed.FSunder a configurable root directory
Example:
import "github.com/jongio/azd-core/copilotskills"
//go:embed skills/my-extension
var skillFS embed.FS
func installSkills(version string) error {
return copilotskills.Install("my-extension", version, skillFS, "skills/my-extension")
}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"])
}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/azure/azure-dev/cli/azd/pkg/azdext"
"github.com/jongio/azd-core/pathutil"
)
// Find a tool in PATH
if node := azdext.LookupTool("node"); node.Found {
fmt.Printf("Node.js found at: %s\n", node.Path)
} else {
fmt.Println(pathutil.GetInstallSuggestion("node"))
}
// Search common system directories
if dockerPath := pathutil.SearchToolInSystemPath("docker"); dockerPath != "" {
fmt.Printf("Docker found at: %s\n", dockerPath)
}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)
}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,
})import "github.com/azure/azure-dev/cli/azd/pkg/azdext"
// Check if process is running
if azdext.IsProcessRunning(pid) {
fmt.Println("Process is running")
}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 Developer CLI (
azd auth login) - Azure CLI (
az login) - Azure PowerShell
- Interactive browser authentication
No global state is maintained, and client caching is thread-safe.
The auth package acquires Azure OAuth tokens for arbitrary REST calls.
DetectScope(url) maps a request URL to the OAuth scope its service expects,
returning an empty scope for a host it does not recognize so the request is sent
unauthenticated. Most of the mapping comes from azdext.ScopeDetector, extended
with the services the SDK does not cover. Two services are resolved locally
because a static host to scope map cannot describe them: Azure Data Explorer
needs a scope derived from the cluster host, and Service Bus and Event Hubs share
a DNS suffix and are told apart by the request path.
IsAzureHost(url) is the broader question of whether to authenticate at all. A
host can be recognizably Azure without azd-core knowing its scope.
Token acquisition goes through AzureTokenProvider, which caches per scope,
applies a request timeout, and classifies failures into AuthPermissionError,
AuthCredentialUnavailableError, or AuthError. Three constructors:
NewAzureTokenProvider()builds a resilient credential chain that tries the azd CLI, the Azure CLI, environment variables, workload identity, and managed identity in that order, continuing past a hard failure rather than stopping at the first one the wayDefaultAzureCredentialdoes.NewAzureTokenProviderForHost(ctx, client, opts)usesazdext.TokenProviderwhen an azd host client is supplied, so the tenant comes from the deployment context, and falls back to the chain when it is not.NewAzureTokenProviderWithCredential(cred)wraps anyazcore.TokenCredential.
# 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.outTests are offline-only and use mocks for Azure SDK interactions.
See CONTRIBUTING.md for guidelines on contributing to this project.
See SECURITY.md for information on reporting security vulnerabilities.
This project is licensed under the MIT License. See LICENSE.