Skip to content

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 manage relation, 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.com

Pin --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 yaml

The response carries the imported Blueprint with its versions array.

5. Install it on the management clusters ​

An imported version composes nothing until its XRD and Composition are on the cluster where Crossplane runs. The control plane never applies them, so apply both, at exactly the version you imported, to every management cluster before a Project provisions from it. For a blueprint of the official catalog, take the files from catalog/<slug>/ of github.com/plexsphere/blueprints at the tag the source tracks:

shell
kubectl apply -f catalog/<slug>/xrd.yaml -f catalog/<slug>/composition.yaml

Until the XRD is there, a resource declared from the version stays in Pending. Install the blueprints you offer names the log line that shows it and how to keep the XRD out of a GitOps prune.

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.json

The version is immutable; re-publishing the same --version exits with blueprint_version_exists.

The owner grant from step 1 is written asynchronously. If you script register → publish back-to-back and the publish returns 403, retry it after a short delay — the grant has not yet propagated.

A published version composes nothing until its XRD and Composition are on the management clusters: apply your xrd.json and composition.json there as step 5 of the import flow above shows.

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 owner grant 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.

See also ​