Multi-repo rollout framework for Azure CLI. Each campaign opens a tracking
issue on a target repo and (optionally) dispatches per-item handlers that can
themselves open PRs. The first campaign, python-upgrade, automates the
mechanical part of bumping Azure CLI's embedded Python interpreter.
.
├── campaigns/ # Framework + registered campaigns
│ ├── base.py # Item / HandlerContext / Campaign protocol
│ ├── state.py # Hidden state-block parser
│ ├── framework.py # Generic runner (gh CLI I/O)
│ ├── report.py # Roadmap diagram renderer
│ ├── registry.py # CAMPAIGNS / HANDLERS lookup tables
│ ├── python_upgrade/ # The python-upgrade campaign
│ │ ├── __init__.py # Campaign class + handler entry points
│ │ ├── intro.md # Tracking-issue intro template
│ │ ├── azure_cli_upgrader/ # AI-assisted bump handler
│ │ ├── knack_upgrader/ # knack support PR handler
│ │ ├── knack_pin_bumper/ # azure-cli setup.py pin handler
│ │ └── validators.py # Reusable PyPI / repo-file checks
│ └── tests/
├── run_campaign.py # CLI entry point
└── .github/workflows/
├── RunCampaign.yml # Manual dispatch + reusable
├── PythonUpgradeDaily.yml # Cron wrapper
├── PythonUpgradeDemo.yml # Forced-version end-to-end demo
└── PythonUpgradeDemoCleanup.yml # One-click teardown of a demo run
- Fork the target repo (e.g.
Azure/azure-cli→YourOrg/azure-cli) if you don't have push rights. The campaign clones it, branches, pushes, and opens a PR. - GitHub App installed on both this campaigns repo and the target
repo, with
Issues: write,Contents: write,Pull requests: write. StoreAGENT_APP_IDandAGENT_APP_PRIVATE_KEYas repo secrets. - Repo variable
RUN_CAMPAIGN_ENABLED=trueon this repo (kill switch). - Repo variable or input override pointing to your fork(s).
Actions → Run Campaign → Run workflow:
campaign:python-upgradetarget_repo:YourOrg/azure-cliissue_repo:YourOrg/azure-cli(or another repo where the issue lives)params_json:{"reference_repo":"Azure/azure-cli","exclude_prs":[33313]}dry_run: tick for first runs
PythonUpgradeDaily.yml runs at 09:00 UTC daily and calls RunCampaign with
the python-upgrade campaign. Edit the default target_repo in that file
for your deployment, or pass overrides via workflow_dispatch.
The whole point of this layout is that the campaigns repo is the only thing you need to fork to experiment safely:
- Fork
YangAn-microsoft/azure-cli-campaignsinto your namespace. - Fork the target repo (e.g.
Azure/azure-cli) into your namespace. - Install your own GitHub App (or use a PAT) on both forks.
- Set
RUN_CAMPAIGN_ENABLED=trueon your campaigns fork. - Dispatch — issues / PRs land on your fork, not upstream.
See docs/adding-a-campaign.md for the full walkthrough — anatomy, a hello-world example, multi-phase items, dependencies, validators, tests, and the workflow wiring checklist.
Short version:
- Create
campaigns/<name>/__init__.pywith a class implementing theCampaignprotocol (id,issue_repo,build(params)). - Optionally add handler functions
(HandlerContext) -> HandlerResult. - Register both in
campaigns/registry.py(CAMPAIGNS+HANDLERS). - Add the campaign id to the
options:list in.github/workflows/RunCampaign.yml. - Drop tests under
campaigns/tests/.
pip install -r requirements.txt # stdlib only; pytest for tests
python -m pytest -q
# Dry-run a campaign locally
$env:GH_TOKEN = (gh auth token)
python -m run_campaign --campaign python-upgrade --dry-run \
--params '{"current_full":"3.13.13","new_full":"3.14.5","handler_repo":"YourOrg/azure-cli"}' \
--issue-repo YourOrg/azure-cli