Building Providers
Defining the API
APIResourceSchemas, the APIExport, permission claims, and schema versioning.
A provider’s API surface is a set of custom resources that tenants create in their own workspaces. The plumbing is kcp’s: you publish an APIExport referencing APIResourceSchema objects; tenants get the resources via an APIBinding (created by the hub’s Enable flow). Your init subcommand applies all of it — see Anatomy & lifecycle for where that runs.
APIResourceSchemas
An APIResourceSchema is kcp’s workspace-aware equivalent of a CRD. You generate them from your Go types the same way you’d generate CRDs (the kedge repo does this with controller-gen plus a conversion step in make codegen), and bake the YAML files into your image under /etc/kedge/schemas.
Names are version-prefixed and encode the group and resource:
v260522-001.greetings.quickstart.providers.kedge.faros.sh
└───┬────┘ └───┬───┘ └──────────────┬──────────────────┘
revision resource group
Schemas are immutable. Once applied, the body can’t change. To change your API, ship a new file with a bumped revision prefix (v260522-002...) and reference the new name from the export. Old bindings keep working against the old schema until they’re migrated.
Group convention: <provider>.providers.kedge.faros.sh for provider-scoped APIs (quickstart), or a first-class group like code.kedge.faros.sh / infrastructure.kedge.faros.sh for platform providers. Pick one and stay consistent — the group appears in every tenant’s kubectl output.
The APIExport
The SDK’s init bootstrap builds the export from three inputs you pass it: the export name, the schema list (derived from the schema files), and permission claims:
// init_cmd.go — what a provider passes to the SDK bootstrap
import sdkinstall "github.com/faroshq/provider-sdk/install"
err := sdkinstall.Bootstrap(ctx, sdkinstall.Options{
Config: config, // rest.Config from KEDGE_PROVIDER_KUBECONFIG
ExportName: "quickstart.providers.kedge.faros.sh",
WorkspacePath: "root:kedge:providers:quickstart",
SchemasDir: "/etc/kedge/schemas",
Claims: []sdkinstall.PermissionClaim{
{Resource: "configmaps", Verbs: []string{"get", "list", "watch"}},
},
CatalogEntryFile: os.Getenv("KEDGE_CATALOGENTRY_FILE"),
})
Two behaviors worth knowing:
spec.resourcesis merged, not replaced. Some providers (infrastructure) add per-template resource entries to their own export at runtime; the SDK merges by group+name soinitre-runs don’t clobber them.- The endpoint slice is delete-and-recreated on path change.
APIExportEndpointSlice.spec.exportis immutable in kcp; the SDK handles stale slices for you.
Permission claims
Permission claims let your controllers touch resources in the tenant’s workspace that your export doesn’t own — Secrets for credentials, ConfigMaps for settings, another provider’s resources for integration.
apiExport:
name: "kuery.providers.kedge.faros.sh"
permissionClaims:
- group: "edges.kedge.faros.sh"
resource: kubernetesclusters
verbs: [get, list, watch]
tenantScoped: true
Rules of the road:
tenantScoped: truemarks a claim as bounded to the binding tenant’s own workspace — the common, auto-acceptable case. Claims that aren’t tenant-scoped are refused unless a platform admin overrides with thekedge.faros.sh/accept-untrusted-claimsannotation.- First-party claim groups need an identity hash. Claiming a
*.faros.shgroup (like the kuery example above) requires the export’sidentityHash, which the platform admin supplies at deploy time (it’s a Helm value, visible in the hub’s admin view). Built-in Kubernetes types (empty group —configmaps,secrets) need none. - Don’t over-claim. Each claim is rendered in the Enable dialog and is friction plus security review for every tenant. Start with the narrowest set you need.
The claims live in two places: on the APIExport (where kcp enforces them) and mirrored on the CatalogEntry (where the portal renders the Enable dialog). Keep them in sync — the dialog shows the CatalogEntry copy, kcp enforces the export copy.
What the tenant sees
After Enable, your resources are ordinary objects in the tenant workspace:
kubectl get greetings.quickstart.providers.kedge.faros.sh
kubectl apply -f my-greeting.yaml
Status subresource conventions follow the kedge house style: a phase string for at-a-glance state plus conditions[] (with Ready as the summary condition) for machine consumption. Every shipped provider follows this; tenants and the portal rely on it.
Consuming your API from controllers
Defining the API is half the story — reconciling it across all tenant workspaces is the other half, and that’s the virtual workspace guide.