Skip to content

Script provisioning provider — full provider implementation #7736

Description

@wbreza

Script provisioning provider -- full provider implementation

Parent epic: #7733
Framework: #7465 | Depends on: #7734 (scaffold), #7735 (config parsing)

Overview

Implement all azdext.ProvisioningProvider lifecycle methods with real behavior, including the three core components: EnvResolver, ScriptExecutor, and OutputCollector.

Core Components

1. EnvResolver (internal/provisioning/env_resolver.go)

Resolves environment variables for each script using the 4-layer priority model:

Layer 4 (highest): secrets: map values (Key Vault refs, prompted values)
Layer 3:           env: map values (${EXPRESSION} substitution)
Layer 2:           azd environment values (.env + prior script outputs)
Layer 1 (lowest):  OS environment variables

Key behaviors:

  • ${EXPRESSION} substitution via osutil.ExpandableString (or drone/envsubst)
  • Missing variable detection with actionable error messages (azd env set guidance)
  • Secret resolution: akvs:// Key Vault refs -> azd env lookup -> interactive prompt -> persist to env
  • Prior script outputs flow as env vars to subsequent scripts

2. ScriptExecutor (internal/provisioning/executor.go)

Runs individual shell scripts with a prepared environment:

  • Shell invocation: bash <script> or pwsh -NoProfile -NonInteractive -File <script>
  • Working directory: project root (directory containing azure.yaml)
  • Streams stdout/stderr to console in real-time
  • Captures exit code; non-zero fails unless continueOnError: true
  • Path containment check before execution (no escaping project root)
type ScriptResult struct {
    ExitCode int
    Stdout   string
    Stderr   string
}

3. OutputCollector (internal/provisioning/output.go)

Discovers and parses outputs.json files after each script:

  • Search strategy: walk up from script directory toward project root, stop at first outputs.json
  • Format: { "outputs": { "KEY": { "type": "string", "value": "..." } } }
  • Merge: outputs accumulate across scripts (last-write-wins for duplicate keys)
  • Missing file: not an error (scripts may have no outputs)

Lifecycle Method Implementations

Initialize(ctx, projectPath, options) error

EnsureEnv(ctx) (*EnsureEnvResult, error)

  • If AZURE_SUBSCRIPTION_ID missing -> show subscription picker via AzdClient.Account().GetSubscriptions() + AzdClient.Prompt().Select()
  • If AZURE_LOCATION missing -> show location picker
  • Resolve all secrets: values across all scripts (Key Vault / prompt / env lookup)
  • Validate all env: expressions resolve to non-empty values
  • Persist prompted values to azd environment
  • Return list of env keys that were set

Deploy(ctx, progress) (*ProvisioningDeployResult, error)

  • Fetch current azd environment values (once, at start)
  • For each script in config.Provision:
    1. Report progress: "Running script (N/M): <name>"
    2. EnvResolver.Resolve() -- build merged environment
    3. ScriptExecutor.Run() -- execute with merged env, stream output
    4. OutputCollector.Collect() -- discover and parse outputs.json
    5. Merge outputs into accumulated outputs + prior output values for next script
    6. Report progress: "Completed: <name>"
  • Handle continueOnError: if script fails and continueOnError: false, stop and return partial results
  • Return ProvisioningDeployResult with all accumulated outputs

Destroy(ctx, progress) (*ProvisioningDestroyResult, error)

  • For each script in config.Destroy:
    1. Report progress
    2. Resolve env, execute script
    3. On failure: stop unless continueOnError
  • Pass AZD_PURGE=true when options.Purge() is true
  • Return list of invalidated environment keys

Preview(ctx, progress) (*DeployPreviewResult, error)

  • Return list of scripts that would execute (name, kind, path)
  • Resolve env status (which variables are set vs. missing)
  • Do NOT execute any scripts

State(ctx) (*ProvisioningStateResult, error)

  • Return last-collected outputs from azd environment
  • Empty resources list (scripts don't track individual Azure resources)

Parameters(ctx) (*ProvisioningParameterResult, error)

  • Return env: and secrets: declarations across all scripts as ProvisioningParameter list
  • Include metadata: name, source (env/secret), required status

PlannedOutputs(ctx) (*PlannedOutputsResult, error)

  • Return empty list (scripts cannot predict their outputs ahead of time)

Cross-Cutting Concerns

Error Handling

  • Per-script failures include: script name, path, exit code, last 50 lines of stderr
  • Partial results preserved (outputs from successful scripts returned even on failure)
  • Descriptive messages per UX spec with actionable guidance

Security

  • Path containment: all script paths validated against project root (no .. escapes)
  • Secret masking: secret values masked in all console output and logging
  • No shell injection: scripts invoked as file arguments, not via shell string interpolation

Progress Reporting

  • Report script start/complete/fail via ProgressFunc
  • Format: "Running script (1/3): Setup Infrastructure", "Completed: Setup Infrastructure"

Testing

Unit Tests

File Coverage
env_resolver_test.go 4-layer merge, expression substitution, missing vars, secret resolution
executor_test.go Script execution, exit codes, stdout/stderr capture, continueOnError
output_test.go outputs.json discovery, parsing, merge, missing file, malformed JSON
provider_test.go Initialize validation, Deploy orchestration, Destroy flow

Integration Test

  • Full provision -> state -> destroy cycle with actual script files
  • Multi-script orchestration: outputs from script N flow as env vars to script N+1
  • Verify outputs appear in DeployResult

Acceptance Criteria

  • azd provision with provider: scripts runs configured scripts and collects outputs
  • azd down with provider: scripts runs configured destroy scripts
  • Multi-script orchestration works: outputs from script N available as env vars to script N+1
  • Script stdout/stderr streams to console in real-time
  • Non-zero exit code fails the operation (unless continueOnError: true)
  • Partial results returned on failure (outputs from successful scripts preserved)
  • Secrets masked in all console output
  • AZURE_SUBSCRIPTION_ID / AZURE_LOCATION trigger standard pickers when missing
  • azd provision --preview lists scripts without executing them
  • State() returns previously-stored outputs
  • AZD_PURGE=true set during azd down --purge
  • Unit tests for EnvResolver, ScriptExecutor, OutputCollector, Provider
  • Integration test: provision -> state -> destroy cycle

Metadata

Metadata

Assignees

Labels

area/provisioningBicep/Terraform/ADE provisioningenhancementNew feature or improvement

Fields

No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions