Skip to content

plexctl cloud

Synopsis

plexctl cloud is the operator surface for the Cloud Inventory aggregate, the per-Cloud Cloud Credential lifecycle, and the Cloud↔Project assignment workflow. It wraps several /v1 surfaces under a single cobra parent:

  • /v1/clouds — CRUD on Clouds.
  • /v1/cloud-credentials (scoped to a Cloud) — create / list / get / revoke through the nested plexctl cloud credential subgroup, plus the clouds-per-credential association surface (attach-cloud, detach-cloud, list-clouds) that manages the Clouds a single credential serves.
  • /v1/projects/{id}/cloud-assignments, /v1/clouds/{id}/cloud-assignments, and /v1/cloud-assignments/{id}/revoke — the Cloud↔Project assignment workflow through the nested plexctl cloud assignment subgroup. Approving and rejecting a request belongs to plexctl approval, not here.
text
plexctl cloud <subcommand> [flags]
plexctl cloud credential <subcommand> [flags]
plexctl cloud assignment <subcommand> [flags]

Subcommands

plexctl cloud create

POSTs to /v1/clouds. Required flags: --slug, --display-name, --provider, --external-id, --endpoint, --region-defaults. The --endpoint and --region-defaults flags carry inline JSON objects that the per-provider validator family validates on the server.

On top of those, the create states the Cloud's provider configuration in exactly one of two modes. Inline mode sets both --provider-package and --provider-config-api-version; bundle mode sets --provider-bundle and neither inline flag. --provider-package is repeatable and takes <source>@<version>; at least one occurrence is required in inline mode, and each source may be named only once. --provider-config-api-version is a single Cloud-level value every declared package serves its ProviderConfig under. --provider-bundle takes the slug or the UUID of a provider bundle, the shared declaration the Cloud then resolves both values from.

--provider-bundle-version pins one published version of that bundle. Omit it and the create pins the bundle's latest version, the declaration an operator reads when they pick the bundle. It belongs to bundle mode and requires --provider-bundle: an inline Cloud pins no bundle, so there is no version for it to name.

--package-override names a package the new Cloud runs at another version than the pinned declaration states, again as <source>@<version>. It is repeatable, each source may be named only once, and the set holds at most sixteen entries. It belongs to bundle mode and requires --provider-bundle for the same reason the version flag does: an override is a deviation from a referenced declaration, and a Cloud that owns its package set runs another version by editing that set.

The mode check runs locally, before any request is built. A create that names --provider-bundle alongside either inline flag, one that sets only one half of the inline pair, one that states no mode at all, and one that names --provider-bundle-version or --package-override without --provider-bundle each exit 2 without a round-trip.

plexctl cloud list

GETs /v1/clouds. Pagination is keyset-based via --cursor / --limit; the --all flag follows next_cursor until exhausted (mutually exclusive with --cursor).

plexctl cloud get <id>

GETs /v1/clouds/{id}. Local UUID validation rejects malformed id with exit 2.

plexctl cloud update <id>

PATCHes /v1/clouds/{id}. Only flags explicitly set on the command line are sent in the PATCH body. --slug and --provider are not exposed because the API rejects mutating them. An empty patch surfaces as 400 empty_patch.

--provider-bundle moves the Cloud into bundle mode and is mutually exclusive with --provider-package and with --provider-config-api-version. Naming it beside either is caught by cobra's flag-group check, which reports the conflicting group and exits 1, not by the local mode check create runs.

--provider-bundle-version rides along with --provider-bundle to attach and pin in one write. On its own, on a Cloud already in bundle mode, it is the promotion: the one write that moves this Cloud onto another declaration of the bundle it already references, in either direction. It moves no other Cloud, and plexctl provider-bundle promote is the same patch issued across a set of them. The flag is exclusive with the two inline flags for the same reason --provider-bundle is.

--package-override states the packages the Cloud runs at other versions than its pinned declaration, and the occurrences replace the whole override set rather than adding to it. --clear-package-overrides sends the empty set instead, which puts the Cloud back on the pinned version as it stands. A patch that names neither leaves the current set untouched, so a rename or a promotion carries the deviations along. Both flags are mutually exclusive with --provider-package, with --provider-config-api-version, and with each other, so cobra reports the conflicting group and exits 1 before the patch is built. Either one may ride along with --provider-bundle or --provider-bundle-version to attach or promote and restate the deviations in a single write.

The way back out of bundle mode is the full inline pair: a patch carrying both --provider-package and --provider-config-api-version restores the Cloud's own declaration and clears the reference and its version pin. A patch that names only one half of the pair on a referencing Cloud is rejected with 400 invalid_cloud_provider_mode, because such a Cloud would be left with neither a package set nor an apiVersion of its own. A version the referenced bundle never published is rejected with 400 provider_bundle_version_not_found. An override stated on a patch that ends with the Cloud declaring its packages inline is rejected with 400 invalid_cloud_provider_mode, the same code the local check on create stands in for.

plexctl cloud delete <id>

DELETEs /v1/clouds/{id}. Requires the persistent --yes flag. A Cloud with attached credentials returns 409 cloud_not_empty.

Every Cloud output (from create, list, get and update) carries a PROVIDER_BUNDLE column naming the provider bundle the Cloud takes its provider configuration from and the version it pins, as <slug>@v<number>, for example acme-aws-standard@v2. The version is part of the cell because the slug alone does not say which declaration the Cloud resolved: a bundle patch publishes a version and moves no Cloud, so two Clouds on the same bundle can render different packages. The cell is empty for a Cloud in inline mode, which declares that configuration itself. The PROVIDER_PACKAGES and PROVIDER_CONFIG_API_VERSION columns beside it are the effective values either way, resolved by the server on every read, so a bundle-mode row shows the pinned version's packages and apiVersion rather than blanks.

The PACKAGE_OVERRIDES column that follows carries the override set the operator authored on the Cloud, in the same comma-joined source:version shape. A package the Cloud overrides appears twice on the row: in PROVIDER_PACKAGES as the version the Cloud runs, and in PACKAGE_OVERRIDES as the deviation that put it there. The cell is empty for a Cloud in inline mode, which authors no override, and for a bundle-mode Cloud that states none. Use --output json to read each effective package with the origin the server stamped it with: bundle, override, or addition.

plexctl cloud credential create

POSTs /v1/clouds/{id}/cloud-credentials to issue a new Cloud Credential under the Cloud named by --cloud-id. Required flags: --cloud-id, --display-name, --payload-file. The --payload-file flag reads the secret bytes from a path, or from standard input when set to -, so the plaintext never lands in shell history. The optional repeatable --key-value flag carries key=value pairs written alongside the payload. The 201 response is the metadata-only projection; the payload and key-values are never returned.

plexctl cloud credential list

GETs /v1/clouds/{id}/cloud-credentials (the --cloud-id flag is required). Metadata only; secret material is never returned on the read paths. Supports --limit and --cursor.

plexctl cloud credential get <id>

GETs /v1/cloud-credentials/{id} — metadata only.

plexctl cloud credential revoke <id>

POSTs /v1/cloud-credentials/{id}/revoke. Requires the persistent --yes flag and a required --reason string that is recorded on the revocation audit row.

plexctl cloud credential attach-cloud <credential-id>

POSTs /v1/cloud-credentials/{id}/clouds to attach the usage Cloud named by --cloud-id, so the credential additionally serves it. The credential's home Cloud is attached at issue time; this command adds further usage Clouds so one credential can serve several Clouds without re-issuing the secret. The 201 response echoes the attached Cloud.

plexctl cloud credential detach-cloud <credential-id>

DELETEs /v1/cloud-credentials/{id}/clouds/{cloud-id} to remove the usage edge named by --cloud-id. The credential's home Cloud is undetachable — the server rejects detaching it with 409 cannot_detach_home_cloud. The call is idempotent (an already-absent edge returns 204) and does not consume the --yes gate, because removing a usage edge is reversible (re-attach) — unlike the irreversible credential revoke.

plexctl cloud credential list-clouds <credential-id>

GETs /v1/cloud-credentials/{id}/clouds and lists the Clouds the credential serves — its home Cloud plus every attached usage Cloud. Supports --limit and --cursor.

plexctl cloud assignment request

POSTs /v1/projects/{id}/cloud-assignments to request usage of the Cloud named by --cloud-id. Required flags: --project-id, --cloud-id. The assignment opens in the requested state.

plexctl cloud assignment grant

POSTs /v1/clouds/{id}/cloud-assignments to grant the Cloud to the Project named by --project-id. Required flags: --cloud-id, --project-id. The operator-initiated grant creates the assignment already in the approved state and materialises the binding at once.

plexctl cloud assignment list

GETs /v1/projects/{id}/cloud-assignments (the --project-id flag is required). Pagination is keyset-based via --cursor / --limit.

Every assignment output — from request, grant, list and revoke — carries a PROVIDER column: one phase standing for every Crossplane provider package the assigned Cloud declares, on the management cluster hosting the Project. The provider_installs entries are folded to the most severe phase present, in the order Conflict, Failed, Installing, Pending, Serving, so the column reports the package that blocks provisioning rather than the healthiest one. The cell is empty when the array is absent or empty, which is the case for any row that is not approved and installs nothing. Use --output json to read every entry with its own source and the message behind a Failed or Conflict phase.

plexctl cloud assignment revoke <id>

POSTs /v1/cloud-assignments/{id}/revoke. Requires the persistent --yes flag and a --reason string. Either party may revoke an approved assignment: the server first checks assign on the Cloud, and on that denial falls back to deploy on the consuming Project, so a Project deployer can hand the Cloud back without holding it. The call is refused only when both checks deny. Whichever party revoked, the same cloud:<id>#uses@project:<id> tuple is narrow-deleted and the audit row records which relation granted.

Deciding a request

plexctl cloud assignment has no approve or reject subcommand. Approving and rejecting a Cloud Assignment happens on the dual-control queue: plexctl approval list --kind cloud_assignment shows the rows the caller may decide, --cloud-id narrows them to one Cloud, and plexctl approval approve or plexctl approval reject takes the assignment id directly. See approval.md.

Flags

plexctl cloud create

FlagTypeRequiredDescription
--slugstringyeskebab-case URL handle.
--display-namestringyesHuman-readable Cloud name.
--providerenumyesUpstream provider: aws | azure | gcp | hetzner | openstack.
--external-idstringyesUpstream provider account identifier.
--endpointJSONyesProvider-specific connection metadata.
--region-defaultsJSONyesProvider-specific region/default metadata.
--provider-packagestringnoOne Crossplane provider package serving this Cloud, as <source>@<version>: the OCI repository without a tag or digest, then an OCI tag optionally pinned with @sha256:<digest>. Repeatable; at least one occurrence is required in inline mode.
--provider-config-api-versionstringno<group>/<version> every declared package serves its ProviderConfig under.
--provider-bundlestringnoProvider bundle the Cloud takes its provider configuration from, by slug or UUID.
--provider-bundle-versionintnoPublished version of that bundle to pin. The bundle's latest version when omitted. Requires --provider-bundle.
--package-overridestringnoPackage the Cloud runs at another version than the pinned bundle declaration states, as <source>@<version>. Repeatable, each source once, at most sixteen entries. Requires --provider-bundle.

None of the last five is required on its own, and the create is refused unless exactly one mode is stated: either --provider-bundle, or both --provider-package and --provider-config-api-version. --provider-bundle-version and --package-override ride along with the bundle flag. Every other combination exits 2.

plexctl cloud list

FlagTypeRequiredDescription
--limitintnoMaximum items per page (server default when zero).
--cursorstringnoContinuation token from a previous call.
--allboolnoFollow next_cursor until exhausted. Mutually exclusive with --cursor.

plexctl cloud update <id>

FlagTypeRequiredDescription
--display-namestringnoNew human-readable Cloud name.
--endpointJSONnoNew endpoint metadata.
--region-defaultsJSONnoNew region-defaults metadata.
--provider-packagestringnoReplacement package as <source>@<version>. Repeatable; the occurrences replace the whole declared set rather than adding to it.
--provider-config-api-versionstringnoNew <group>/<version> for the ProviderConfig. Patches on its own: it is a Cloud-level value, so changing it does not require restating the package set.
--provider-bundlestringnoProvider bundle the Cloud takes its provider configuration from after the patch, by slug or UUID. Mutually exclusive with both flags above.
--provider-bundle-versionintnoPublished version of the referenced bundle the Cloud pins after the patch. Set on its own, it promotes this one Cloud. Mutually exclusive with --provider-package and --provider-config-api-version.
--package-overridestringnoPackage the Cloud runs at another version than its pinned declaration states, as <source>@<version>. Repeatable; the occurrences replace the whole override set. Mutually exclusive with --provider-package, --provider-config-api-version, and --clear-package-overrides.
--clear-package-overridesboolnoDrop every package override, putting the Cloud back on the pinned bundle version as it stands. Same exclusions as --package-override.

plexctl cloud delete <id>

FlagTypeRequiredDescription
--yes (persistent)boolyesRequired confirmation for the destructive operation.

plexctl cloud credential create

FlagTypeRequiredDescription
--cloud-idstringyesOwning Cloud UUID.
--display-namestringyesHuman-readable Cloud Credential name.
--payload-filestringyesPath to the secret payload bytes, or - to read from stdin.
--key-valuestringnoFlat key=value pair written alongside the payload. Repeatable.

plexctl cloud credential revoke <id>

FlagTypeRequiredDescription
--reasonstringyesAudit-recorded revocation rationale.
--yes (persistent)boolyesRequired confirmation for the destructive operation.

plexctl cloud credential attach-cloud <credential-id>

FlagTypeRequiredDescription
--cloud-idUUIDyesUsage Cloud UUID to attach.

plexctl cloud credential detach-cloud <credential-id>

FlagTypeRequiredDescription
--cloud-idUUIDyesUsage Cloud UUID to detach. The home Cloud is undetachable.

plexctl cloud credential list-clouds <credential-id>

FlagTypeRequiredDescription
--limitintnoMaximum items per page (server default when zero).
--cursorstringnoContinuation token from a previous call.

plexctl cloud assignment request

FlagTypeRequiredDescription
--project-idUUIDyesOwning Project UUID.
--cloud-idUUIDyesCloud UUID to request usage of.

plexctl cloud assignment grant

FlagTypeRequiredDescription
--cloud-idUUIDyesCloud UUID to grant.
--project-idUUIDyesProject UUID to grant the Cloud to.

plexctl cloud assignment list

FlagTypeRequiredDescription
--project-idUUIDyesOwning Project UUID.
--limitintnoMaximum items per page (server default when zero).
--cursorstringnoContinuation token from a previous call.

plexctl cloud assignment revoke <id>

FlagTypeRequiredDescription
--reasonstringyesAudit-recorded revocation rationale.
--yes (persistent)boolyesRequired confirmation for the destructive operation.

Persistent flags inherited from root

--server, --profile, --token-file, --output, --yes, --reveal-secrets. See ../plexctl.md for the canonical list.

Exit codes

See ../plexctl.md#exit-code-taxonomy.

Examples

Create a Cloud

shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"

plexctl cloud create \
  --server          "${PLEXSPHERE_URL}" \
  --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-package xpkg.upbound.io/upbound/provider-aws-ec2@v2.6.1 \
  --provider-package xpkg.upbound.io/upbound/provider-aws-s3@v2.6.1 \
  --provider-config-api-version aws.m.upbound.io/v1beta1

Create a Cloud from a bundle with a package override

The Cloud takes version 2 of the shared declaration and runs one package of it at a newer version:

shell
plexctl cloud create \
  --server          "${PLEXSPHERE_URL}" \
  --slug            acme-aws-staging \
  --display-name    "Acme AWS staging" \
  --provider        aws \
  --external-id     "123456789012" \
  --endpoint        '{"region":"eu-central-1","partition":"aws"}' \
  --region-defaults '{"default_region":"eu-central-1"}' \
  --provider-bundle acme-aws-standard \
  --provider-bundle-version 2 \
  --package-override xpkg.upbound.io/upbound/provider-aws-s3@v2.7.1

Drop the deviation again and the Cloud runs version 2 as published:

shell
plexctl cloud update 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --server "${PLEXSPHERE_URL}" \
  --clear-package-overrides

Delete a Cloud (requires --yes)

shell
plexctl cloud delete 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --server "${PLEXSPHERE_URL}" \
  --yes

Issue a Cloud Credential

shell
plexctl cloud credential create \
  --server       "${PLEXSPHERE_URL}" \
  --cloud-id     0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --display-name "Acme AWS provisioner secret" \
  --payload-file ./aws-provisioner.json \
  --key-value    role_arn=arn:aws:iam::123456789012:role/acme-provisioner

Pipe the payload from stdin instead of a file with --payload-file -:

shell
printf '%s' "${SECRET_JSON}" | plexctl cloud credential create \
  --server       "${PLEXSPHERE_URL}" \
  --cloud-id     0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --display-name "Acme AWS provisioner secret" \
  --payload-file -

Attach a usage Cloud to a credential and list what it serves

shell
plexctl cloud credential attach-cloud 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
  --server   "${PLEXSPHERE_URL}" \
  --cloud-id 0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0

plexctl cloud credential list-clouds 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a1 \
  --server "${PLEXSPHERE_URL}" \
  --output json

Grant a Cloud to a Project

shell
plexctl cloud assignment grant \
  --server     "${PLEXSPHERE_URL}" \
  --cloud-id   0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0 \
  --project-id 0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0

Request a Cloud assignment and approve it

The request is a Cloud Assignment operation; the decision is an approvals-queue operation on the same id.

shell
plexctl cloud assignment request \
  --server     "${PLEXSPHERE_URL}" \
  --project-id 0190a8b8-a0c0-7a0a-8a0a-c0c0c0c0c0c0 \
  --cloud-id   0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0

plexctl approval list \
  --server   "${PLEXSPHERE_URL}" \
  --kind     cloud_assignment \
  --cloud-id 0190a8b8-a0c0-7a0a-8a0a-b0b0b0b0b0b0

plexctl approval approve 0190a8b8-a0c0-7a0a-8a0a-d0d0d0d0d0d0 \
  --server "${PLEXSPHERE_URL}"

Cross-references