Appearance
Author a Blueprint
A Blueprint is a catalog entry; an operator provisions a Resource from one of its published versions. The catalog starts empty. The fastest way to fill it is to register an external OCI catalog as a source and import the Blueprints you need; hand-authoring the manifests yourself is the advanced path covered at the end of this guide.
Prerequisites
- An authenticated session — see Log in with plexctl.
- The platform-level
managerelation, which gates registration and import.
Bind the control-plane URL the steps reference:
shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"Import from a catalog source
1. Register the catalog source
shell
plexctl blueprint catalog add \
--server "${PLEXSPHERE_URL}" \
--name "Platform catalog" \
--ref ghcr.io/plexsphere/catalog:v1.2.3 \
--verify-identity '^https://github\.com/plexsphere/catalog/\.github/workflows/release\.yml@refs/tags/v.*$' \
--verify-issuer https://token.actions.githubusercontent.comPin --verify-identity to the exact repository, workflow file, and tag ref of the catalog's release pipeline, as shown above. A broader pattern such as ^https://github\.com/plexsphere/.*$ accepts a signature from any workflow in the organization — including one an attacker could trigger to sign attacker-controlled content — which defeats the point of pinning the signer.
The command prints the new source's id. Pin the bundle by immutable digest with --digest ghcr.io/plexsphere/catalog@sha256:... in place of --ref, or have the source re-resolve its tag on an interval with --track 15m. Scope the registration to one Domain with --domain-id, and point a private registry at its pull secret with --registry-credential-ref namespace/name. Use --insecure-unsigned in place of the --verify-identity/--verify-issuer pair only for a registry you fully trust.
2. Browse what the source offers
shell
plexctl blueprint catalog browse <source-id> \
--server "${PLEXSPHERE_URL}"Nothing is imported yet — the response lists the slug, version, and provider kinds of each Blueprint the bundle offers.
3. Import the Blueprints you want
Import a chosen subset by slug, or --all to import everything the source offers:
shell
plexctl blueprint import <source-id> managed-postgres managed-redis \
--server "${PLEXSPHERE_URL}"Each requested slug gets one outcome row. A slug already owned by a different source reports conflict; an identical re-import reports unchanged. These are per-Blueprint, not a batch failure, so the command still exits zero.
4. Confirm
shell
plexctl blueprint get <blueprint-id> \
--server "${PLEXSPHERE_URL}" \
--output yamlThe response carries the imported Blueprint with its versions array.
Advanced: hand-author a Blueprint
When a Blueprint you need is not published in any catalog, register it and publish its first version from a pair of Crossplane manifests yourself. The registrar is granted owner on the new Blueprint, which confers the publish permission used in step 3. You need three JSON files: the Crossplane XRD, the Composition, and the parameter-schema document. The Composition's compositeTypeRef must name the composite type the XRD declares.
1. Register the Blueprint
shell
plexctl blueprint create \
--server "${PLEXSPHERE_URL}" \
--slug managed-postgres \
--display-name "Managed PostgreSQL" \
--description "A managed PostgreSQL database instance."The command prints the new Blueprint's id. A duplicate slug exits non-zero with blueprint_slug_conflict.
2. Prepare the version files
The parameter-schema file is the canonical document the catalog stores:
json
{
"parameters": [
{ "name": "storage_gb", "type": "integer", "required": true },
{ "name": "high_availability", "type": "boolean", "required": false }
]
}xrd.json and composition.json carry the Crossplane manifests as JSON objects. A minimal pair:
json
{
"apiVersion": "apiextensions.crossplane.io/v2",
"kind": "CompositeResourceDefinition",
"metadata": { "name": "xmanagedpostgres.example.org" },
"spec": { "group": "example.org", "names": { "kind": "XManagedPostgres", "plural": "xmanagedpostgres" } }
}json
{
"apiVersion": "apiextensions.crossplane.io/v1",
"kind": "Composition",
"metadata": { "name": "managed-postgres-composition" },
"spec": { "compositeTypeRef": { "apiVersion": "example.org/v1", "kind": "XManagedPostgres" } }
}3. Publish a version
Use the Blueprint id from step 1:
shell
plexctl blueprint version create \
--server "${PLEXSPHERE_URL}" \
--blueprint-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0b0 \
--version 1.0.0 \
--injection-strategy helm-values \
--provider-kinds aws,hetzner \
--xrd-file ./xrd.json \
--composition-file ./composition.json \
--parameter-schema-file ./parameter-schema.jsonThe version is immutable; re-publishing the same --version exits with blueprint_version_exists.
The
ownergrant from step 1 is written asynchronously. If you script register → publish back-to-back and the publish returns403, retry it after a short delay — the grant has not yet propagated.
Do this in the Console
Both hand-authoring steps have a browser equivalent in the Console. Open the Blueprints page under the Blueprints sidebar group.
Step 1 (blueprint create) is the Register Blueprint button above the table. It requires the platform-level manage relation. The dialog takes a kebab-case slug, a display name, an optional description, and a Scope picker that defaults to catalogue-wide and offers every Domain you can read. A duplicate slug is rejected inline on the Slug field as blueprint_slug_conflict without closing the dialog. On success the new Blueprint appears in the list, and you are granted owner on it — the grant that confers publish.
Step 3 (blueprint version create) is the Publish version action in the Blueprint detail masthead. It requires the publish permission the owner grant confers. The dialog takes a version label; a checkbox group over the provider kinds (aws, azure, gcp, hetzner, openstack); an injection strategy (cloud-init-user-data, helm-values, provider-secret); and the XRD, Composition, and parameter-schema documents as three JSON entries you can paste or load from a file. Each document is checked for being a JSON object before it is sent, so a syntax error surfaces on its own field. The server's validation rejections map back to their field — invalid_provider_kind, invalid_injection_strategy, invalid_parameter_schema, and invalid_manifest — and re-publishing a version shows blueprint_version_exists. On success the new version card appears on the Versions tab.
The
ownergrant is written asynchronously, so right after registering a Blueprint the Publish version control — or a submit — may be refused for a moment. The disabled tooltip and the residual denial both say so; retry shortly.