Skip to content

Manage Clouds

A Cloud is a provider account plus its region defaults and the Crossplane provider packages that serve it. This guide drives /v1/clouds with curl; the same operations are available as plexctl cloud subcommands.

Prerequisites

  • An authenticated session — see Log in with plexctl.
  • Provider data, which differs per provider. aws needs endpoint.region and endpoint.partition plus region_defaults.default_region; azure needs endpoint.cloud_environment plus subscription_id and tenant_id; gcp needs endpoint.project plus region_defaults.default_region; hetzner needs endpoint.api_endpoint plus region_defaults.default_location; openstack needs endpoint.auth_url plus region_defaults.default_region. The full table is in the Clouds API reference.

Bind the two placeholders the steps reference. PLEXSPHERE_URL is the control-plane URL; TOKEN reuses the bearer token plexctl login persisted to the default profile (or set PLEXSPHERE_TOKEN directly for a service token):

shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"
TOKEN="$(jq -r '.profiles[.default].token' ~/.config/plexctl/config.json)"

Steps

Create a Cloud

shell
curl --silent --show-error --fail-with-body -X POST \
  -H "Authorization: Bearer ${TOKEN}" -H 'content-type: application/json' \
  "${PLEXSPHERE_URL}/v1/clouds" -d '{
  "slug": "acme-aws-prod",
  "display_name": "Acme AWS Production",
  "provider": "aws",
  "external_id": "123456789012",
  "endpoint": {"region": "eu-central-1", "partition": "aws"},
  "region_defaults": {"default_region": "eu-central-1"},
  "provider_packages": [
    {"source": "xpkg.upbound.io/upbound/provider-aws-ec2", "version": "v2.6.1"},
    {"source": "xpkg.upbound.io/upbound/provider-aws-s3", "version": "v2.6.1"}
  ],
  "provider_config_api_version": "aws.m.upbound.io/v1beta1"
}'

The slug is immutable. provider_packages names the Crossplane provider packages that serve this Cloud, between one and sixteen entries and each source at most once; the reads render it in source-ascending order whatever order you state it in. provider_config_api_version is a single Cloud-level value: the broker renders one ProviderConfig per Cloud, so every declared package serves that one group.

The body above states the configuration in inline mode, one of the two modes a Cloud has. Inline mode carries the provider_packages / provider_config_api_version pair, and both halves are required together. Bundle mode carries neither and names a provider_bundle_id instead, taking both values from a provider bundle that several Clouds share. A body that names provider_bundle_id alongside either inline field is rejected with 400 invalid_cloud_provider_mode; one that states neither mode, or only one half of the inline pair, with 400 invalid_cloud. Authoring a bundle and pointing Clouds at it is covered in Manage provider bundles.

A PATCH carrying provider_packages replaces the whole set, so restate every package the Cloud should keep; provider_config_api_version patches on its own.

A bundle reference also pins one published version of that bundle. Name it as provider_bundle_version beside the provider_bundle_id, or leave it out and the write pins the bundle's latest_version, the declaration you read when you picked the bundle:

shell
curl --silent --show-error --fail-with-body -X POST \
  -H "Authorization: Bearer ${TOKEN}" -H 'content-type: application/json' \
  "${PLEXSPHERE_URL}/v1/clouds" -d '{
  "slug": "acme-aws-staging",
  "display_name": "Acme AWS Staging",
  "provider": "aws",
  "external_id": "123456789013",
  "endpoint": {"region": "eu-central-1", "partition": "aws"},
  "region_defaults": {"default_region": "eu-central-1"},
  "provider_bundle_id": "<bundle-uuid>",
  "provider_bundle_version": 2
}'

Editing the bundle publishes a new version and moves no Cloud. A PATCH naming provider_bundle_version alone on a Cloud already in bundle mode is what moves this one Cloud onto another declaration, in either direction:

shell
curl -s -X PATCH -H "Authorization: Bearer ${TOKEN}" -H 'content-type: application/json' \
  "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>" -d '{"provider_bundle_version": 3}'

A version the referenced bundle never published is rejected with 400 provider_bundle_version_not_found; read the versions it did publish at GET /v1/provider-bundles/{id}/versions. Naming the field on a Cloud that declares its packages inline is rejected with 400 invalid_cloud_provider_mode, because there is no bundle for the version to point into.

Override a package on one Cloud

A Cloud in bundle mode may run some of the pinned declaration's packages at other versions without leaving the bundle. provider_package_overrides carries those deviations as {source, version} pairs, each source named at most once and at most sixteen entries:

shell
curl -s -X PATCH -H "Authorization: Bearer ${TOKEN}" -H 'content-type: application/json' \
  "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>" -d '{
  "provider_package_overrides": [
    {"source": "xpkg.upbound.io/upbound/provider-aws-s3", "version": "v2.7.1"}
  ]
}'

The same write with plexctl repeats one flag occurrence per overridden package:

shell
plexctl cloud update <cloud-uuid> \
  --package-override xpkg.upbound.io/upbound/provider-aws-s3@v2.7.1

The field replaces the whole set, so restate every override the Cloud should keep. Omitting it leaves the current set untouched, which is what carries the deviations across a rename or a version bump. Sending [] clears them and puts the Cloud back on the pinned version as it stands, which is the body the plexctl flag sends:

shell
plexctl cloud update <cloud-uuid> --clear-package-overrides

An override whose source the pinned version carries replaces that entry's version, one naming a source the pinned version does not carry joins the effective set, and nothing is ever removed. Stating the field on a Cloud that declares its packages inline is rejected with 400 invalid_cloud_provider_mode: an override deviates from a referenced declaration, and a Cloud that owns its package set changes a version by editing that set.

Read the Cloud back for both halves, the set it runs and the deviations that shaped it:

shell
plexctl cloud get <cloud-uuid> --output json \
  | jq '{provider_package_overrides, provider_packages}'
# {
#   "provider_package_overrides": [
#     {"source": "xpkg.upbound.io/upbound/provider-aws-s3", "version": "v2.7.1"}
#   ],
#   "provider_packages": [
#     {"source": "xpkg.upbound.io/upbound/provider-aws-ec2", "version": "v2.6.1", "origin": "bundle"},
#     {"source": "xpkg.upbound.io/upbound/provider-aws-s3", "version": "v2.7.1", "origin": "override"}
#   ]
# }

origin reads bundle for an entry taken as the pinned version published it, override where a deviation replaced its version, and addition where the override named a source that version does not carry. The text output states the same pair in two columns: PROVIDER_PACKAGES is the effective set, PACKAGE_OVERRIDES the deviations the operator authored.

List, inspect, update, delete

shell
curl -s -H "Authorization: Bearer ${TOKEN}" "${PLEXSPHERE_URL}/v1/clouds"
curl -s -H "Authorization: Bearer ${TOKEN}" "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>"
curl -s -X PATCH -H "Authorization: Bearer ${TOKEN}" -H 'content-type: application/json' \
  "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>" -d '{"display_name":"Acme AWS (prod)"}'
curl -s -X DELETE -H "Authorization: Bearer ${TOKEN}" "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>"

Verification

shell
curl -s -H "Authorization: Bearer ${TOKEN}" \
  "${PLEXSPHERE_URL}/v1/clouds/<cloud-uuid>" | jq '{slug, provider}'
# {
#   "slug": "acme-aws-prod",
#   "provider": "aws"
# }

See also