You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cloudrun): align snapshot payload with server's snapshot-examples doc (#4986)
Three changes brings the payload into line with the conventions
documented in the server repo's out-snapshot-examples.txt:
- Added the top-level "type": "cloud-run" literal that every other
env-type report ships explicitly. Without it, the (still-to-come)
CloudRunReport model on the server would only accept the URL
default; with it, the request is unambiguous.
- Renamed the per-artifact "service_name" to "serviceName" (camelCase)
matching the doc's K8S/ECS examples. The existing CLI's ECS code
uses snake_case, but a new type is better off following the doc.
- Dropped the per-artifact "project" and "region" fields. The doc
notes extra="forbid" on every Pydantic model, so unilaterally
picking custom field names risks 422 once the server defines its
CloudRunReport. Project + region are already in the URL and flags,
mirroring how ECS reports don't carry account/region per artifact.
Verified end-to-end against the hello-world-cli-demo project; payload
now matches the doc shape exactly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/handover/2026-04-28-4986-google-cloud-run-1.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,7 @@ GCP test environment (provisioned, ADC already configured on this machine):
34
34
- Package named `internal/cloudrun` (not `gcprun`) to mirror the user-facing command name `snapshot cloud-run`. If GCP integrations expand later we'll either rename or split into `internal/gcp/run`.
35
35
-`internal/cloudrun` reports all revisions referenced in `service.traffic[]` regardless of percent (including 0%). Trade-off: matches the user's framing of "running or could run" without dragging in retired revisions that aren't currently configured for traffic; canary 90/10 splits surface both revisions naturally.
36
36
- Digest extraction follows the ECS pattern (`internal/aws/aws.go:670-693`): use a `@sha256:` substring if present, else leave the digest empty rather than calling Artifact Registry. Registry-lookup mode (analogous to Azure's `--digests-source acr`) is deferred until customers ask for it.
37
-
- Wire payload mirrors `EcsEnvRequest` plus `project` and `region` fields per artifact (so each row is self-describing). Endpoint name is `report/cloud-run` (kebab-case, parallels `report/azure-apps`). Server-side endpoint does not yet exist; the forced dry-run means no network call is made.
37
+
- Wire payload follows the server's `out-snapshot-examples.txt` reference: top-level `{"type": "cloud-run", "artifacts": [...]}` with camelCase per-artifact fields (`revisionName`, `serviceName`, `digests`, `creationTimestamp`). Endpoint is `report/cloud-run` (kebab-case, parallels `report/azure-apps`). Server-side endpoint does not yet exist; the forced dry-run means no network call is made. Initial design (Slice 3) added `project`/`region` per artifact mirroring ECS's `cluster_name`; reverted in Slice 3.5 because the doc specifies `extra="forbid"` on every Pydantic model and project/region are derivable from the URL + flags.
38
38
- Command depends on a local `cloudRunLister` interface and a package-level `newCloudRunClient` variable so tests can substitute a stub without touching ADC. The seam stays in `cmd/kosli/snapshotCloudRun.go` rather than being exposed from `internal/cloudrun` — keeps the public package surface minimal.
39
39
- Error classification (`Classify`) lives in `internal/cloudrun` (GCP knowledge belongs to the package) but is *applied* at the command layer, not inside `Client.ListServices`. Why: applying it inside `ListServices` would double-wrap real-call errors when the command also classified them, and bypass the friendly path entirely for stub-driven tests. Calling it once at the command boundary covers both real and stub error sources.
40
40
@@ -46,7 +46,8 @@ Slice plan (each slice is a separate, independently-mergeable branch):
-[x]**Slice 2:** Internal `internal/cloudrun` package — wraps `cloud.google.com/go/run/apiv2` to list services in project+region; unit-tested with a fake. Done 2026-04-28: `Client.ListServices` returns `Service{Name, URI, Revisions}` with one `Revision{Name, Digests, CreatedAt}` per traffic-configured revision (any percent including 0%, with `LATEST` resolved via `LatestReadyRevision` and dupes removed). Digest extraction mirrors the ECS fallback (`@sha256:` parse, else empty string). 9 unit tests passing.
49
-
-[x]**Slice 3:** End-to-end happy path — wire the package into `RunE`, build the snapshot payload, POST to the server `cloud-run` endpoint (still dry-run only). Done 2026-04-28: command now calls `cloudrun.New` + `ListServices`, builds an `EnvRequest` via `ToEnvRequest(services, project, region)`, and submits PUT `report/cloud-run` via `kosliClient.Do` (dry-run forced, so no network call leaves the client). Tested against the real `hello-world-cli-demo` GCP project — emits a digest-pinned artifact for the running `hello-world` service.
49
+
-[x]**Slice 3:** End-to-end happy path — wire the package into `RunE`, build the snapshot payload, POST to the server `cloud-run` endpoint (still dry-run only). Done 2026-04-28: command now calls `cloudrun.New` + `ListServices`, builds an `EnvRequest` via `ToEnvRequest(services)`, and submits PUT `report/cloud-run` via `kosliClient.Do` (dry-run forced, so no network call leaves the client). Tested against the real `hello-world-cli-demo` GCP project — emits a digest-pinned artifact for the running `hello-world` service.
50
+
-[x]**Slice 3.5:** Align payload with the server's snapshot-examples doc. Done 2026-04-28: added top-level `"type": "cloud-run"`; renamed `service_name` → `serviceName` (camelCase, matching the doc's K8S/ECS examples); dropped per-artifact `project` and `region` (would be rejected by `extra="forbid"` once the server defines a `CloudRunReport` model — they're identifiable from the URL + flags anyway, like ECS region).
50
51
-[x]**Slice 4:** Filtering flags — `--services`, `--services-regex`, `--exclude`, `--exclude-regex`. Done 2026-04-28: backed by `filters.ResourceFilterOptions` (same struct ECS uses); 4 mutex pairs validated in `PreRunE`. Filter is applied in the command after `cloudrun.ListServices` returns — services excluded by name still cost their revision-fetch round-trips. If that becomes a bottleneck, push the filter into `cloudrun.ListServices` so excluded services skip the per-revision API calls.
51
52
-[x]**Slice 5:**~~Multi-revision / traffic splitting — handle services with multiple active revisions and services with no active revisions.~~ Dropped 2026-04-28: multi-revision (traffic splitting), `LATEST` resolution, and dedup were all completed in Slice 2; the only remaining edge case (services with no active revisions emitting a placeholder artifact) was deferred until the server-side wire contract is defined, since picking the format unilaterally now risks rework. Re-open as a small slice once the server contract lands.
52
53
-[x]**Slice 6:** Auth error UX — clear messages for ADC / `GOOGLE_APPLICATION_CREDENTIALS` failures and for missing project/region. Done 2026-04-28: `cloudrun.Classify(err, project, region)` maps gRPC `Unauthenticated` → ADC advice, `PermissionDenied` → `roles/run.viewer` advice, `NotFound` → "project or region not found"; other codes pass through. Auth message names all three credential sources (env var, `gcloud auth application-default login`, GCE/GKE metadata server / Workload Identity) since the production deployment is a GKE cron job. `cloudrun.New(ctx)` errors get a generic "GCP client setup failed" prefix.
0 commit comments