Zum Inhalt

Polycrate API 0.16.1

Release-Datum: 27. April 2026
Typ: Fix

Highlights

  • DNSZone-Provisioning stabalisiert – Drei zusammenhängende Bugs beim Erstellen interner DNS-Zonen behoben: doppelter provision()-Aufruf, nicht-idempotenter PowerDNS-API-Call und Transaction-Poisoning durch ProductizedModelMixin (Specs 211, 212, 214).
  • AttributeError in _integrity_error_response behoben – Logger-Shadowing in views.py verursachte HTTP 500 statt HTTP 409 bei UniqueConstraint-Verletzungen (Spec 210).

Details zu einzelnen Specs: polycrate spec inspect <id> im Workspace polycrate-api.

Artefakte

Docker Image

docker pull cargo.ayedo.cloud/polycrate/polycrate-api:0.16.1

Block

polycrate pull cargo.ayedo.cloud/ayedo/k8s/polycrate-api
polycrate run polycrate-api install

Fixes

Logger-Shadowing in _integrity_error_response (Spec 210)

src/polycrate_api/views.py importierte polycrate_api.logging.logger (eine Klasse, keine Logger-Instance) nach der Modul-Level-Definition logger = logging.getLogger(__name__). Dieser Import überschrieb die Variable, sodass _integrity_error_response beim Aufruf von logger.warning(...) mit AttributeError: type object 'logger' has no attribute 'warning' abbrach.

Auswirkung: POST auf bereits existierende Ressourcen (z.B. K8sCluster) lieferte HTTP 500 statt HTTP 409, der Polycrate Operator retried dadurch endlos.

Fix: _integrity_error_response nutzt jetzt logging.getLogger(__name__).warning(...) direkt — unabhängig von der Modul-Level-Variable.

DNSZone-Provisioning: Doppelter provision()-Aufruf (Spec 214)

DNSZoneCreateForm.save() rief zone.provision() intern auf. Anschließend rief form_valid() in polycrate_api/views.py nochmals self.object.provision() auf — sync_to_powerdns() wurde damit bei jedem Erstellen einer Zone zweimal ausgeführt. Vor Spec 211 (nicht-idempotenter create_zone()-Call) resultierte der zweite Aufruf direkt in einem 409 Conflict.

Fix: provision() wurde aus DNSZoneCreateForm.save() entfernt. form_valid() übernimmt das Provisioning alleinig innerhalb seiner transaction.atomic()-Grenze.

DNSZone-Provisioning: Nicht-idempotenter PowerDNS-API-Call (Spec 211)

DNSZone.sync_to_powerdns() verwendete client.create_zone() (nicht-idempotenter POST). War die Zone bereits in PowerDNS vorhanden (z.B. durch Litestream-Restore aus S3), schlug der Call mit 409 Conflict fehl — die Zone existierte danach in PowerDNS, aber nicht in der Polycrate-API-Datenbank.

Fix: Wechsel auf client.ensure_zone_exists() (GET-then-POST): existierende Zonen werden zurückgegeben, nicht erneut angelegt.

DNSZone-Provisioning: Transaction-Poisoning durch ProductizedModelMixin (Spec 212)

ProductizedModelMixin._post_save rief reconcile_organization_product() innerhalb eines transaction.atomic(savepoint=True)-Blocks auf. Schlug der Savepoint fehl, setzte Django connection.needs_rollback = True — die äußere Transaktion (aus perform_create/form_valid) war damit vergiftet. Folgeoperationen (z.B. self.save(update_fields=["powerdns_metadata"]) am Ende von sync_to_powerdns()) schlugen mit TransactionManagementError fehl und rollten den DB-Eintrag zurück, während die Zone in PowerDNS bereits angelegt war.

Fix: _post_save und _post_delete nutzen jetzt transaction.on_commit() statt einem internen Savepoint. Die Pricing-Reconciliation läuft erst nach erfolgreichem Commit der äußeren Transaktion — wenn die Transaktion rollt zurück, feuert der Callback nie.

Kompatibilität und Deployment

  • Kein Breaking Change, keine Datenbankmigrationen.
  • Betrifft alle Deployments die interne DNS-Zonen (PowerDNS) verwenden.