§ Concepts
How pravo works
One run of pravo in seven stages, from the CUE files of a directory to the providers, the state and the output.
On this page
A run is one pravo command. pravo plan takes a directory of CUE files, a state and a set of providers, and prints the changes. pravo apply makes them. Both pass through the same stages, in the same order.
This page names the stages, says what each one reads and writes, and shows where each command stops.
The seven stages
1. Evaluate
pravo loads the CUE package in the directory. Every file of the package joins it, in any order. CUE unifies the files and their imports into one value. The imports include the definitions that pravo init generated from the provider schemas.
A conflict, a misspelt attribute or a value outside its constraint ends the run here. The error names the file and the line. No provider has been asked for anything. Evaluation has the details.
2. Discover
pravo walks the value and collects every struct that carries a $kind field. Such a struct is a resource, a data source, an ephemeral resource, a function call, a provider, an output, an environment variable or the outputs of another root. The address of a struct is its path in the value.
Definitions, hidden fields and the top-level $settings field are not searched. Resources and addresses explains the identity.
3. Build the graph
pravo reads every field of every resource and records each reference to another resource as a dependency. A reference in provider configuration is a dependency of every resource that uses the provider. The result is a graph in which a resource runs after everything it refers to. Resources that refer to each other in a circle stop the run with dependency cycle between and the names of the resources on the cycle.
4. Plan
pravo visits the graph in dependency order, at most ten resources at a time. For each resource it reads the recorded node from the state and converts the body to a value that the provider understands. A resource that is unchanged since the last apply is skipped. The plan reads the record of a resource from its provider only for the resources it reaches and for the resources they read, and only when the record is older than its refresh age. The other records are not read.
Every other resource goes to its provider, which returns the planned state. From the answer pravo derives one action: create, update, replace, delete or no-op. A record in the state that no struct declares becomes a delete.
This stage writes only records that it refreshed, moved or imported, and the marks that a read leaves on a record whose object differs or is gone. Plans covers it.
5. Judge
A policy is a CUE file. The person who runs pravo names it with --policy. pravo prints the plan, then unifies each policy with the plan document. A plan that violates a policy ends the run with exit code 5.
Without --policy, this stage does nothing. Policies covers both kinds of rule.
6. Apply
pravo starts a transaction and locks every node that the plan writes. A lock held by another process stops the run before anything changes. pravo then runs the graph again over the nodes the plan changes and the nodes they read.
Each changed resource is planned a second time, now with the values of the resources applied before it, compared with the plan, and then changed through its provider. The apply does not read the providers again: it uses what the plan read. A node that a change reads is taken from its record. Records that no struct declares are destroyed last, in reverse dependency order.
7. Record
pravo writes each node when its change completes, with a new revision. When the graph is finished, it writes the outputs and the transaction and releases the locks. An interrupted apply leaves the nodes that were written before it stopped.
State describes the records.
The parts of a run
pravo is one process. It reads the files of the root and, when asked, the files of the policies. It starts one child process for each provider instance. It talks to the process over gRPC, in version 5 or 6 of the plugin protocol. It reads and writes the state through a small storage interface that a directory or an S3 bucket implements.
A provider process starts the first time a resource needs it and stops when the command ends. Providers describes how pravo finds, starts and configures them.
One run, as pravo reports it
pravo -v logs what pravo decides, on standard error. The filter below removes the timings and the paths and keeps the lines that mark a stage. Work in an empty directory.
package estate
import (
rnd "pravo.dev/providers/hashicorp/random"
loc "pravo.dev/providers/hashicorp/local"
)
$random: rnd.#Provider
$local: loc.#Provider
port: $random.#Resource.random_integer & {
min: 20000
max: 29999
seed: "web"
}
web: $local.#Resource.local_file & {
filename: "web.conf"
content: "port = \(port.result)\n"
}
// Deleting a managed resource needs a human.
changes: [...{
addr: string
mode: string
action: string
if mode == "managed" && action == "delete" {
error("\(addr): delete is not allowed")
}
}]
Configuration does not load yet; generating schemas for the declared providers first.
Created cue.mod/module.cue
Providers:
...
pravo has been initialised. Run "pravo plan" to see what it would do.
The first run is an apply into an empty state.
loaded managed=2 data=0 ephemeral=0 calls=0 outputs=0 providers=2 moves=0 imports=0
state opened backend=local nodes=0
plan refresh=age full=false destroy=false tasks=2
graph built tasks=2
provider started provider=random version=<v>
task addr=port phase=plan outcome=ok action=create
provider started provider=local version=<v>
task addr=web phase=plan outcome=ok action=create
Plan: 2 to add, 0 to change, 0 to destroy.
Graph: planned 2 of 2.
apply changes=2 destroy=false
task addr=port phase=apply outcome=ok action=create
task addr=web phase=apply outcome=ok action=create
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. [txn <id>]
Read the lines in order.
| Line | Stage |
|---|---|
loaded |
1 and 2. The directory evaluated, and discovery found two resources and no data sources. |
state opened |
The state was read. It holds no nodes. |
plan, graph built |
3, and the start of 4. Two resources are in the graph. |
provider started |
A provider process starts when the first resource that needs it is reached. |
task ... phase=plan |
4. One line per resource, with the action. |
apply |
6. The graph runs again over the nodes the plan changes and the nodes they read, now with the values of the resources applied before. |
Apply complete! |
7. The transaction id is the record of the run. |
The second run is a plan with a policy. Nothing changed, so every resource is skipped, and the policy is satisfied.
loaded managed=2 data=0 ephemeral=0 calls=0 outputs=0 providers=2 moves=0 imports=0
state opened backend=local nodes=2
plan refresh=age full=false destroy=false tasks=2
graph built tasks=2
task addr=port phase=plan outcome=ok action=no-op skipped=true unread=true
task addr=web phase=plan outcome=ok action=no-op skipped=true unread=true
No changes. The specification matches the state. 2 records were not read; run pravo drift to compare them with the infrastructure.
Graph: planned 0 of 2, 2 unchanged and skipped, 2 not read.
Policy policy/no-delete.cue (sha256:a66a4e4c547d) is satisfied.
The Policy line is stage 5. It appears after the plan and before any file is saved.
Where each command stops
| Command | Stages | What else it does |
|---|---|---|
pravo validate |
1 and 2 | Resolves the providers and reads their schemas. It starts no provider and opens no state. |
pravo eval |
1 and 2 | Opens the state and fills the recorded attributes of each resource into the value. |
pravo plan |
1 to 5 | -out saves the plan after the policies are satisfied. |
pravo drift, pravo why |
1 to 4 | drift reads every record, or with --due the records past their refresh age. why prints the reasons of each change. |
pravo apply |
1 to 4, then 6 and 7 | Prints the plan and asks for yes. With --policy it is refused. |
pravo apply FILE |
1 and 2, then 5 to 7 | Reads the saved plan in place of stages 3 and 4 and checks that it is current. Stage 6 builds the graph and plans each resource of the plan again. |
pravo destroy |
1 and 2, then 4 to 7 | The plan is the list of records in the state, with no graph built from the specification. It is judged before the prompt. |
What each stage touches
| Stage | Reads | Writes |
|---|---|---|
| 1 Evaluate | The .cue files, cue.mod, tags |
Nothing |
| 2 Discover | The value | Nothing |
| 3 Graph | The value | Nothing |
| 4 Plan | Records, providers for the records it looks at, ephemeral resources, data sources | Refreshed, moved and imported records, marks, a plan transaction when it wrote anything, reservations |
| 5 Judge | The policy files | Nothing |
| 6 Apply | Records, providers | Locks, the objects that providers manage |
| 7 Record | Nothing | Nodes, outputs, the transaction |
Recap
- A run evaluates, discovers, builds the graph and plans. Apply adds locks, changes and records.
- Evaluation and discovery read files only. No provider is asked for anything before the plan stage.
- Each command stops at a fixed stage.
validateandevalstop after discovery.
Next: Evaluation follows the first three stages in detail.