Skip to content

azb — Developer Guide

Package Structure

tools/azb/
  pyproject.toml                    # pip/pipx installable
  azb/                              # Python package
    __init__.py                     # version
    __main__.py                     # python -m azb
    cli.py                          # main() entry point + project discovery
    discovery.py                    # git root, .cloud/azure/, ProjectContext
    models/                         # Resource, Environment, Template classes
    loaders/                        # Discovery + layered merge logic
    commands/                       # Top-level CLI commands
    pipelines/                      # Pipeline subsystem
      commands/                     # Pipeline sub-commands
      definitions/                  # Pipeline + PipelineStage models
      nodes/                        # Node model + loader
      rendering/                    # YAML renderer
      resolve_azp.py                # Path/shortname resolver
    utilities/                      # Shared utilities
    bundled/                        # Default artifacts shipped with the tool
      resources/                    # .azr + .azr.bicep + .azr.py
      environments/                 # .aze + .aze.py
      templates/                    # .azt
      pipelines/
        nodes/                      # .azpn + .azpn.yml
        work/                       # .yml (shared step templates)

Discovery Flow

  1. Walk up from cwd to find .git (git root)
  2. Find {git-root}/.cloud/azure/ (cloud root)
  3. Load azb.config from cloud root
  4. Build ProjectContext with paths to bundled and local roots

Layered Resolution

All artifact types follow the same pattern:

python
bundled = load_X(project.bundled_X_root)
local   = load_X(project.local_X_root)
merged  = merge_X(bundled, local)     # local wins by id

Resources, environments, and templates use folder-based discovery (scan subdirectories for manifests). Pipeline nodes use recursive glob for .azpn files.

Adding a New Resource

  1. Create azb/bundled/resources/{id}/
  2. Add {id}.azr (JSON manifest — name, aliases, parameters, nameTemplate, configure actions)
  3. Add {id}.azr.bicep (Bicep deployment template)
  4. Optionally add {id}.azr.py with a ResourceImpl(Resource) class for validation and configure actions

The manifest structure:

json
{
  "aliases": ["short-name"],
  "name": "Human Name",
  "description": "What this resource is",
  "nameTemplate": "{prefix}-{workload}-xx-{domain}-{environment}-{region}",
  "targetScope": "resourceGroup",
  "parameters": {
    "domain": { "type": "string", "description": "Logical domain" },
    "sku": { "type": "string", "default": "Standard", "description": "Pricing tier" }
  },
  "configure": {
    "action-name": {
      "description": "What this does",
      "params": {
        "flag-name": { "type": "string", "required": true }
      }
    }
  }
}

Adding a New Environment

  1. Create azb/bundled/environments/{id}/
  2. Add {id}.aze (JSON manifest — naming, defaults, tags, constraints, capabilities)
  3. Optionally add {id}.aze.py with an EnvironmentImpl(Environment) class

Adding a New Pipeline Node

  1. Create azb/bundled/pipelines/nodes/{id}.azpn (JSON manifest)
  2. Create azb/bundled/pipelines/nodes/{id}.azpn.yml (Azure DevOps YAML template)
  3. Use {token} placeholders for config values — they're substituted at generation time

Node YAML files can reference work templates via - template: ../work/{name}.yml. The generate command writes both node and work YAML to the repo.

Adding a New Work Template

  1. Create azb/bundled/pipelines/work/{name}.yml
  2. Define parameters: and steps: as normal Azure DevOps template
  3. Use {token} placeholders for config values
  4. Node YAML that references it via - template: ../work/{name}.yml will trigger it to be written on generate

Token Substitution

Two levels of substitution:

Generation-time (azb resolves from config)

All keys in azb.confignaming are available. Common tokens:

TokenExample valueSource
{prefix}sbazb.config
{workload}plaazb.config
{region}uksazb.config
{npmFeed}carrot/packagesazb.config

Applied to: .azp parameter values, node YAML, work YAML.

Runtime (Azure DevOps resolves)

TokenRendered asResolved by
{env}${{ variables['env'] }}Azure DevOps

The env variable is set by the pipeline's environmentMapping block based on the triggering branch.

Command Contract

All commands follow the same interface:

python
class Command:
    name: str
    description: str

    def run(self, positional, flags, config):
        ...

config is a dict containing the loaded azb.config values plus environment (resolved Environment object) and project (ProjectContext).

Dependencies

None — pure Python 3.11+, no external packages. The only runtime dependency is the Azure CLI (az) for plan/deploy/configure commands.


Usage Guide

Setup

azb must be run from inside a git repository that contains a .cloud/azure/ directory with an azb.config file.

bash
pip install -e tools/azb

azb.config

json
{
  "defaultEnvironment": "dev",
  "naming": {
    "prefix": "sb",
    "platform": "pla",
    "region": "uks",
    "npmFeed": "carrot/packages"
  },
  "tags": {
    "company": "Carrot",
    "platform": "Carrot",
    "managedBy": "azure-devops"
  }
}

The naming block provides token values used throughout resources, templates, and pipeline generation. Any key here is available as {key} in .azp, .azr, and pipeline YAML files.

Resources

List and describe

bash
azb list resources
azb describe resource keyvault
azb describe resource kv          # alias works too

Predict (no Azure calls)

bash
azb predict resource keyvault --domain platform
# → sb-pla-kv-platform-dev-uks

Plan (Azure what-if)

bash
azb plan resource keyvault --domain platform --environment demo

Deploy

bash
azb deploy resource keyvault --domain platform --environment demo --commit

Configure (post-deployment)

bash
azb configure keyvault set-secret --secret-name API_KEY --secret-value s3cret --domain platform --commit
azb configure storageaccount static-website --domain cdn --commit
azb configure frontdoor purge --endpoint cdn --domain edge --commit

Templates

Templates deploy ordered groups of resources together.

bash
azb list templates
azb describe template cdn
azb predict template cdn --environment demo
azb deploy template cdn --environment demo --commit

Pipelines

Pipeline definitions (.azp) live alongside the source code they deploy. The generated pipeline YAML always lands in .cloud/azure/pipelines/.

Discover all pipelines in the repo

bash
azb pipelines discover

Generate by path or short name

bash
# Direct path
azb pipelines generate src/net/Services/MyService/ci.azp --commit

# Short name (searches repo for ci.azp — errors if 0 or 2+ matches)
azb pipelines generate ci --commit

Preview without writing

bash
azb pipelines generate cdn

Omit --commit to preview the generated YAML and see what files would be written.

Output structure

.cloud/azure/pipelines/
  cdn.yml              ← generated pipeline (Azure DevOps points here)
  nodes/               ← node YAML templates (written by azb)
  work/                ← work YAML templates (written by azb)

Token substitution

Pipeline .azp files and node/work YAML templates use {token} placeholders resolved from azb.config:

json
{
  "params": {
    "azureSubscription": "{prefix}-sc-{env}",
    "appServiceName": "{prefix}-{workload}-app-cdn-{env}-{region}"
  }
}
  • Config tokens ({prefix}, {workload}, {region}, {npmFeed}, etc.) — resolved at generation time
  • {env} — becomes ${{ variables['env'] }} (resolved at pipeline runtime by Azure DevOps)

Environments

bash
azb list environments
azb describe environment demo

Environments provide naming context, default parameters, tags, and optional validation hooks. The --environment flag (or defaultEnvironment in config) selects which one to use.

Local Overrides

Any bundled artifact can be overridden locally:

ArtifactBundled locationLocal override
Resourcesazb/bundled/resources/.cloud/azure/resources/
Environmentsazb/bundled/environments/.cloud/azure/environments/
Templatesazb/bundled/templates/.cloud/azure/templates/
Nodesazb/bundled/pipelines/nodes/Anywhere under .cloud/azure/

Same id in the local repo takes precedence over the bundled version.

Carrot