Skip to content

feat(plugin): populate ProvisionRequest.dependency_outputs_json from params.ResourceOutputs - #571

Merged
universe-ops merged 4 commits into
mainfrom
feat/plugin-dependency-outputs
Nov 8, 2026
Merged

universe-ops merged 4 commits into
mainfrom
feat/plugin-dependency-outputs

Conversation

@smecsia

@smecsia smecsia commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Mirrors the parent_info_json wire-protocol fix in #567. Fills the gap that
broke gcp-docker-image-push (and will break any future plugin resource
that reads a sibling's output) post the GCP extraction. Fleet-wide, not
GCP-specific.

What

  • proto: new bytes dependency_outputs_json = 12 on ProvisionRequest.
    The pre-existing flat dependency_outputs = 5 field couldn't carry the
    per-value secret flag or the nested outputs map plugin: let a sibling read a plugin-owned resource's outputs, not just its URN #569 introduced; the JSON
    blob does.
  • sidecar (pkg/clouds/pulumi/plugin_sidecars.go):
    makeResourceSidecar walks params.ResourceOutputs, picks up every
    entry whose .Ref is a *api.PluginResourceOutput (what
    harvestPluginOutputs produces post-plugin: let a sibling read a plugin-owned resource's outputs, not just its URN #569), and json.Marshals the
    result. In-tree typed Refs are dropped silently (the plugin can't
    unmarshal unknown Go struct shapes) — matches the pre-field silent-drop
    behaviour.
  • runtime (pkg/plugin/runtime/compute.go): new
    DependencyOutputsFromProvisionRequest(req) helper, mirror of
    ParentInfoFromProvisionRequest. Decodes into
    map[string]*api.ResourceOutput with Ref: *api.PluginResourceOutput,
    which is exactly what sibling-reading bodies already type-assert on
    after plugin: let a sibling read a plugin-owned resource's outputs, not just its URN #569.
  • tests: three runtime tests (round-trip / empty-means-nil /
    malformed-JSON) + two sidecar tests (packs mixed inputs correctly /
    empty outputs → empty field).

Why

Live-caught 2026-11-08 by the sc-plugin-gcp phase-5 tier-2 smoke:

plugin "gcp" Provision("gcp-docker-image-push", "tier2-nginx-push"):
  rpc error: expected registry resource "tier2-registry" to be present
  in outputs for "tier2-nginx" in "staging"

The in-tree code read params.ResourceOutputs[dep] and type-asserted on
a sibling resource's internal typed struct. Post-extraction, the map was
empty because the sidecar never populated the wire field. Any plugin
resource across the fleet that implements api.WithParentDependencies
and reads a sibling output hits the same gap.

Backwards compatibility

Additive proto field. Older plugins ignore it and get nil
params.ResourceOutputs — the current (broken) silent-drop shape. Newer
plugins receive the typed map. Wire-protocol version unchanged.

Companion follow-up

sc-plugin-gcp's docker_image.go still type-asserts on the internal
*ArtifactRegistryOut; the sc-plugin-gcp side of the fix will switch it
to *api.PluginResourceOutput and re-read the URL via
.Outputs["url"].Value.(string) once this merges + an api release cuts.
Tier-2 smoke will then put docker-image-push back in the fixture
end-to-end.

Roadmap

  • forge/docs/roadmap/items/sc-plugin-dependency-outputs-not-populated.md

Test plan

  • go build ./...
  • go test ./pkg/plugin/runtime/... ./pkg/clouds/pulumi/... -count=1 — all
    green locally including the 5 new tests
  • gofumpt + gci (this repo's 6-section config) applied to all
    touched files
  • Downstream sc-plugin-gcp PR switching docker_image.go to the
    PluginResourceOutput shape, then tier-2 smoke re-runs with
    docker-image-push live

…params.ResourceOutputs

Mirrors the `parent_info_json` wire-protocol fix in #567. The
pre-existing `dependency_outputs = 5` field on ProvisionRequest was
declared (flat `map<string, string>`) but never populated by any
sidecar — the string-only shape couldn't carry the per-value secret
flag or the nested outputs map #569 introduced. A new JSON-blob field
`dependency_outputs_json = 12` carries `map[resourceName]*api.PluginResourceOutput`
across the bus.

## What

- **proto**: new `bytes dependency_outputs_json = 12` on ProvisionRequest
  with doc comment pointing at the roadmap item and the #569 shape it
  carries.
- **sidecar**: `makeResourceSidecar`'s ProvisionRequest literal
  populates the field by walking `params.ResourceOutputs` and picking
  up every entry whose `.Ref` is a `*api.PluginResourceOutput` (the
  shape `harvestPluginOutputs` publishes post-#569). In-tree
  dependencies (typed Go struct Refs) are dropped silently — the plugin
  side can't unmarshal unknown struct shapes, and the receiving
  Provision body already errors clearly when an expected sibling is
  absent from its outputs map.
- **runtime**: new `DependencyOutputsFromProvisionRequest(req)` helper
  sits next to `ParentInfoFromProvisionRequest` in `pkg/plugin/runtime`.
  Decodes the JSON into `map[string]*api.ResourceOutput` with
  `Ref: *api.PluginResourceOutput`, which is exactly what sibling
  consumers' Provision bodies already type-assert on post-#569. Plugin
  dispatch threads this onto `ProvisionParams.ResourceOutputs` before
  the body runs.
- **tests**: `runtime.DependencyOutputsFromProvisionRequest` round-trip
  + empty-means-nil + malformed-JSON rejection. Sidecar packing test
  verifies mixed input (one plugin-owned, one in-tree) lands only the
  plugin-owned entry in the outbound JSON.

## Why

Surfaced 2026-11-08 by the sc-plugin-gcp phase-5 tier-2 smoke:
`gcp-docker-image-push` reads the artifact-registry URL via
`params.ResourceOutputs[registryResource].Ref.(*ArtifactRegistryOut)`
inside its Provision body. In-tree, SC core populated that map
before dispatching; post-extraction the map arrived empty and the
plugin errored with `expected registry resource %q to be present in
outputs for %q in %q`. Any plugin resource across the fleet that
depends on another resource's output breaks the same way post-
extraction — not GCP-specific.

## Backwards compatibility

Additive proto field. Older plugins ignore the field and see nil
`params.ResourceOutputs` — same silent-drop shape that today's broken
state exhibits. Newer plugins receive the typed map. Preserves wire-
protocol version.

## Follow-up (not in this PR)

sc-plugin-gcp's `docker_image.go` currently type-asserts
`.Ref.(*ArtifactRegistryOut)` — the internal GCP struct. Switch it
to the `*api.PluginResourceOutput` shape (read the URL via
`.Outputs["url"].Value.(string)` or the `api.PluginOutputsOf` helper)
in a sc-plugin-gcp PR once this merges + an api release cuts.

## Roadmap

- `forge/docs/roadmap/items/sc-plugin-dependency-outputs-not-populated.md`

## Test plan

- [x] `go build ./...`
- [x] `go test ./pkg/plugin/runtime/... ./pkg/clouds/pulumi/... -count=1` — all
  green locally including the three new runtime tests + two new sidecar tests.
- [ ] Downstream sc-plugin-gcp update: switch docker_image.go to the
  PluginResourceOutput shape; rerun tier-2 smoke with docker-image-push
  back in the fixture end-to-end.

Signed-off-by: Ilya Sadykov <smecsia@gmail.com>
@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Semgrep Scan Results

Repository: api | Commit: ada18e5

Check Status Details
⚠️ Semgrep Warning 11 warning(s), 15 total

Scanned at 2026-11-08 18:23 UTC

@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Security Scan Results

Repository: api | Commit: ada18e5

Check Status Details
✅ Secret Scan Pass No secrets detected
✅ Dependencies (Trivy) Pass 1 total (no critical/high)
✅ Dependencies (Grype) Pass 1 total (no critical/high)
📦 SBOM Generated 517 components (CycloneDX)

Scanned at 2026-11-08 18:23 UTC

@universe-ops
universe-ops enabled auto-merge (squash) November 8, 2026 18:13
@universe-ops
universe-ops merged commit 4c33bd3 into main Nov 8, 2026
19 of 20 checks passed
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.

3 participants