Development
Source, issues and pull requests live at
https://github.com/cloudchristoph/certbot-dns-azure-modern. The plugin is a single
module, certbot_dns_azure/_internal/dns_azure.py, implementing certbot’s
certbot.plugins.dns_common.DNSAuthenticator. There is no public Python API
beyond the certbot plugin interface.
Local setup
git clone https://github.com/cloudchristoph/certbot-dns-azure-modern.git
cd certbot-dns-azure-modern
python -m venv .venv && . .venv/bin/activate
pip install certbot 'azure-identity>=1.19.0' 'azure-mgmt-dns>=8.2.0' 'azure-core>=1.38.0' pytest build
pip install --no-deps -e .
Unit tests
The unit tests mock the Azure SDK and run without any Azure access:
python -m pytest -q tests/ -W error::DeprecationWarning
CI runs them on Python 3.10 to 3.13 against certbot 3.x and the latest release, and
against both supported azure-mgmt-dns lines (8.x and 9.x). When touching
compatibility, also test the oldest supported line locally (certbot>=3.0,<4.0
together with pyOpenSSL<26, and azure-mgmt-dns<9).
Integration tests
azure_tests/ issues real certificates from the Let’s Encrypt staging
environment against dedicated Azure DNS zones. They need a zone whose name starts
with certbot-test. (the test module refuses anything else, so it can never touch
a production zone) plus the following environment variables:
export AZURE_TENANT_ID=... AZURE_SUBSCRIPTION_ID=... \
AZURE_DNS_RESOURCE_GROUP=... AZURE_DNS_TEST_DOMAIN=certbot-test.example.com \
EMAIL=you@example.com
pytest -rA azure_tests/
The tests authenticate with the current Azure CLI login, or with a service principal
when AZURE_CLIENT_ID and AZURE_CLIENT_SECRET are set, and only delete
_acme-challenge* TXT records that they created themselves. The required
zone layout (a base zone with static CNAME/TXT records for the delegation tests plus
the delegated zone1 and zone2) is described at the top of
azure_tests/integration_test.py.
CI runs them on pull requests that change code, on release tags and once a week.
Dependency audit
.github/workflows/dependency-audit.yml checks the runtime dependencies against
the OSV vulnerability database every Monday, on manual dispatch and on pull requests
that change setup.py. It audits two resolutions on Python 3.10 and 3.13: the
latest versions a fresh install gets, and the declared lower bounds. pip keeps an
older, already installed version as long as it satisfies the bound, so a finding in
the lower-bound run means the bound in setup.py has to be raised. A failing
scheduled run opens an issue (or comments on the open one). To run the same check
locally:
uv pip compile setup.py --resolution lowest-direct > /tmp/resolved.txt
uvx pip-audit --no-deps --disable-pip -r /tmp/resolved.txt --vulnerability-service osv
Build the docs
pip install -r docs/requirements.txt
sphinx-build -W -b html docs docs/_build/html
.github/workflows/docs.yml publishes them to GitHub Pages when a push to main
changes docs/, CHANGELOG.md or setup.py. Pull requests only build them.
Release checklist
Bump
versioninsetup.pyand updateCHANGELOG.md.Open a pull request, wait for the checks and merge it;
mainonly accepts changes through pull requests.Tag with the bare version (
git tag 2.8.0 && git push origin 2.8.0). The build job refuses a tag that differs from thesetup.pyversion.The workflow builds the wheel and sdist, runs the integration tests, publishes to PyPI via trusted publishing and creates the GitHub release.