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.
Remove 35+ unused imports across 20+ files (W0611). Wrap 19 lines
to fit under 100 character limit (C0301). Add type casts and
annotations in egress_addon_core.py to resolve pyright errors
caused by JSON parsing of untyped objects.
Key changes:
- Remove unused imports (abstractmethod, mock utilities, etc)
- Split long lines at logical breaks (method calls, error messages)
- Add typing.cast() for proper type inference in JSON parsing
- Explicit type annotations for dict/list accesses
Results:
- Pylint rating: 8.73/10
- egress_addon_core.py: 0 pyright errors (was 15)
- All W0611 and C0301 issues fixed
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- 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