Skip to content

Provision a cloud Resource

The other tutorials work with tenancy — Domains, Projects, identities — and one works with the mesh. This one works with provisioning: the broker that turns a declared Resource into real cloud substrate. You will hand the platform a single resource create and then watch it do everything else — render the Composite Resource, drive the cloud provider, and report back — until the Resource reaches its terminal Ready phase.

The Resource you provision is an AWS S3 bucket from the aws-s3-bucket Blueprint. It is a standalone (nodeless) resource: a real cloud object with no plexd Node to enrol, served on the lean stack by the floci AWS emulator — so the bucket genuinely materialises, with no real account or cost. Because there is no node, the broker mints no bootstrap token and the lifecycle skips the Enrolling phase: the bucket is Ready the moment its substrate composes. The flow you drive — resource create, the phase machine — is identical to provisioning against a real AWS account; only the endpoint differs.

Provisioning deliberately splits two concerns: the platform operator curates the shared catalog and decides whose credential pays, while the project owner consumes the catalog. You played both sides across the earlier lessons — you built the catalog in the three catalog lessons and bound a credential to your Project under two-party governance in Assign a Cloud Credential. Here you act as the project owner alone, consuming what those lessons set up.

Everything this lesson needs is already in place:

You will:

  • recover the ids you need — your Project, its approved credential, and the aws-s3-bucket Blueprint version,
  • resource create an S3 bucket and get back 202 + a Location,
  • poll the Resource through Pending → Provisioning → Ready, and confirm the bucket really exists in the emulated AWS,
  • tear it back down.

By the end you will understand the shape of a provisioned Resource and the phases the broker drives a nodeless resource through to make it real.

This lesson takes about ten minutes — most of it spent watching the broker work.

Before you start

This lesson builds onAssign a Cloud Credential, which in turn builds on the three catalog lessons: the demo AWS Cloud, its Credential, the aws-s3-bucket Blueprint, and an approved credential assignment on the provision-demo Project must already exist, because this lesson consumes them. Complete the provisioning track up to and including Assign a Cloud Credential first. Beyond that you need a running, logged-in stack from Set up your local plexsphere, plus jq on your $PATH to read fields out of the JSON responses.

You act as the project owner throughout — admin@example.com, your default profile, the same identity every other tenancy lesson uses. The two-party credential-assignment governance happened in the previous lesson; here the catalog and the approved assignment are already in place, so a single identity drives the whole flow. It signs in with the dev password password; see the tutorials overview for the full cast.

Recreate the shell environment and read the Acme Corp Domain id with make dev-ids:

bash
export PATH="$PWD/bin:$PATH"
export PLEXSPHERE_URL=http://localhost:8080

eval "$(make -s dev-ids)"
echo "domain=$DOMAIN_ID"

Step 1 — Recover the ids you need

The catalog and the approved assignment already exist — you built them in the earlier lessons. This lesson consumes them, so begin by recovering the ids resource create needs. You recover all of them as the project owner: the Blueprint catalog is scoped to your Acme Corp Domain, so a domain admin reads the Blueprint and its version directly — there is no operator step in this lesson.

Sign in as the project owner under your default profile, completing sign-in in the browser as admin@example.com with the password password:

bash
plexctl login --domain-id "$DOMAIN_ID"

Recover the provision-demo Project you created in the assignment lesson by its slug:

bash
PROJECT_ID=$(plexctl project list --domain "$DOMAIN_ID" --output json \
  | jq -r '.items[] | select(.slug == "provision-demo") | .id')
echo "$PROJECT_ID"

Read back the approved credential the assignment bound to that Project — resource create names the credential id directly, so capture it from the approved assignment:

bash
CREDENTIAL_ID=$(plexctl credential assignment list --project-id "$PROJECT_ID" --output json \
  | jq -r 'first(.items[] | select(.state == "approved") | .cloud_credential_id)')
echo "$CREDENTIAL_ID"

resource create takes the BlueprintVersion's id, the broker's internal handle for that revision. Resolve the aws-s3-bucket Blueprint by its slug, then read the version id from the Blueprint's versions array — blueprint get surfaces it on the read API:

bash
BLUEPRINT_ID=$(plexctl blueprint list --all --output json \
  | jq -r '.items[] | select(.slug == "aws-s3-bucket") | .id')
BLUEPRINT_VERSION_ID=$(plexctl blueprint get "$BLUEPRINT_ID" --output json \
  | jq -r '.versions[] | select(.version == "v1alpha1") | .id')
echo "$BLUEPRINT_VERSION_ID"

You now hold everything the broker needs: $PROJECT_ID, $CREDENTIAL_ID, and $BLUEPRINT_VERSION_ID.

Step 2 — Create the provisioned Resource

You have everything the broker needs: a Project, an approved credential, and a blueprint version. Declare the Resource:

bash
RESOURCE_ID=$(plexctl resource create \
  --project-id "$PROJECT_ID" \
  --kind bucket \
  --blueprint-version-id "$BLUEPRINT_VERSION_ID" \
  --cloud-credential-id "$CREDENTIAL_ID" \
  --parameters '{"region":"us-east-1"}' \
  --output json | jq -r '.id')
echo "$RESOURCE_ID"
text
019efb32-a148-7c11-a995-3ba6be65aabf

That one line is the captured $RESOURCE_ID. resource create prints the accepted Resource and nothing else, so the capture is the same shape as every other create in these lessons.

The aws-s3-bucket Blueprint declares a required region parameter, so the --parameters object names it — a declaration missing region fails closed before any substrate is touched. --kind is a free-form label on the tenancy Resource; bucket records what this Resource is.

A grant to land. The approval from the previous lesson writes the Project's use of the credential to the authorization mirror a moment later. If resource create returns Permission Denied: project not authorised to use the named credential, wait a second and re-run it — the same eventual-consistency window as a fresh Project grant.

resource create returns 202 Accepted with a Location header pointing at the new Resource — provisioning is asynchronous, so the call accepts your declaration rather than waiting for the substrate. The Resource is created at phase Pending:

bash
plexctl resource get "$RESOURCE_ID" --output json | jq '{kind, origin, phase: .provisioning.phase}'
json
{
  "kind": "bucket",
  "origin": "provisioned",
  "phase": "Pending"
}

Step 3 — Watch it reach Ready

From here you do nothing — the broker reconcile loop drives the Resource forward on its own ticker. The phase is a single resource get away — run it by hand whenever you want a snapshot:

bash
plexctl resource get "$RESOURCE_ID" --output json | jq -r '.provisioning.phase'
text
Provisioning

Poll the same read until it is Ready:

bash
while true; do
  PHASE=$(plexctl resource get "$RESOURCE_ID" --output json | jq -r '.provisioning.phase')
  echo "$(date +%T) $PHASE"
  case "$PHASE" in Ready|Failed) break ;; esac
  sleep 10
done
text
14:31:02 Pending
14:31:12 Provisioning
14:33:12 Ready

Each phase is a real milestone the broker observes, never a timer — and a standalone resource has only two transitions, because it skips the node-enrolment phase:

  • Pending → Provisioning — the Project's management-fleet namespace became ready, so the broker applied the blueprint's Composite Resource (the XAWSS3Bucket). It mints no bootstrap token: there is no node to enrol.
  • Provisioning → Ready — the upjet AWS provider composed the bucket against the emulated AWS endpoint and Crossplane reported the substrate Ready. For a standalone resource that is the terminal success — the broker never waits on an Enrolling step, because nothing registers.

That is a real cloud Resource provisioned end to end. Confirm the bucket actually exists in the emulated AWS by asking floci to list its buckets. floci validates no credentials, so an unsigned ListBuckets is enough — no aws CLI, and no local port forward, because the probe runs inside the cluster where floci is already reachable by service name:

bash
kubectl run floci-probe --rm -i --quiet --restart=Never \
  --image=curlimages/curl:8.10.1 -- -sS http://floci:4566/ \
  | grep -o '<Name>[^<]*' | cut -d'>' -f2
text
pr-019fb9d7-fb6c-7681-a082-07407e041cc0-a2908b4cb3ce

The first run pulls the curl image, so give it a few seconds; --rm deletes the Pod afterwards and --quiet keeps its lifecycle chatter off your terminal.

A bucket the Composition created is there — a real object in the emulated AWS, its name derived from the Resource id with a collision suffix. The same resource create against a real AWS account would have created a real S3 bucket; only the endpoint floci serves differs.

Step 4 — Deprovision the Resource

A provisioned Resource is torn down the way it was built — you declare the intent and the broker reconciles it. Delete it; the --yes gate guards the destructive act:

bash
plexctl resource delete "$RESOURCE_ID" --yes

The command prints nothing — a successful delete is silent, like every other destructive plexctl command. And like create, it is asynchronous: the call returns 202 Accepted and queues the teardown rather than blocking on it. The same single resource get shows the teardown underway:

bash
plexctl resource get "$RESOURCE_ID" --output json | jq -r '.provisioning.phase'
text
Deprovisioning

Poll the phase the same way you did on the way up, now until it reaches Deleted:

bash
while true; do
  PHASE=$(plexctl resource get "$RESOURCE_ID" --output json | jq -r '.provisioning.phase')
  echo "$(date +%T) $PHASE"
  case "$PHASE" in Deleted|Failed) break ;; esac
  sleep 10
done
text
14:40:03 Deprovisioning
14:41:13 Deleted

Teardown runs the provisioning arc in reverse. A node resource drains its plexd node out of the mesh first (a Deregistering phase), but a standalone resource has no node, so its teardown opens directly at Deprovisioning:

  • Ready → Deprovisioning — the broker deletes the substrate (the Crossplane Composite Resource and its ProviderConfig), so the upjet provider removes the bucket from the emulated AWS.
  • Deprovisioning → Deleted — the substrate is gone; Deleted is terminal. Re-running the floci ListBuckets probe from Step 3 now prints nothing for this Resource.

Unlike provisioning, teardown needs no credential assignment and no two-party approval — a single resource delete with the delete relation on the Resource is enough. Separation of duties guards standing spend up, not tearing it down.

Do this in the Console

The provisioning steps above have a browser equivalent in the Console. The credential-assignment governance has its own browser walkthrough in Assign a Cloud Credential; here you create the Resource. Open the Projects page, find your Project, and click Open to reach the Project detail page — the host for the Project-scoped Resources panel:

  • Resources lists the Project's Resources and hosts Create Resource. Pick the provisioned origin, enter the aws-s3-bucket kind, the BlueprintVersion id, and the approved Cloud Credential id, and submit. The new Resource appears with a Status that advances Pending → Provisioning → Ready as the panel polls it — the browser equivalent of re-running resource get until the phase settles. An adopted Resource has no broker phase, so its Status shows an em dash.

What you learned

  • Provisioning splits two roles. The platform operator curates the catalog — Clouds, Credentials, Blueprints — and approves which Project may spend a credential; a project owner consumes it. You built the catalog and approved the assignment in the earlier lessons, then consumed them here as the owner alone.
  • A Resource is a declaration the broker reconciles. resource create returns 202 and a Location because provisioning is asynchronous; the Resource then advances through observed phases, not a fixed schedule.
  • A blueprint version is the unit you provision against. The Blueprint is the recipe; the immutable BlueprintVersion id is what resource create takes, and its parameter schema tells you what input it needs.
  • The phases are facts, not a clock. Provisioning waits on the substrate being applied, and Ready on Crossplane reporting the bucket composed. A node blueprint adds an Enrolling phase between them, where the broker waits for the node to register; a standalone resource has no node, so it skips that phase and is Ready as soon as the substrate is.
  • Mesh role shapes the lifecycle. The aws-s3-bucket Blueprint is standalone: the broker mints no bootstrap token and runs no enrolment for it, because there is no plexd Node. A node blueprint — the one Reach your Resource provisions — does, which is why only that lesson has something to log in to.
  • Teardown is the provisioning arc in reverse. resource delete is asynchronous like create. A node resource deregisters its node from the mesh before it deletes the substrate, so a machine is never stranded; a standalone resource has no node, so it deletes the substrate directly. Either way, tearing down needs no credential assignment and no approval.

Where to go next

  • Keep learning by doingReach your Resource issues short-lived, mediated sessions — a kubeconfig, a TCP forward, an SSH login — to the Node you just provisioned.

Or pick the quadrant that matches what you need now: