feat(certificatemanager): add Certificate and CertificateMapEntry - #189
Merged
Conversation
The rest of the free surface of this service, taking it from three types to five. A managed certificate carries no private key, so the case describes one without committing key material - the certificate never leaves PROVISIONING, because example.com is not ours, and no phase depends on it doing so. Four things the API forced, each found by probing it: The API field is "managed". A forma cannot use that name: managed is a fixed property of formae.Resource and a resource redeclaring it does not evaluate. The schema calls it managedCertificate and the plugin renames it both ways. A forma names another resource by short id - that is all a resolvable yields - while the API wants a full path and answers with one, carrying the project number where the forma used the project id. So the request expands and the response shortens. Doing only the first is worse than doing neither: the declared side stays short, state holds a path, and since both fields are immutable every re-apply plans a replacement of a certificate that a map entry still references, which the API then refuses. That is exactly how this failed before the response half existed. State and authorizationAttemptInfo are output-only and live inside managed. A schema hint is only emitted for a top-level field, so a nested one can never be marked hasProviderDefault and Verify rejects it as undeclared; they are dropped on read. A parentless List addresses a collection that does not exist, and the entry was never discovered. Certificate Manager accepts "-" as a wildcard parent, which enumerates entries across every map in one call - cheaper than the walker the same problem needed in Service Directory and Cloud SQL. Deleting a DnsAuthorization while a certificate still references it is reported as retryable rather than fatal: the certificate's delete is a long-running operation, so the refusal is a race that clears itself. Also adds the Certificate Manager sweep, which did not exist - these resources accumulated through the session. Verified against the live project by clearing what this work left behind. Verified locally against the service account CI uses: certmanager-certificate CRUD 8/8 discovery 4/4 certmanager-certificate-map-entry CRUD 8/8 discovery 4/4
naxty
added a commit
that referenced
this pull request
Sep 2, 2026
Following the naming standardisation, an audit of every type the plugin ships against every section of this script. Ten services could create resources that nothing ever collected, and four of them bill: Filestore provisioned capacity per GB-hour; one leaked at 1024GB before Redis instances and clusters hold memory, charged per hour privateca a CA pool is billed monthly; two ENTERPRISE pools leaked before Datastream a private connection reserves a peering range and is billable The rest are free but noisy: Pub/Sub (162 topics accumulated once), Cloud DNS, Cloud Scheduler, Cloud Tasks, Network Connectivity and Eventarc. Three sweeps that looked present were not working: Cloud SQL matched the literal "formae-test-sql", so the instances built by the backup-run and ssl-cert fixtures - named formae-test-backuprun- and formae-test-sslcert- - were never collected. Cloud SQL instances are not cheap. BigQuery matched "formae_plugin_sdk_test_", a prefix that no longer exists after the rename in the previous commit. Renaming fixtures without repointing the sweeps would have started a fresh leak, which is the same mistake in the other direction. The VPC access connector sweep matched a single hand-written prefix. It now uses the shared pattern like everything else. Eventarc needed splitting: gcloud knows triggers, channels and channel-connections and nothing else, so the Advanced types - message buses, pipelines, enrollments, Google API sources - are swept over REST. A message bus is capped at one per region, so a leaked one blocks the next run rather than costing money. Every gcloud subcommand added here was checked to exist first: one that does not fails into "|| true" and silently collects nothing, which is the failure this whole change is about. Still unswept, deliberately: Container, GKE Hub and GKE Backup create nothing because their cases cannot run without a cluster; KMS keyrings cannot be deleted by anyone; Essential Contacts and Org Policy are free configuration. Certificate Manager is swept by #189. Not verified live - there are no working credentials on this machine right now. Every command is guarded by "|| true" and every listing is filtered by SWEEP_RE and KEEP_RE, so the failure mode is collecting nothing rather than deleting something it should not.
naxty
added a commit
that referenced
this pull request
Sep 3, 2026
…lected nothing * fix(ci): sweep the services that had no sweep at all Following the naming standardisation, an audit of every type the plugin ships against every section of this script. Ten services could create resources that nothing ever collected, and four of them bill: Filestore provisioned capacity per GB-hour; one leaked at 1024GB before Redis instances and clusters hold memory, charged per hour privateca a CA pool is billed monthly; two ENTERPRISE pools leaked before Datastream a private connection reserves a peering range and is billable The rest are free but noisy: Pub/Sub (162 topics accumulated once), Cloud DNS, Cloud Scheduler, Cloud Tasks, Network Connectivity and Eventarc. Three sweeps that looked present were not working: Cloud SQL matched the literal "formae-test-sql", so the instances built by the backup-run and ssl-cert fixtures - named formae-test-backuprun- and formae-test-sslcert- - were never collected. Cloud SQL instances are not cheap. BigQuery matched "formae_plugin_sdk_test_", a prefix that no longer exists after the rename in the previous commit. Renaming fixtures without repointing the sweeps would have started a fresh leak, which is the same mistake in the other direction. The VPC access connector sweep matched a single hand-written prefix. It now uses the shared pattern like everything else. Eventarc needed splitting: gcloud knows triggers, channels and channel-connections and nothing else, so the Advanced types - message buses, pipelines, enrollments, Google API sources - are swept over REST. A message bus is capped at one per region, so a leaked one blocks the next run rather than costing money. Every gcloud subcommand added here was checked to exist first: one that does not fails into "|| true" and silently collects nothing, which is the failure this whole change is about. Still unswept, deliberately: Container, GKE Hub and GKE Backup create nothing because their cases cannot run without a cluster; KMS keyrings cannot be deleted by anyone; Essential Contacts and Org Policy are free configuration. Certificate Manager is swept by #189. Not verified live - there are no working credentials on this machine right now. Every command is guarded by "|| true" and every listing is filtered by SWEEP_RE and KEEP_RE, so the failure mode is collecting nothing rather than deleting something it should not. * fix(ci): sweep the resources named before the convention A live survey of the project found 135 leaked resources, and most of them predate the naming standardisation: 85 Pub/Sub topics, 21 secrets and 2 networks still carry formae-plugin-sdk-test-. SWEEP_RE matched only the new shape, so merging this branch as it stood would have left every one of them in the project for good - fixing the drift and abandoning what the drift produced. "plugin" joins the pattern for as long as those exist. It still cannot reach formae-tester@ or formae-byo-cert, which is the property that matters. The survey also confirmed what the Cloud SQL prefix was costing: 21 leaked instances, all named formae-test-sslcert- or formae-test-backuprun-, none of which the old literal "formae-test-sql" ever matched. * feat(ci): add find-leaks.sh, and give both sweepers one set of patterns Nothing in the repo could answer "what has leaked?" without running the full cleanup, which deletes as it goes. A live survey of the project found 135 leaked resources - 21 Cloud SQL instances and a Redis instance among them, both billed by the hour - and none of it was visible from the tooling. find-leaks.sh surveys by default and deletes only with --delete, so the list can be read before anything is removed. It groups billable resources first, because that is the part worth looking at. Both scripts now read scripts/ci/sweep-patterns.sh instead of each carrying its own copies. Separate copies are precisely what drifted: the sweeps matched prefixes the fixtures had stopped using, and the project filled up unseen. One definition, two readers. Two details the survey forced, both about not lying: gcloud exits 0 with empty output when it cannot see a collection, so a non-zero status is not the only failure - the sweep reads stderr as well and reports UNREADABLE rather than "clean". And the credential preflight is a real read of a real resource: gcloud projects describe and gcloud auth print-access-token both succeed from cache while every request comes back UNAUTHENTICATED, which reports an unreadable project as an empty one. REUSE compliant; every tracked shell script still parses. * fix(ci): sweep packet mirroring policies, and delete Cloud SQL over REST Two findings from clearing the backlog by hand. A packet mirroring policy pins the network it mirrors: the network delete answers "already being used by ... packetMirrorings" and fails. Nothing swept that collection, so two leaked policies held two networks in the project indefinitely, and the network sweep looked broken when it was not. It runs before networks now, where the ordering comment for firewalls and subnetworks already said it belongs. gcloud cannot delete a Cloud SQL instance in this project at all: Invalid request: Final Backup Retention Days can not be set if enable_final_backup is disabled It exits non-zero every time, so a sweep built on it removes nothing while appearing to try. Twenty-one instances accumulated behind that, all of them running and billed. clean-environment.sh already used the REST call; find-leaks.sh now does too. The project is clean as of this commit: 135 leaked resources removed - 21 Cloud SQL instances, one Redis instance, 85 Pub/Sub topics, 21 secrets, 7 networks and the policies and subnets that pinned them. The survey reports zero.
…atch2 # Conflicts: # CHANGELOG.md # scripts/ci/clean-environment.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The rest of the free surface of this service, taking it from three types to five.
GCP::CertificateManager::Certificatecertmanager-certificateGCP::CertificateManager::CertificateMapEntrycertmanager-certificate-map-entryVerified locally against the same service account CI uses. A managed
certificate carries no private key, so the case describes one without committing
key material — it never leaves
PROVISIONING, becauseexample.comis not ours,and no phase depends on it doing so.
CertificateIssuanceConfigis left out: itneeds a CA pool, which bills.
Four things the API forced
Each found by probing it, not by reading docs.
managedis not a name a forma can use. It is a fixed property offormae.Resource, and a resource redeclaring it does not evaluate. The schemacalls it
managedCertificate; the plugin renames it in both directions, andthose two transformers are the only places that know.
A reference resolves short; the API speaks in paths. A forma names another
resource by short id — that is all a resolvable yields — while the API wants a
full path and answers with one, carrying the project number where the forma
used the project id. So the request expands and the response shortens.
Doing only the first half is worse than doing neither: the declared side
stays short, state holds a path, and since both fields are immutable every
re-apply plans a replacement of a certificate that a map entry still
references — which the API then refuses. That is exactly how this failed before
the response half existed, and a unit test now pins the round trip as the
identity.
Output-only fields nested inside
managed.stateandauthorizationAttemptInfodescribe issuance progress. A schema hint is onlyemitted for a top-level field, so a nested one can never be marked
hasProviderDefault, and Verify rejects it as undeclared. They are dropped onread.
A parentless List addressed a collection that does not exist, so the entry
was never discovered. Certificate Manager accepts
-as a wildcard parent,enumerating entries across every map in one call — cheaper than the walker the
same problem needed in Service Directory and Cloud SQL.
Deleting a
DnsAuthorizationwhile a certificate still references it is reportedas retryable rather than fatal: the certificate's own delete is a
long-running operation, so the refusal is a race that clears itself. Same
treatment Cloud SQL gives a database whose sessions have not been reaped.
Also: the Certificate Manager sweep did not exist
These resources accumulated through the session — certificates, maps and
authorizations left by conformance runs — because
clean-environment.shhad nosection for them. Added, in dependency order (an entry pins its map and its
certificate; a certificate pins its authorization), and verified against the
live project by using it to clear what this work left behind: 3 certificates,
3 maps and 7 authorizations down to zero.