Skip to content

Script provisioning provider — config design and parsing #7735

Description

@wbreza

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:

  1. run is non-empty -- error: "provision[0].run is required"
  2. Script path is relative -- reject absolute paths
  3. No path traversal -- reject paths containing .. that escape project root
  4. Script file exists -- os.Stat(filepath.Join(projectPath, sc.Run))
  5. Valid kind -- sh, pwsh, or auto-inferred from file extension (.sh -> sh, .ps1 -> pwsh)
  6. Shell -> Kind mapping -- if Kind is empty but Shell is set, map Shell to Kind (backward compat with hooks)
  7. 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

  • ParseProviderConfig() correctly converts map[string]any -> *ProviderConfig
  • Validation catches: missing run, absolute paths, .. path traversal, missing script files, unknown kinds
  • kind auto-inferred from .sh -> sh, .ps1 -> pwsh when omitted
  • shell field maps to kind for backward compatibility
  • Platform override merging works (Windows/Posix)
  • Unit tests in config_test.go covering:
    • Valid multi-script config
    • Empty config (no provision or destroy)
    • Missing script file
    • Path traversal attempt (../../../etc/passwd)
    • Unknown kind value
    • Kind inference from file extension
    • Shell -> Kind backward compat mapping
  • Error messages include section name, index, and actionable details

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