Appearance
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 nestedplexctl cloud credentialsubgroup, 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 nestedplexctl cloud assignmentsubgroup. Approving and rejecting a request belongs toplexctl 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
| Flag | Type | Required | Description |
|---|---|---|---|
--slug | string | yes | kebab-case URL handle. |
--display-name | string | yes | Human-readable Cloud name. |
--provider | enum | yes | Upstream provider: aws | azure | gcp | hetzner | openstack. |
--external-id | string | yes | Upstream provider account identifier. |
--endpoint | JSON | yes | Provider-specific connection metadata. |
--region-defaults | JSON | yes | Provider-specific region/default metadata. |
--provider-package | string | no | One 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-version | string | no | <group>/<version> every declared package serves its ProviderConfig under. |
--provider-bundle | string | no | Provider bundle the Cloud takes its provider configuration from, by slug or UUID. |
--provider-bundle-version | int | no | Published version of that bundle to pin. The bundle's latest version when omitted. Requires --provider-bundle. |
--package-override | string | no | Package 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
| Flag | Type | Required | Description |
|---|---|---|---|
--limit | int | no | Maximum items per page (server default when zero). |
--cursor | string | no | Continuation token from a previous call. |
--all | bool | no | Follow next_cursor until exhausted. Mutually exclusive with --cursor. |
plexctl cloud update <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--display-name | string | no | New human-readable Cloud name. |
--endpoint | JSON | no | New endpoint metadata. |
--region-defaults | JSON | no | New region-defaults metadata. |
--provider-package | string | no | Replacement package as <source>@<version>. Repeatable; the occurrences replace the whole declared set rather than adding to it. |
--provider-config-api-version | string | no | New <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-bundle | string | no | Provider bundle the Cloud takes its provider configuration from after the patch, by slug or UUID. Mutually exclusive with both flags above. |
--provider-bundle-version | int | no | Published 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-override | string | no | Package 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-overrides | bool | no | Drop every package override, putting the Cloud back on the pinned bundle version as it stands. Same exclusions as --package-override. |
plexctl cloud delete <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--yes (persistent) | bool | yes | Required confirmation for the destructive operation. |
plexctl cloud credential create
| Flag | Type | Required | Description |
|---|---|---|---|
--cloud-id | string | yes | Owning Cloud UUID. |
--display-name | string | yes | Human-readable Cloud Credential name. |
--payload-file | string | yes | Path to the secret payload bytes, or - to read from stdin. |
--key-value | string | no | Flat key=value pair written alongside the payload. Repeatable. |
plexctl cloud credential revoke <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--reason | string | yes | Audit-recorded revocation rationale. |
--yes (persistent) | bool | yes | Required confirmation for the destructive operation. |
plexctl cloud credential attach-cloud <credential-id>
| Flag | Type | Required | Description |
|---|---|---|---|
--cloud-id | UUID | yes | Usage Cloud UUID to attach. |
plexctl cloud credential detach-cloud <credential-id>
| Flag | Type | Required | Description |
|---|---|---|---|
--cloud-id | UUID | yes | Usage Cloud UUID to detach. The home Cloud is undetachable. |
plexctl cloud credential list-clouds <credential-id>
| Flag | Type | Required | Description |
|---|---|---|---|
--limit | int | no | Maximum items per page (server default when zero). |
--cursor | string | no | Continuation token from a previous call. |
plexctl cloud assignment request
| Flag | Type | Required | Description |
|---|---|---|---|
--project-id | UUID | yes | Owning Project UUID. |
--cloud-id | UUID | yes | Cloud UUID to request usage of. |
plexctl cloud assignment grant
| Flag | Type | Required | Description |
|---|---|---|---|
--cloud-id | UUID | yes | Cloud UUID to grant. |
--project-id | UUID | yes | Project UUID to grant the Cloud to. |
plexctl cloud assignment list
| Flag | Type | Required | Description |
|---|---|---|---|
--project-id | UUID | yes | Owning Project UUID. |
--limit | int | no | Maximum items per page (server default when zero). |
--cursor | string | no | Continuation token from a previous call. |
plexctl cloud assignment revoke <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--reason | string | yes | Audit-recorded revocation rationale. |
--yes (persistent) | bool | yes | Required 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/v1beta1Create 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.1Drop 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-overridesDelete a Cloud (requires --yes)
shell
plexctl cloud delete 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--server "${PLEXSPHERE_URL}" \
--yesIssue 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-provisionerPipe 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 jsonGrant 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-c0c0c0c0c0c0Request 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}"