Script provisioning provider -- config design and parsing
Parent epic: #7733
Framework: #7465 | Depends on: #7734 (scaffold)
Overview
Implement configuration parsing from ProvisioningOptions.Config (a google.protobuf.Struct) into typed Go structs, plus validation logic that runs during Initialize().
Configuration Model
Users configure the script provider in azure.yaml:
infra:
provider: scripts
config:
provision:
- kind: sh
run: scripts/setup.sh
name: Setup Infrastructure
env:
AZURE_LOCATION: ${AZURE_LOCATION}
RESOURCE_GROUP: rg-${AZURE_ENV_NAME}
secrets:
DB_PASSWORD: akvs://my-vault/db-password
continueOnError: false
- kind: pwsh
run: scripts/configure-app.ps1
env:
APP_NAME: myapp-${AZURE_ENV_NAME}
destroy:
- kind: sh
run: scripts/teardown.sh
env:
RESOURCE_GROUP: rg-${AZURE_ENV_NAME}
Types to Implement
File: internal/provisioning/config.go
// ProviderConfig is the top-level configuration parsed from infra.config in azure.yaml.
type ProviderConfig struct {
Provision []*ScriptConfig `json:"provision,omitempty"`
Destroy []*ScriptConfig `json:"destroy,omitempty"`
}
// ScriptConfig defines the configuration for a single script entry.
// Field names align with HookConfig (pkg/ext/models.go) for user familiarity.
type ScriptConfig struct {
Kind string `json:"kind,omitempty"`
Shell string `json:"shell,omitempty"` // deprecated alias for Kind
Run string `json:"run"`
Name string `json:"name,omitempty"`
Env map[string]string `json:"env,omitempty"`
Secrets map[string]string `json:"secrets,omitempty"`
ContinueOnError bool `json:"continueOnError,omitempty"`
Windows *ScriptConfig `json:"windows,omitempty"`
Posix *ScriptConfig `json:"posix,omitempty"`
}
Parsing Strategy
Use the JSON re-marshal pattern established in azd (tools.UnmarshalHookConfig):
func ParseProviderConfig(raw map[string]any) (*ProviderConfig, error) {
data, err := json.Marshal(raw)
// ...
var cfg ProviderConfig
err = json.Unmarshal(data, &cfg)
// ...
}
The ProvisioningOptions.Config is a google.protobuf.Struct. Convert via options.GetConfig().AsMap() -> ParseProviderConfig().
Validation (during Initialize)
Implement validateScriptConfig(sc *ScriptConfig, projectPath string, index int, section string) error with these checks:
run is non-empty -- error: "provision[0].run is required"
- Script path is relative -- reject absolute paths
- No path traversal -- reject paths containing
.. that escape project root
- Script file exists --
os.Stat(filepath.Join(projectPath, sc.Run))
- Valid kind --
sh, pwsh, or auto-inferred from file extension (.sh -> sh, .ps1 -> pwsh)
- Shell -> Kind mapping -- if
Kind is empty but Shell is set, map Shell to Kind (backward compat with hooks)
- At least one script -- config must have at least one
provision or destroy entry
Error messages should follow UX spec patterns:
ERROR: Invalid script configuration in provision[0]:
Script file not found: scripts/setup.sh
Resolved path: /home/user/myproject/scripts/setup.sh
Platform Resolution
On Windows, if sc.Windows != nil, merge its fields into the base config. On Linux/macOS, use sc.Posix. This mirrors HookConfig.Windows/HookConfig.Posix semantics.
Acceptance Criteria
Script provisioning provider -- config design and parsing
Parent epic: #7733
Framework: #7465 | Depends on: #7734 (scaffold)
Overview
Implement configuration parsing from
ProvisioningOptions.Config(agoogle.protobuf.Struct) into typed Go structs, plus validation logic that runs duringInitialize().Configuration Model
Users configure the script provider in
azure.yaml:Types to Implement
File:
internal/provisioning/config.goParsing Strategy
Use the JSON re-marshal pattern established in azd (
tools.UnmarshalHookConfig):The
ProvisioningOptions.Configis agoogle.protobuf.Struct. Convert viaoptions.GetConfig().AsMap()->ParseProviderConfig().Validation (during Initialize)
Implement
validateScriptConfig(sc *ScriptConfig, projectPath string, index int, section string) errorwith these checks:runis non-empty -- error:"provision[0].run is required"..that escape project rootos.Stat(filepath.Join(projectPath, sc.Run))sh,pwsh, or auto-inferred from file extension (.sh->sh,.ps1->pwsh)Kindis empty butShellis set, mapShelltoKind(backward compat with hooks)provisionordestroyentryError messages should follow UX spec patterns:
Platform Resolution
On Windows, if
sc.Windows != nil, merge its fields into the base config. On Linux/macOS, usesc.Posix. This mirrorsHookConfig.Windows/HookConfig.Posixsemantics.Acceptance Criteria
ParseProviderConfig()correctly convertsmap[string]any->*ProviderConfigrun, absolute paths,..path traversal, missing script files, unknown kindskindauto-inferred from.sh->sh,.ps1->pwshwhen omittedshellfield maps tokindfor backward compatibilityconfig_test.gocovering:../../../etc/passwd)