From eb50cfcfc400bde024054838df2b8d979fe1b7cd Mon Sep 17 00:00:00 2001 From: Leidy Garzon Date: Mon, 31 Aug 2026 17:24:41 +0200 Subject: [PATCH 1/3] docs: document Marketplace provider registration and content-for label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add steps 5 and 6 to bootstrap-provider.md covering the three-resource requirement for Marketplace visibility (ProviderMetadata + APIExport with at least one schema + ContentConfiguration, all linked by the ui.platform-mesh.io/content-for label) and the bind ClusterRole/ ClusterRoleBinding required for the Enable button to work. Add the ui.platform-mesh.io/content-for label to the metadata-catalog labels table with a cross-reference to the new how-to steps. These gaps were discovered while validating the HSP → Platform Mesh extension migration end-to-end on a local kind cluster. Signed-off-by: Leidy Garzon --- how-to-guides/bootstrap-provider.md | 146 +++++++++++++++++++++++- reference/resources/metadata-catalog.md | 5 +- 2 files changed, 147 insertions(+), 4 deletions(-) diff --git a/how-to-guides/bootstrap-provider.md b/how-to-guides/bootstrap-provider.md index 0a68c6e8b..161b1f4c3 100644 --- a/how-to-guides/bootstrap-provider.md +++ b/how-to-guides/bootstrap-provider.md @@ -71,7 +71,149 @@ export PROVIDER_KUBECONFIG=provider-kubeconfig.yaml kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f ``` -## Step 5: Wire the kubeconfig into your service controllers +## Step 5: Register your provider in the Marketplace + +For your provider to appear in the Platform Mesh Marketplace, apply three resources to the provider workspace, all linked by the same `ui.platform-mesh.io/content-for` label set to the `ProviderMetadata` name: + +| Resource | API group | Purpose | +| --- | --- | --- | +| `ProviderMetadata` | `ui.platform-mesh.io/v1alpha1` | Marketplace card — name, description, icon, contacts, documentation, support links. | +| `APIExport` | `apis.kcp.io/v1alpha1` | Publishes the provider's API group. **Must reference at least one `APIResourceSchema`** — the Marketplace skips exports with no schemas. UI-only providers that expose no real API still need a placeholder schema so the export is considered established. | +| `ContentConfiguration` | `ui.platform-mesh.io/v1alpha1` | Portal navigation fragment (sidebar nodes, micro-frontend URL). | + +All three resources must carry the same label: + +```yaml +labels: + ui.platform-mesh.io/content-for: +``` + +where `` matches `metadata.name` of the `ProviderMetadata`. + +A minimal example for a UI-only provider: + +```yaml +# providermetadata.yaml +apiVersion: ui.platform-mesh.io/v1alpha1 +kind: ProviderMetadata +metadata: + name: my-service + labels: + ui.platform-mesh.io/content-for: my-service +spec: + displayName: My Service + description: Short description shown in the Marketplace card. + tags: [example] + contacts: + - displayName: My Team + email: my-team@example.com + role: [Owner] + documentation: + - displayName: Documentation + url: https://docs.example.com + icon: + light: + url: https://example.com/icon-light.svg + dark: + url: https://example.com/icon-dark.svg +``` + +```yaml +# apiresourceschema.yaml (placeholder for UI-only providers) +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + name: v1.myresources.my-service.example.com +spec: + group: my-service.example.com + names: + kind: MyResource + plural: myresources + scope: Cluster + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +``` + +```yaml +# apiexport.yaml +apiVersion: apis.kcp.io/v1alpha1 +kind: APIExport +metadata: + name: my-service.example.com + labels: + ui.platform-mesh.io/content-for: my-service +spec: + latestResourceSchemas: + - v1.myresources.my-service.example.com +``` + +```yaml +# contentconfiguration.yaml +apiVersion: ui.platform-mesh.io/v1alpha1 +kind: ContentConfiguration +metadata: + name: my-service-ui + labels: + ui.platform-mesh.io/content-for: my-service + ui.platform-mesh.io/entity: core_platform-mesh_io_account +spec: + remoteConfiguration: + url: https://example.com/portal-config.json + contentType: json +``` + +Apply them using the provider kubeconfig: + +```bash +export PROVIDER_KUBECONFIG=provider-kubeconfig.yaml +kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f apiresourceschema.yaml +kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f apiexport.yaml +kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f providermetadata.yaml +kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f contentconfiguration.yaml +``` + +## Step 6: Grant bind permission + +The Marketplace install flow creates an `APIBinding` in the consumer workspace by calling `bind` on the provider's `APIExport`. Without an explicit grant, the call fails with "no permission to bind to export". Apply a `ClusterRole` and `ClusterRoleBinding` to the provider workspace to allow it: + +```yaml +# rbac-bind.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: my-service-bind +rules: + - apiGroups: ["apis.kcp.io"] + resources: ["apiexports"] + resourceNames: ["my-service.example.com"] + verbs: ["bind"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: my-service-bind +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: my-service-bind +subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +``` + +```bash +kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f rbac-bind.yaml +``` + +After both steps, open the Marketplace in a consumer workspace — the provider card appears and the **Enable** button works. + +## Step 7: Wire the kubeconfig into your service controllers Configure your service controllers to use the provider kubeconfig to watch the `APIExport` virtual workspace and reconcile service consumers. See [Integration paths](/concepts/integration-paths.md) to choose the right mechanism and find the corresponding tutorial. @@ -81,3 +223,5 @@ Configure your service controllers to use the provider kubeconfig to watch the ` - [Provider bootstrap](/concepts/provider-bootstrap.md) - [Integration paths](/concepts/integration-paths.md) - [Service provider persona](/concepts/personas/service-provider.md) +- [Metadata catalog](/reference/resources/metadata-catalog.md) — `ui.platform-mesh.io/content-for` label reference +- [ContentConfiguration](/reference/resources/content-configuration.md) diff --git a/reference/resources/metadata-catalog.md b/reference/resources/metadata-catalog.md index ffb67cfc9..fe95cda7b 100644 --- a/reference/resources/metadata-catalog.md +++ b/reference/resources/metadata-catalog.md @@ -28,11 +28,10 @@ Platform Mesh attaches labels to its own resources and, in some cases, to upstre | Label | Used on | Purpose | | --- | --- | --- | | `core.platform-mesh.io/org` | WorkspaceTypes managed by the account-operator | Scopes a workspace type to a specific organization so child accounts inherit the right RBAC. | +| `ui.platform-mesh.io/content-for` | `ProviderMetadata`, `APIExport`, `ContentConfiguration` | **Required by provider authors.** Links the three resources that make up a Marketplace entry. The value must be the name of the `ProviderMetadata` on all three resources. The virtual-workspaces server joins them by this label when building the `MarketplaceEntry` list. See [Register your provider in the Marketplace](/how-to-guides/bootstrap-provider.md#register-your-provider-in-the-marketplace). | | `ui.platform-mesh.io/entity` | ContentConfiguration | Attaches the configuration to a portal navigation entity (for example, `core_platform-mesh_io_account` to extend Account pages). | | `extensions.openmfp.io` | ContentConfiguration (legacy) | Maps to an `ExtensionClass` in older deployments. New deployments prefer `ui.platform-mesh.io/entity`. | -Provider authors generally do **not** need to set Platform Mesh labels on APIExports or APIBindings — onboarding scripts and operators handle that. - ## Finalizers Finalizers ensure that Platform Mesh resources are torn down in the right order — for example, an Account's IAM Store must be cleaned up before the workspace itself is deleted, otherwise stranded permissions remain in OpenFGA. @@ -71,7 +70,7 @@ This catalog is updated as Platform Mesh component owners contribute the support ## Related - [Account resource](./account-resource.md) — uses the `core.platform-mesh.io` API group and its finalizers -- [ContentConfiguration](./content-configuration.md) — uses the `ui.platform-mesh.io` API group and the `ui.platform-mesh.io/entity` label +- [ContentConfiguration](./content-configuration.md) — uses the `ui.platform-mesh.io` API group and the `ui.platform-mesh.io/entity` and `ui.platform-mesh.io/content-for` labels - [Provider resource](./provider-resource.md) — uses the `providers.platform-mesh.io` API group and its finalizers - [ManagedProvider resource](./managed-provider-resource.md) — uses the `providers.platform-mesh.io` API group and its finalizers - [Account model](/concepts/account-model.md) From bb5cc573bb0662d26702c7100063b80d76ea3893 Mon Sep 17 00:00:00 2001 From: Leidy Garzon Date: Mon, 31 Aug 2026 17:52:16 +0200 Subject: [PATCH 2/3] docs: remove placeholder schema requirement for UI-only providers platform-mesh/platform-mesh#332 fixes the Marketplace filter to use status.identityHash instead of len(latestResourceSchemas), so UI-only providers no longer need a dummy APIResourceSchema to appear in the Marketplace. Update the example accordingly. Signed-off-by: Leidy Garzon --- how-to-guides/bootstrap-provider.md | 30 +++-------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/how-to-guides/bootstrap-provider.md b/how-to-guides/bootstrap-provider.md index 161b1f4c3..2fb955a49 100644 --- a/how-to-guides/bootstrap-provider.md +++ b/how-to-guides/bootstrap-provider.md @@ -78,7 +78,7 @@ For your provider to appear in the Platform Mesh Marketplace, apply three resour | Resource | API group | Purpose | | --- | --- | --- | | `ProviderMetadata` | `ui.platform-mesh.io/v1alpha1` | Marketplace card — name, description, icon, contacts, documentation, support links. | -| `APIExport` | `apis.kcp.io/v1alpha1` | Publishes the provider's API group. **Must reference at least one `APIResourceSchema`** — the Marketplace skips exports with no schemas. UI-only providers that expose no real API still need a placeholder schema so the export is considered established. | +| `APIExport` | `apis.kcp.io/v1alpha1` | Publishes the provider's API group. The Marketplace skips exports whose `status.identityHash` is empty (not yet established by kcp). UI-only providers that expose no CRD can omit `spec.latestResourceSchemas`; the export is still considered established once kcp sets the identity hash. | | `ContentConfiguration` | `ui.platform-mesh.io/v1alpha1` | Portal navigation fragment (sidebar nodes, micro-frontend URL). | All three resources must carry the same label: @@ -119,37 +119,14 @@ spec: ``` ```yaml -# apiresourceschema.yaml (placeholder for UI-only providers) -apiVersion: apis.kcp.io/v1alpha1 -kind: APIResourceSchema -metadata: - name: v1.myresources.my-service.example.com -spec: - group: my-service.example.com - names: - kind: MyResource - plural: myresources - scope: Cluster - versions: - - name: v1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object -``` - -```yaml -# apiexport.yaml +# apiexport.yaml (UI-only: no latestResourceSchemas needed) apiVersion: apis.kcp.io/v1alpha1 kind: APIExport metadata: name: my-service.example.com labels: ui.platform-mesh.io/content-for: my-service -spec: - latestResourceSchemas: - - v1.myresources.my-service.example.com +spec: {} ``` ```yaml @@ -171,7 +148,6 @@ Apply them using the provider kubeconfig: ```bash export PROVIDER_KUBECONFIG=provider-kubeconfig.yaml -kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f apiresourceschema.yaml kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f apiexport.yaml kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f providermetadata.yaml kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f contentconfiguration.yaml From 6f28e629442e7c03757e817974ba14adeca23f4c Mon Sep 17 00:00:00 2001 From: Leidy Garzon Date: Mon, 31 Aug 2026 18:21:34 +0200 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20fix=20content-for=20label=20values?= =?UTF-8?q?=20=E2=80=94=20CC=20uses=20APIExport=20name,=20not=20ProviderMe?= =?UTF-8?q?tadata=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The content-for label has two different values depending on the resource: - ProviderMetadata and APIExport: content-for: (joins them for Marketplace listing, filter.go:219) - ContentConfiguration: content-for: (projects nav into consumer workspace after install, filter.go:144) Previously the doc incorrectly stated all three resources share the same value. Verified against virtual-workspaces/pkg/storage/filter.go and the github provider in local-setup (APIExport has content-for: github, CC has content-for: github.dxp.sap.com). Signed-off-by: Leidy Garzon --- how-to-guides/bootstrap-provider.md | 24 ++++++++++-------------- reference/resources/metadata-catalog.md | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/how-to-guides/bootstrap-provider.md b/how-to-guides/bootstrap-provider.md index 2fb955a49..08b76fc3f 100644 --- a/how-to-guides/bootstrap-provider.md +++ b/how-to-guides/bootstrap-provider.md @@ -73,22 +73,18 @@ kubectl --kubeconfig $PROVIDER_KUBECONFIG apply -f ## Step 5: Register your provider in the Marketplace -For your provider to appear in the Platform Mesh Marketplace, apply three resources to the provider workspace, all linked by the same `ui.platform-mesh.io/content-for` label set to the `ProviderMetadata` name: +For your provider to appear in the Platform Mesh Marketplace and project its navigation into consumer workspaces, apply three resources to the provider workspace. The `ui.platform-mesh.io/content-for` label is the join key, but its **value differs by resource**: -| Resource | API group | Purpose | +| Resource | `content-for` value | Purpose | | --- | --- | --- | -| `ProviderMetadata` | `ui.platform-mesh.io/v1alpha1` | Marketplace card — name, description, icon, contacts, documentation, support links. | -| `APIExport` | `apis.kcp.io/v1alpha1` | Publishes the provider's API group. The Marketplace skips exports whose `status.identityHash` is empty (not yet established by kcp). UI-only providers that expose no CRD can omit `spec.latestResourceSchemas`; the export is still considered established once kcp sets the identity hash. | -| `ContentConfiguration` | `ui.platform-mesh.io/v1alpha1` | Portal navigation fragment (sidebar nodes, micro-frontend URL). | +| `ProviderMetadata` | `` — for example `my-service` | Marketplace card — name, description, icon, contacts, documentation, support links. | +| `APIExport` | `` — same as `ProviderMetadata.name` | The Marketplace filter joins `APIExport` to `ProviderMetadata` by this value. The Marketplace skips exports whose `status.identityHash` is empty (not yet established by kcp). UI-only providers that expose no CRD can omit `spec.latestResourceSchemas`. | +| `ContentConfiguration` | `` — for example `my-service.example.com` | The portal's nav projection reads ContentConfigurations by `content-for: ` from the provider workspace once the APIBinding is installed. | -All three resources must carry the same label: - -```yaml -labels: - ui.platform-mesh.io/content-for: -``` - -where `` matches `metadata.name` of the `ProviderMetadata`. +::: tip Two different values for the same label key +`ProviderMetadata` and `APIExport` share `content-for: ` so the Marketplace can join them. +`ContentConfiguration` uses `content-for: ` (the full API group name) so the portal can project nav nodes into the consumer workspace after install. +::: A minimal example for a UI-only provider: @@ -136,7 +132,7 @@ kind: ContentConfiguration metadata: name: my-service-ui labels: - ui.platform-mesh.io/content-for: my-service + ui.platform-mesh.io/content-for: my-service.example.com ui.platform-mesh.io/entity: core_platform-mesh_io_account spec: remoteConfiguration: diff --git a/reference/resources/metadata-catalog.md b/reference/resources/metadata-catalog.md index fe95cda7b..f90197387 100644 --- a/reference/resources/metadata-catalog.md +++ b/reference/resources/metadata-catalog.md @@ -28,7 +28,7 @@ Platform Mesh attaches labels to its own resources and, in some cases, to upstre | Label | Used on | Purpose | | --- | --- | --- | | `core.platform-mesh.io/org` | WorkspaceTypes managed by the account-operator | Scopes a workspace type to a specific organization so child accounts inherit the right RBAC. | -| `ui.platform-mesh.io/content-for` | `ProviderMetadata`, `APIExport`, `ContentConfiguration` | **Required by provider authors.** Links the three resources that make up a Marketplace entry. The value must be the name of the `ProviderMetadata` on all three resources. The virtual-workspaces server joins them by this label when building the `MarketplaceEntry` list. See [Register your provider in the Marketplace](/how-to-guides/bootstrap-provider.md#register-your-provider-in-the-marketplace). | +| `ui.platform-mesh.io/content-for` | `ProviderMetadata`, `APIExport`, `ContentConfiguration` | **Required by provider authors.** Links the three resources that make up a Marketplace entry and a portal nav projection, but the value differs by resource: `ProviderMetadata` and `APIExport` use the `ProviderMetadata` name (for example `my-service`) so the Marketplace can join them; `ContentConfiguration` uses the `APIExport` name (for example `my-service.example.com`) so the portal can project nav nodes into a consumer workspace after install. See [Register your provider in the Marketplace](/how-to-guides/bootstrap-provider.md#register-your-provider-in-the-marketplace). | | `ui.platform-mesh.io/entity` | ContentConfiguration | Attaches the configuration to a portal navigation entity (for example, `core_platform-mesh_io_account` to extend Account pages). | | `extensions.openmfp.io` | ContentConfiguration (legacy) | Maps to an `ExtensionClass` in older deployments. New deployments prefer `ui.platform-mesh.io/entity`. |