fix(infrastructure): portal fetches + parses template sampleValues (one-click pre-fill)#344
Merged
Merged
Conversation
…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>
a14e984 to
b4160a8
Compare
…/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>
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>
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>
…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>
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>
…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>
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.
Problem
PR #343 added
spec.sampleValuesto theapplicationtemplate (and theTemplateSpecCRD field) to pre-fill the provision form for the one-click demo.ProvisionPagealready seeds the form fromtemplate.sampleValues— but the form stayed empty, because the portal never actually received the value.Two gaps in
api.ts:refreshIndex(catalog) andgetTemplate(provision page) GraphQL queries selected... schemabut notsampleValues, so it was never fetched → alwaysundefined.sampleValuesis apreserve-unknown-fieldsfield, which the gateway returns as a JSONString scalar (exactly likeschema).templateFromGQLparsesschemafrom its string form but castsampleValuesstraight to an object — a string would have spread into a char-indexed object inProvisionPage.Fix
sampleValuesto both template GraphQL queries.templateFromGQL, mirroring the existingschemahandling (accepts an object too, defensively).Verification
npm run build(vite) succeeds; confirmed the rebuilt bundle containsschema sampleValues(query) + the parse logic.dist/main.jsis gitignored and rebuilt atgo buildtime, so only theapi.tssource 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.🤖 Generated with Claude Code