Gitea returns HTTP 422 when a deploy key title or public key content
already exists on the repo. The provisioner previously surfaced this
as a generic RuntimeError with the raw status code. Introduce
DeployKeyCollisionError (a RuntimeError subclass) in the base module
and detect 422 in GiteaDeployKeyProvisioner.create so callers can
catch collisions explicitly and the error message names the repo and
title involved.
- manifest_git.py: add ProvisionedKeyConfig dataclass; extend GitEntry
with ProvisionedKey field (optional); make IdentityFile default to ""
so provisioned_key entries can be constructed without a static path;
add _parse_provisioned_key_config; update from_repos_entry to accept
provisioned_key as an alternative to identity (mutually exclusive,
parser rejects both-or-neither)
- deploy_key_provisioner.py (new): DeployKeyProvisioner ABC with create()
and delete() abstract methods; get_provisioner() factory with lazy
contrib import for gitea
- contrib/gitea/deploy_key_provisioner.py (new): GiteaDeployKeyProvisioner
generating ed25519 keypairs via ssh-keygen and managing them through
the Gitea deploy-key API (POST/DELETE); 404 on delete is success;
all other errors raise RuntimeError
- git_gate.py: add _provision_dynamic_key() called in GitGate.prepare()
for entries with ProvisionedKey — generates key, writes private key
and key ID files to stage_dir, patches GitGateUpstream.identity_file;
add revoke_git_gate_provisioned_keys() for teardown — raises on failure
- docker/launch.py: call revoke_git_gate_provisioned_keys() in teardown()
after stack.close() so revocation runs after containers stop and
failures propagate (not suppressed)
- smolmachines/launch.py: extract _teardown_smolmachines() helper that
catches stack.close() errors (warn + re-raise) then calls revocation;
same fatal-on-failure contract as docker backend
- test_manifest_git.py: 9 new cases for provisioned_key parsing
- test_deploy_key_provisioner.py (new): factory smoke tests
- test_contrib_gitea_deploy_key.py (new): create/delete/error/split tests
Closes#169