Appearance
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
- Walk up from cwd to find
.git(git root) - Find
{git-root}/.cloud/azure/(cloud root) - Load
azb.configfrom cloud root - Build
ProjectContextwith 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 idResources, environments, and templates use folder-based discovery (scan subdirectories for manifests). Pipeline nodes use recursive glob for .azpn files.
Adding a New Resource
- Create
azb/bundled/resources/{id}/ - Add
{id}.azr(JSON manifest — name, aliases, parameters, nameTemplate, configure actions) - Add
{id}.azr.bicep(Bicep deployment template) - Optionally add
{id}.azr.pywith aResourceImpl(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
- Create
azb/bundled/environments/{id}/ - Add
{id}.aze(JSON manifest — naming, defaults, tags, constraints, capabilities) - Optionally add
{id}.aze.pywith anEnvironmentImpl(Environment)class
Adding a New Pipeline Node
- Create
azb/bundled/pipelines/nodes/{id}.azpn(JSON manifest) - Create
azb/bundled/pipelines/nodes/{id}.azpn.yml(Azure DevOps YAML template) - 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
- Create
azb/bundled/pipelines/work/{name}.yml - Define
parameters:andsteps:as normal Azure DevOps template - Use
{token}placeholders for config values - Node YAML that references it via
- template: ../work/{name}.ymlwill trigger it to be written on generate
Token Substitution
Two levels of substitution:
Generation-time (azb resolves from config)
All keys in azb.config → naming are available. Common tokens:
| Token | Example value | Source |
|---|---|---|
{prefix} | sb | azb.config |
{workload} | pla | azb.config |
{region} | uks | azb.config |
{npmFeed} | carrot/packages | azb.config |
Applied to: .azp parameter values, node YAML, work YAML.
Runtime (Azure DevOps resolves)
| Token | Rendered as | Resolved 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/azbazb.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 tooPredict (no Azure calls)
bash
azb predict resource keyvault --domain platform
# → sb-pla-kv-platform-dev-uksPlan (Azure what-if)
bash
azb plan resource keyvault --domain platform --environment demoDeploy
bash
azb deploy resource keyvault --domain platform --environment demo --commitConfigure (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 --commitTemplates
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 --commitPipelines
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 discoverGenerate 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 --commitPreview without writing
bash
azb pipelines generate cdnOmit --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 demoEnvironments 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:
| Artifact | Bundled location | Local override |
|---|---|---|
| Resources | azb/bundled/resources/ | .cloud/azure/resources/ |
| Environments | azb/bundled/environments/ | .cloud/azure/environments/ |
| Templates | azb/bundled/templates/ | .cloud/azure/templates/ |
| Nodes | azb/bundled/pipelines/nodes/ | Anywhere under .cloud/azure/ |
Same id in the local repo takes precedence over the bundled version.