Skip to content

fix(infrastructure): portal fetches + parses template sampleValues (one-click pre-fill)#344

Merged
mjudeikis merged 12 commits into
mainfrom
fix-portal-template-sample-prefill
Jun 22, 2026
Merged

fix(infrastructure): portal fetches + parses template sampleValues (one-click pre-fill)#344
mjudeikis merged 12 commits into
mainfrom
fix-portal-template-sample-prefill

Conversation

@mjudeikis

Copy link
Copy Markdown
Contributor

Problem

PR #343 added spec.sampleValues to the application template (and the TemplateSpec CRD field) to pre-fill the provision form for the one-click demo. ProvisionPage already seeds the form from template.sampleValuesbut the form stayed empty, because the portal never actually received the value.

Two gaps in api.ts:

  1. Neither template query selected it. Both refreshIndex (catalog) and getTemplate (provision page) GraphQL queries selected ... schema but not sampleValues, so it was never fetched → always undefined.
  2. It wouldn't have parsed anyway. sampleValues is a preserve-unknown-fields field, which the gateway returns as a JSONString scalar (exactly like schema). templateFromGQL parses schema from its string form but cast sampleValues straight to an object — a string would have spread into a char-indexed object in ProvisionPage.

Fix

  • Add sampleValues to both template GraphQL queries.
  • Parse the JSONString form in templateFromGQL, mirroring the existing schema handling (accepts an object too, defensively).

Verification

  • npm run build (vite) succeeds; confirmed the rebuilt bundle contains schema sampleValues (query) + the parse logic.
  • dist/main.js is gitignored and rebuilt at go build time, so only the api.ts source change is committed.

After this, the application template's provision form arrives pre-filled (name=demo, example frontend/backend images, oidc.mode=none) — the actual one-click demo.

Note: the form still renders nested objects (oidc/database/expose) as raw inputs and shows the computed credentialsSecretName — the pre-fill works regardless (top-level image/name fields populate; the nested oidc object rides along in the submitted values). The form-polish (hide computed + render nested groups) remains a separate follow-up.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 22, 2026 13:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…esilient)

The application template ships spec.sampleValues (PR #343) to pre-fill the
provision form for the one-click demo, and ProvisionPage already seeds the form
from template.sampleValues — but the portal never received the value, so the form
stayed empty.

Gaps in api.ts:
- neither template GraphQL query (refreshIndex, getTemplate) selected
  sampleValues, so it was never fetched;
- sampleValues is a preserve-unknown-fields field the gateway returns as a
  JSONString scalar (same as schema), but templateFromGQL cast it straight to an
  object instead of parsing it.

Selecting sampleValues unconditionally is itself a trap: a gateway whose schema
was built from an older CRD (pre-sampleValues) rejects the field with a hard
"Cannot query field sampleValues" error that breaks the whole catalog/provision
page. So select it optimistically via a shared templateSpec() selection and, on
that specific error, remember it's unsupported and retry without it (degrading to
no pre-fill). Once the updated Template CRD is deployed, the field resolves and
the form pre-fills. dist/main.js is gitignored and rebuilt at go build time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mjudeikis mjudeikis force-pushed the fix-portal-template-sample-prefill branch from a14e984 to b4160a8 Compare June 22, 2026 14:00
…/crds)

The operator embeds the Template CRD from install/crds/ (//go:embed in
install/crds.go) and applies it into the kcp provider workspace, but
`make codegen-infrastructure-provider` only wrote the regenerated CRD to
config/crds/. The two drifted: config/crds gained spec.sampleValues (PR #343)
while the embedded install/crds copy stayed stale — so the operator installed a
Template CRD without sampleValues, and kcp silently pruned the field on every
SeedTemplates write ("unknown field spec.sampleValues"). The portal then had
nothing to pre-fill from, regardless of the api.ts fix.

- Sync install/crds/…templates.yaml with the generated schema, and make the
  codegen target copy it on every run so they can't drift again.
- Tiltfile: watch apis/, install/, config (via install), controller/, operator/,
  backend/, apps/ for the infrastructure resource. They were unwatched, so edits
  to the CRD schema / seed templates / controller never triggered a rebuild and
  the running operator kept a stale embed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 14:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

mjudeikis and others added 2 commits June 22, 2026 17:22
With sampleValues now reaching the form, the nested oidc object rendered as a
single "[object Object]" text input (DynamicForm only handled leaf fields).

DynamicForm now:
- renders nested objects (type: object with properties) as a labeled group via
  recursion (oidc/database/expose show their sub-fields), and
- hides platform-computed fields (description starts with "Computed by the
  platform"), so expose.fqdn and credentialsSecretName no longer appear as
  editable inputs.

dist/main.js is gitignored and rebuilt at go build time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pressions

kro compiles the template expressions to CEL, and CEL has no string+int
concatenation. Two URL values interpolated an int port directly into a string,
so the RGD failed to validate ("found no matching overload for '_+_' applied to
'(string, int)'"), blocking every Application instance:
- frontendDeployment BACKEND_URL: http://<name>-backend:<backendPort>
- oauthDeployment --upstream:     http://<name>-frontend:<frontendPort>
  (compiled even though includeWhen-excluded — kro validates all resources).

Wrap the port refs in CEL string(...). The standalone ${...Port} values on
int fields (containerPort/port/targetPort) are unchanged — those resolve to int
directly with no concatenation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 14:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

mjudeikis and others added 2 commits June 22, 2026 17:31
This kro fork forbids ${schema.*} in instance status expressions (buildStatusSchema
restricts them to resource references only — "status field \"host\" expression
\"schema.spec.expose.fqdn\": references unknown identifiers: [schema]"), which
blocked the RGD.

Read the controller-stamped fqdn from the Ingress rule (ingress.spec.rules[0].host)
instead of schema.spec.expose.fqdn for status.url/host/redirectURL. The Ingress is
always created (only its backend changes by oidc.mode), so the reference is valid
in both modes; resource refs are what kro status allows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t serve

run-provider-infrastructure (the Tiltfile serve_cmd) set KRO_KUBECONFIG but not
KEDGE_APP_BASE_DOMAIN, so the Application instance controller stayed disabled
(it needs both). Provisioned Application instances therefore never got their
spec.expose.fqdn stamped, and kro couldn't materialize them (no Ingress host,
no tenant namespace).

Default KEDGE_APP_BASE_DOMAIN (apps.127.0.0.1.sslip.io) and KEDGE_INGRESS_CLASS
(nginx) in the dev target, both overridable from the environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 14:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

mjudeikis and others added 2 commits June 22, 2026 17:42
…roller

The Application controller built its multicluster manager with a bare
runtime.NewScheme(), but the apiexport provider builds a typed cache over
APIExportEndpointSlice to discover the virtual-workspace URL. So the controller
failed to start:

  application controller: NOT started: creating apiexport multicluster provider:
  ... no kind is registered for the type v1alpha1.APIExportEndpointSlice in scheme

— which meant provisioned Application instances never got spec.expose.fqdn
stamped and kro never materialized them.

Register apis.kcp.io v1alpha1 + v1alpha2 into the scheme (mirroring the kuery
engagement controller, which uses the same apiexport rail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fs resolve

The RGD references ${schema.spec.database.version} (and oidc.mode) unconditionally,
but an instance that omits the database/oidc object never gets the nested defaults
applied — CRD defaulting does not instantiate an absent object without default: {}.
Result: kro fails to render the Postgres StatefulSet ("no such key: database"), so
the backend crash-loops with no DB.

Add default: {} to database/expose/oidc so omitted objects are instantiated and
their nested defaults (version=16, oidc.mode=none, etc.) apply. Also add database
to sampleValues so the provision form pre-fills it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

mjudeikis and others added 2 commits June 22, 2026 20:34
Adopt the convention that the application's API lives at /api and the UI at /,
on a single public URL, with the path routing done at the exposure layer so the
backend is never exposed bypassing the gate:

- mode=none: a second Ingress (gated to none) adds /api → backend alongside the
  "/" → frontend rule (same host; controller merges, longest-prefix wins).
- byo: oauth2-proxy gets a second --upstream (/api/ → backend, / → frontend), so
  both stay behind authentication.

The example frontend becomes a UI-only SPA that calls /api/messages from the
browser (same origin) — no more server-side BACKEND_URL proxy; the env is
dropped from the frontend Deployment. Backend already serves /api/*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r MCP

Templates today are UI-shaped (displayName/description/schema for the form). Add
a machine-facing spec.agent block so LLM agents discovering templates over MCP
know how to use them and where outputs land:

- usage (markdown): what it provisions, when to use it, how it's exposed (URL/
  ingress/auth), and how to operate it.
- prerequisites: conditions to satisfy first (e.g. the cloud-credentials Secret
  + key for byo OIDC).
- outputs: where results land (status.url, the <name>-db-credentials Secret + uri
  key, redirectURL, per-tier readiness).

Wired API → DTO → MCP: TemplateSpec.Agent (CRD), kro.Template.Agent, and
templateFromUnstructured reads spec.agent. Populated for the application template.
Regenerated CRD/deepcopy (config + embedded install/crds). The portal ignores it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

mjudeikis and others added 2 commits June 22, 2026 20:46
…e contract

The agent supplies only the two images, so spell out the contract those images
must honor instead of pointing at a Secret a human would wire up:
- BACKEND: reads DATABASE_URL env (postgres:// URI, db appdb / user appuser),
  PostgreSQL version = database.version; DB starts empty so create schema on
  startup and retry the initial connection; listen on backendPort, serve /api/*.
- FRONTEND: listen on frontendPort, serve /, call /api from the browser
  same-origin (no separate backend URL); no DB access.
Reframe the db-credentials Secret as informational (the image just reads
DATABASE_URL, never the Secret).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ages upfront

Fill the gaps an agent needs to produce both images before provisioning:
- both: bind 0.0.0.0 (not 127.0.0.1); stateless (state only in Postgres).
- backend: DATABASE_URL has no sslmode + Postgres TLS is off → connect
  sslmode=disable; /api path is PRESERVED (not stripped); the API shape is the
  agent's own design (it builds both tiers).
- frontend: no env injected; serve / and fetch /api same-origin.
Verified against the RGD: only DATABASE_URL is injected (backend), nothing into
the frontend, no probes; URI is postgres://appuser:<pw>@<host>:5432/appdb.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 22, 2026 17:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mjudeikis mjudeikis merged commit 5bacc12 into main Jun 22, 2026
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.

2 participants