Skip to content

feat(gcp): add Network Services meshes, gateways and LB policies - #191

Open
naxty wants to merge 1 commit into
mainfrom
naxty/gcpNetworkServicesMesh
Open

feat(gcp): add Network Services meshes, gateways and LB policies#191
naxty wants to merge 1 commit into
mainfrom
naxty/gcpNetworkServicesMesh

Conversation

@naxty

@naxty naxty commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Adds three types from networkservices.googleapis.com v1, a service this plugin had no package for.

Type Scope Cost Update
GCP::NetworkServices::Mesh global free PATCH, all fields but name
GCP::NetworkServices::Gateway regional free as OPEN_MESH PATCH, minus type and scope
GCP::NetworkServices::ServiceLbPolicy global free PATCH, all fields but name

Conformance has NOT been run — these types are unverified

No make conformance-* target was run against this branch, and make install was deliberately not run either (it does rm -rf $HOME/.pel/formae/plugins/gcp, which is shared with other agents' runs). The three types are therefore unverified end-to-end through formae. The conformance fixtures in this PR have never been executed.

What was verified, and how, is set out below — but it was verified by driving the REST API directly with curl, not by running the plugin. Nothing here should be read as "these types work in formae".

What the live probe did establish

Every type was created, its operation polled to completion, then GET'd to confirm the resource actually materialised — this API accepts a create, returns an operation, and can still fail it with nothing left behind but the operation's error. All probe resources were deleted and their absence re-listed; zero leaks.

Three behaviours are encoded in this PR that the discovery document does not show, each of which would otherwise be a defect:

Gateway.scope is immutable, silently. A patch changing it is accepted, the operation reports done and successful, and the value does not change. Left as an ordinary field, a changed scope would look applied and never be, and the forma would disagree with state permanently. It is dropped from the update body and marked createOnly so a change plans a replacement. Gateway.type fails honestly (Gateway type can not be updated once created) and gets the same treatment.

A Gateway PATCH validates the whole resource, not just the masked fields. Omit ports and the API answers Gateway must have at least one port however narrow the update mask was. The update fixture repeats ports for that reason.

ServiceLbPolicy.autoCapacityDrain drops a false enable on the way back. Sending {"enable": false} reads back as {}, so a policy with the drain turned off would drift on every reconcile and never settle. The response transformer restores the omitted false. Because the field is nested it cannot carry a hasProviderDefault hint, so the fix lives in the transformer; enable is required in the schema, which is what makes restoring it unambiguous — the block reaches a forma only when someone actually wrote the field. The update fixture flips it true → false specifically to exercise this against the live API when conformance is eventually run.

Read-back forms were checked for the mismatch class that has bitten this repo before: name comes back as a full path (handled by ShortNameResponseTransformer), and the project appears as the project id, not the number. No other field changed form between request and response.

serviceBindings dropped from the batch

It was in the original batch of four. Its service field is the only thing that gives the type meaning, and the API now refuses it on both create and update:

Cloud Service Mesh's integration with Service Directory will be deprecated.
Creating service binding resources with service field is disabled.

A binding without it is creatable but inert — a name, a description and labels. Shipping that would mean shipping a resource that cannot express its own purpose, so it is left out rather than added as an empty shell.

Shared-project state change

networkservices.googleapis.com was disabled on development-477117 and I enabled it. It is additive, free, and a hard prerequisite for these types (and for conformance), but it is a change to shared project state and is flagged here rather than left silent. The API also needed roles/networkservices.admin, which was granted out of band to both formae-tester@ and github-deploy@ — CI would 403 on all three types without the latter.

Gates

go build · golangci-lint run · make test-unit · make verify-schema · pkl eval formae-plugin.pkl · bash -n on all tracked shell files — all pass. All six fixtures were evaluated with pkl eval and their rendered Properties diffed against the request bodies the live API actually accepted.

Unit tests cover the path builder (global vs regional, and that a target's region cannot leak into a global URL), native-id extraction from an operation, operation failure mapping, and the autoCapacityDrain round trip.

No hasProviderDefault is used anywhere in these schemas, so schema/provider-default-dispositions.json needs no new rows.

Three types from networkservices.googleapis.com v1, a service the plugin
had no package for: Mesh and ServiceLbPolicy (global) and Gateway
(regional). All three are config objects that provision no capacity, so
the conformance cases they come with are free to run.

Each was exercised live before this landed - create, poll the operation
to completion, GET to confirm the resource actually materialised, PATCH,
delete - because this API accepts a create, hands back an operation and
can still fail it, leaving nothing behind but the operation's error.

Three findings are encoded here that are not visible from the discovery
document, and each would otherwise be a defect:

Gateway.scope is immutable, silently. The API accepts a changed scope in
the update mask, reports the operation as done and successful, and keeps
the old value. Left as a normal field it would look applied and never be,
and the forma would disagree with state forever with nothing to show for
it. It is dropped from the update body and marked createOnly, so a change
plans a replacement - the only thing that actually moves it. Gateway.type
fails the honest way, refused outright, and gets the same treatment.

A Gateway PATCH validates the whole resource rather than only the fields
it masks, so ports must be present on every update: omit it and the API
answers "Gateway must have at least one port" however narrow the mask
was. The update fixture repeats ports for that reason, not by accident.

ServiceLbPolicy.autoCapacityDrain is a message whose only field is a
bool, and the API omits that bool when it is false: {"enable": false}
reads back as {}. A policy that turned the drain off would then drift on
every reconcile and never settle, so the response transformer puts the
omitted false back. The field is nested, so the fix belongs in the
transformer rather than a hasProviderDefault hint; enable is required in
the schema, which is what makes restoring it unambiguous - the block
reaches a forma only when someone wrote the field.

serviceBindings was in the batch and is dropped. Its `service` field is
the only thing that gives the type meaning, and the API now refuses it on
both create and update: "Cloud Service Mesh's integration with Service
Directory will be deprecated. Creating service binding resources with
service field is disabled." What is left is creatable but inert - a name,
a description and labels - so shipping it would mean shipping a resource
that cannot express its own purpose.

The schema file is gateway_networkservices.pkl rather than gateway.pkl
because apigateway already claims that basename and verify-schema rejects
duplicate filenames across directories; the module keeps its natural name,
as spanner/database_spanner.pkl does.

Conformance has NOT been run for these types - see the PR description.
@naxty
naxty force-pushed the naxty/gcpNetworkServicesMesh branch from 0e7f384 to 6481a5b Compare September 3, 2026 09:35
@naxty
naxty marked this pull request as ready for review September 3, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant