§ How-to · Operate
Share a plan before you apply it
Store a plan in the state so that a team can list it, read it, see who else plans the same resources and apply it by its id.
On this page
A plan file belongs to the person who made it. Nobody else knows that it exists, and two people can plan the same resource without noticing. pravo plan --store writes the plan into the state instead. Anyone who can read the state can list the plan, read it and apply it.
This page follows two people, alice and bob. Each has a checkout of one repository, and the two checkouts share one state.
Warning
A stored plan holds the same values as a plan file, sensitive values included. Whoever can read the state can read its plans, as they can read its records. pravo serve withholds them, as it does for a plan file.
1. Share a state
Work in an empty directory. It holds the checkout of alice, the checkout of bob and the state they share. A team keeps the state in a bucket, as section 9 shows. A directory next to both checkouts stands in for it.
package demo
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
$settings: pravo.#Settings & {state: path: "../state"}
$random: rnd.#Provider
port: $random.#Resource.random_integer & {min: 20000, max: 29999, seed: "web"}
Bob's checkout is a copy of alice's. A plan applies only to the files it was made from, cue.mod included.
2. Store a plan
Alice makes a plan and stores it. --actor names her and --reason says why. pravo records both and does not verify them. Without --actor, the author of a plan is the operating system user.
Graph: planned 1 of 1.
Stored the plan as plan-<id>; unless it is applied it is removed after 7 days. To apply exactly these actions, run:
pravo apply plan-<id>
The plan is the object that plan --out writes to a file. pravo writes it below plans/ in the state and prints its id: plan- and the first eight hexadecimal digits of its digest. Nothing is applied. A plan that creates, changes or destroys no resource is not stored: pravo apply applies it.
The id differs on every run. This page keeps it in a file, and $(cat alice.id) stands for the id that pravo printed.
3. Find the plan and read it
Bob lists the plans of the state.
plan-<id> pending alice just now +1 ~0 -0 expires in 7 days
The columns are the id, the status, the author and the age. Then come the counts of resources to add, change and destroy, and a note. pending means that the plan can be applied. The note says when it expires.
plan show prints the plan after a header. pravo show ID prints the same.
Stored plan plan-<id> by alice, just now (pending)
reason: Reserve a port for the web server
command: pravo -C alice --actor alice --reason Reserve a port for the web server plan --store
expires in 7 days
apply: pravo apply plan-<id>
pravo will perform the following actions:
The header names the author, the age, the status and the reason. It adds the command that made the plan, its expiry and the command that applies it. Six hexadecimal digits are enough to find a plan, as in plan-2f9c1a, when no other plan starts with them. pravo serve lists the same plans in a browser and shows which resources they share: Explore an estate in a browser.
4. See who else plans the same resource
Bob wants a narrower range. He changes his checkout and stores a plan.
package demo
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
$settings: pravo.#Settings & {state: path: "../state"}
$random: rnd.#Provider
port: $random.#Resource.random_integer & {min: 20000, max: 29998, seed: "web"}
Graph: planned 1 of 1.
Warning: plan-<id> by alice (just now) also changes 1 of these resources: port
Stored the plan as plan-<id>; unless it is applied it is removed after 7 days. To apply exactly these actions, run:
pravo apply plan-<id>
pravo compares the new plan with the stored plans that can still be applied. Alice's plan changes port too, so pravo warns bob. The warning is information. It does not stop bob and does not change alice's plan.
A plan that is not stored warns in the same way. It names every open plan that changes one of its resources.
Warning: plan-<id> by bob (just now) also changes 1 of these resources: port
Warning: plan-<id> by alice (just now) also changes 1 of these resources: port
--touching lists the plans that change one resource.
plan-<id> pending bob just now +1 ~0 -0 expires in 7 days
plan-<id> pending alice just now +1 ~0 -0 expires in 7 days
plan list judges a plan by the state alone. plan show and apply also compare it with the checkout and with the providers. Bob's checkout differs from the files alice planned from, and plan show says so.
Warning: this plan can no longer be applied: the configuration changed since the plan was made (main.cue changed)
Bob cannot apply alice's plan from this checkout. Alice can apply it from hers. The reason names the files that differ. When it names cue.mod/module.cue, the two checkouts have different module files, as they do when pravo init wrote the module in directories with different names: commit cue.mod/module.cue and use the same commit.
5. Apply by id
Alice applies her plan by its id. The checks are those of a plan file. pravo compares the configuration, the providers, the lineage of the state and the revision of every record the plan uses. A policy named with --policy judges the whole plan. --only applies a closed part.
port: Creating...
port: Creation complete after 0s [id=<id>]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <id>]
The plan is now applied. Bob's plan assumed that port does not exist, so it is stale: pravo cannot apply it any more. A plan is stale as a whole, even when another part of it could still be applied. plan list shows the plans that can be applied, and --all adds the others.
No stored plan can be applied. "pravo plan list --all" lists the others.
plan-<id> stale bob just now +1 ~0 -0 port changed in state (revision 1, plan expected 0)
plan-<id> applied alice just now +1 ~0 -0 applied just now
An apply of a stale plan changes nothing and exits with code 3.
Error: saved plan is stale; run "pravo plan" again:
- port changed in state (revision 1, plan expected 0)
Bob plans again against the new state.
6. Collect what nobody needs
pravo plan gc removes the plans that nobody will apply. The first collection that finds a plan stale marks it and removes nothing.
1 plan can no longer be applied; a collection that runs 1 hour later removes it:
plan-<id> bob stale, port changed in state (revision 1, plan expected 0)
A collection that runs more than an hour later removes the plan. Until then, a stale plan stays readable. A plan that loses its staleness loses the mark.
| A plan is removed when | Rule |
|---|---|
| It expires | Seven days after it was made, or when --ttl says. |
| It is stale | By the first collection that runs more than one hour after a collection saw it stale. |
| It was applied completely | 24 hours after the last change was applied. |
--older-than DURATION is given |
It was not applied and was made longer ago than DURATION. |
A plan that an apply is running from is never removed. Nor is a plan that another collection is removing, or one that holds a reservation made with plan --reserve. pravo plan --store runs a small collection of its own.
7. Let a plan expire
A stored plan that nobody applies expires after seven days. --ttl sets another time: 90m, 36h or 7d. A short time shows what expiry does.
Stored the plan as plan-<id>; unless it is applied it is removed after a moment. To apply exactly these actions, run:
pravo apply plan-<id>
No stored plan can be applied. "pravo plan list --all" lists the others.
plan-<id> expired bob just now +1 ~0 -1 expired a moment ago
plan-<id> stale bob just now +1 ~0 -0 port changed in state (revision 1, plan expected 0)
plan-<id> applied alice just now +1 ~0 -0 applied just now
An expired plan is listed only with --all. apply refuses it with exit code 3, even before a collection has removed it. --dry-run says what a collection would remove and changes nothing, not even a mark.
Would remove 1 stored plan:
plan-<id> bob expired a moment ago
Removed 1 stored plan:
plan-<id> bob expired a moment ago
8. Keep a plan longer, or take it with you
Bob stores a plan that he wants to keep for a day and a half.
Stored the plan as plan-<id>; unless it is applied it is removed after 36 hours. To apply exactly these actions, run:
pravo apply plan-<id>
plan pull writes a stored plan to a plan file. Send the file, attach it to a ticket or apply it elsewhere.
Wrote plan-<id> to bob.plan. To apply exactly these actions, run:
pravo apply bob.plan
Saved plan created <time> by "pravo -C bob --actor bob plan --store --ttl 36h" (actor bob)
Applying the file records its progress in the file. The stored plan does not learn of it. Its status follows the state, which has moved, and it is stale.
Apply complete! Resources: 1 added, 0 changed, 1 destroyed. [txn <id>]
plan-<id> stale bob just now +1 ~0 -1 port changed in state (revision 2, plan expected 1)
9. Use a bucket
The commands are the same when the state is in an S3 bucket. The objects below plans/ sit next to the records, under the same prefix. The commands below run against tools/fakes3, the in-memory S3 server in pravo's source, on port 9000. Keep the state in a shared bucket sets up a real bucket.
package team
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
$settings: pravo.#Settings & {
state: pravo.#S3State & {
backend: "s3"
bucket: "pravo-state"
prefix: "demo/team"
region: "us-east-1"
endpoint: "http://127.0.0.1:9000"
path_style: true
}
}
$random: rnd.#Provider
port: $random.#Resource.random_integer & {min: 20000, max: 29999, seed: "web"}
Stored the plan as plan-<id>; unless it is applied it is removed after 7 days. To apply exactly these actions, run:
pravo apply plan-<id>
plan-<id> pending alice just now +1 ~0 -0 expires in 7 days
Copied 3 objects from s3://pravo-state/demo/team to copy
plan-<id>.meta.json
plan-<id>.plan.json
state pull and state push copy the plans with the records. Storing, listing and collecting plans needs the permissions that the state already needs: get, put, delete and list on the objects of the bucket. pravo creates the plan and its summary with If-None-Match: *, and replaces the progress object and deletes objects with If-Match. S3-compatible stores says how to test that a store honours them.
What next
pravo planlists every flag and subcommand, and the fields of the JSON document.- Plans explains the life of a stored plan.
- Run pravo in a pipeline uses plans in a job.
- State format lists the objects of a stored plan.