feat: Script Provisioning Provider extension (microsoft.azd.scripts)#7737
feat: Script Provisioning Provider extension (microsoft.azd.scripts)#7737wbreza wants to merge 2 commits intoAzure:feature/ext-provisioning-providerfrom
Conversation
Implements the Script Provisioning Provider extension that enables shell script-based provisioning and teardown workflows in azd. This extension registers a 'scripts' provisioning provider via gRPC, allowing users to configure bash/PowerShell scripts as their infrastructure provider in azure.yaml. Key components: - Extension scaffold following microsoft.azd.demo pattern - Config parsing with validation (path safety, kind inference, platform overrides) - EnvResolver with 4-layer environment variable merging - ScriptExecutor for bash/pwsh script execution - OutputCollector for outputs.json discovery and parsing - Full ProvisioningProvider interface implementation Resolves Azure#7734, Azure#7735, Azure#7736 Part of Azure#7733 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix ContinueOnError bug: platform override no longer unconditionally resets the field when the override doesn't set it - Fix getAzdEnv: propagate context cancellation, warn on other errors instead of silently swallowing all failures - Add 10MB size limit on outputs.json to prevent OOM - Remove dead ScriptResult fields (Stdout/Stderr never populated) - Remove unused exported functions (OutputsToEnvMap, OutputsToProvisioning) - Extract toProtoOutputs helper to reduce duplication - Improve shBinary() portability: use LookPath with /bin/sh fallback - Add platform override tests (ContinueOnError preservation, env merge) - Add executor unit tests (buildShellCommand, mapToEnvSlice) - Fix hardcoded /tmp path in test to use t.TempDir() - Update README: clarify secrets are plain values in alpha Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0665896 to
30e76ca
Compare
jongio
left a comment
There was a problem hiding this comment.
6 findings from deep review. Key items:
- 2 HIGH: nondeterministic env var resolution order, no provider.go tests
- 2 MEDIUM: ContinueOnError zero-value limitation, raw stderr warning
- 2 LOW: destroy outputs discarded, missing ctx check between iterations
The extension architecture is clean - good separation of config/resolver/executor/collector. The 4-layer env resolution model is well-designed. Tests cover the individual components thoroughly.
| for k, tmpl := range sc.Env { | ||
| resolved, err := envsubst.Eval(tmpl, lookup) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("resolving env variable %q: %w", k, err) |
There was a problem hiding this comment.
Go map iteration order is nondeterministic. If two vars in the same env map reference each other (e.g., A: "${B}", B: "hello"), the result depends on which key Go happens to iterate first.
Consider sorting keys before iteration:
keys := slices.Sorted(maps.Keys(sc.Env))
for _, k := range keys {
tmpl := sc.Env[k]
// ...
}Alternatively, document that cross-references within the same env map aren't supported and users should use azd environment vars (Layer 2) for shared values instead.
| } | ||
|
|
||
| return result, nil | ||
| } |
There was a problem hiding this comment.
This file (223 lines) is the main integration point but has no test coverage. Config parsing, env resolution, executor, and output collector all have unit tests - but runScripts (which orchestrates error handling, output merging, and progress reporting) doesn't.
Consider adding tests that mock AzdClient and verify:
- Script execution order and progress callbacks
- Output merging across multiple scripts
ContinueOnErrorbehavior (continue vs stop)- Context cancellation propagation
| sc.Shell = override.Shell | ||
| } | ||
| if override.Name != "" { | ||
| sc.Name = override.Name |
There was a problem hiding this comment.
if override.ContinueOnError only triggers when the override sets it to true. Due to Go zero-value semantics, a platform override can't explicitly disable a ContinueOnError that was set in the base config.
If this is intentional (platform overrides can only add permissiveness, not remove it), a comment here would help. If not, consider using *bool to distinguish 'not set' from 'explicitly false'.
| if ctx.Err() != nil { | ||
| return nil, ctx.Err() | ||
| } | ||
|
|
There was a problem hiding this comment.
fmt.Fprintf(os.Stderr, ...) bypasses the extension framework's logging. The azdext package provides structured logging that integrates with azd's output handling - using it here would keep diagnostics consistent with the rest of the extension framework.
|
|
||
| return &azdext.ProvisioningDestroyResult{ | ||
| InvalidatedEnvKeys: invalidatedKeys, | ||
| }, nil |
There was a problem hiding this comment.
runScripts collects outputs from destroy scripts, but they're not wired into the result. Only invalidation keys from prior provisioning outputs are returned. If this is intentional (destroy scripts don't produce meaningful outputs), a brief comment would clarify. If destroy scripts should be able to produce outputs (e.g., for audit/logging), consider including them.
|
|
||
| resolver := NewEnvResolver(azdEnv) | ||
| executor := NewScriptExecutor(p.projectPath) | ||
| collector := NewOutputCollector(p.projectPath) |
There was a problem hiding this comment.
nit: The loop doesn't check ctx.Err() between script iterations. For long script sequences, adding an early cancellation check before each iteration would improve responsiveness:
for i, sc := range scripts {
if err := ctx.Err(); err != nil {
return nil, err
}
// ...
}
Description
Implements the Script Provisioning Provider extension (
microsoft.azd.scripts) that enables shell script-based provisioning and teardown workflows in azd. This is a first-party extension that registers ascriptsprovisioning provider via gRPC.Epic: #7733
Depends on: #7482 (provisioning provider framework)
Resolves: #7734, #7735, #7736
Architecture
The extension is a pure provisioning provider — no custom CLI commands, no MCP server. It registers via
WithProvisioningProvider("scripts", factory)and implements the fullazdext.ProvisioningProviderinterface.Key Components
internal/provisioning/config.goinfra.configwith validationinternal/provisioning/env_resolver.gointernal/provisioning/executor.gointernal/provisioning/output_collector.gooutputs.jsonfilesinternal/provisioning/provider.goProvisioningProviderimplementationUser Configuration
Testing
Checklist
go buildscriptsprovider via gRPC.sh->sh,.ps1->pwsh)${EXPRESSION}substitution