§ Reference · Commands
pravo state
Inspect and edit the state graph: list, show, move or forget a node, read the log and the locks, copy the state, query it, convert an OpenTofu state file.
On this page
Quick reference
| Synopsis | pravo state SUBCOMMAND [ARGS] |
| Reads | The state graph in the location the specification names: a directory, pravo.state by default, or an S3-compatible bucket. |
| Tags | Every subcommand takes -t and -T and evaluates the specification with them. When $settings.state.path depends on a tag, the tags select the state. Without a tag, a subcommand reads the state that the defaults select. |
| Taught in | B3, Apply, outputs, state, B8, Renames, moves, imports and Lab 2, Two applies at once. |
An address is the CUE path of a resource: pet, api.config, servers[0]. Quote an address that contains brackets or quotes for the shell. See Resources and addresses.
Subcommands
| Subcommand | Prints or does | Writes the state | --json |
|---|---|---|---|
list |
Every address, sorted, one per line. | no | no |
show ADDR |
One node: bookkeeping as comments, then the attributes as CUE. | no | yes |
rm ADDR |
Forgets the node. The real object stays. | yes | no |
mv FROM TO |
Renames a node and updates the edges that point at it. | yes | no |
log |
The recorded transactions, newest first. | no | yes |
locks |
The locks and reservations held. | no | yes |
probe |
Checks that the S3 store honours the conditional requests that locks need. One line for each check. See S3-compatible stores. | no, it writes and deletes scratch objects | yes |
unlock ADDR, unlock --stale |
Releases a lock or a reservation, or every one that may be reclaimed. | yes | no |
pull DIR |
Copies the configured state into the local directory DIR. |
no | no |
push DIR |
Copies the local directory DIR into the configured backend. |
yes | no |
graph [--dot] |
The dependency edges, as from -> to or as Graphviz. |
no | no |
dependents ADDR |
Every node a change to ADDR can reach. |
no | no |
query EXPR |
The value of a CUE expression over the state. | no | yes |
import-tfstate FILE [--force] |
Converts a state file of OpenTofu or legacy Terraform. | yes | no |
list, show, log, locks, graph, dependents and query read an empty state when none exists, and create nothing.
Flags
| Flag | Subcommand | Meaning |
|---|---|---|
-t, --tag key=value |
all | Inject a value for an @tag() field. Repeatable. |
-T, --inject-vars |
all | Inject the system variables of CUE into tags written @tag(name,var=os). |
--dot |
graph |
Print Graphviz DOT. |
--force |
import-tfstate |
Replace a state that is not empty. |
The global flags apply.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Done. |
| 1 | The address is not in the state or already exists, or the expression is invalid. |
| 4 | Another process holds a lock on a node that rm or mv needs. |
| 6 | The specification does not load or evaluate. |
Examples
Work in an empty directory named demo. The state needs something in it.
package demo
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
$random: rnd.#Provider
base: $random.#Resource.random_integer & {min: 1, max: 9, seed: "base"}
port: $random.#Resource.random_integer & {
min: 20000 + base.result
max: 20100
seed: "port"
}
url: pravo.#Output & {value: "http://localhost:\(port.result)"}
base
port
show
// port (provider registry.opentofu.org/hashicorp/random v3.9.1 as random, schema version 0, revision 1)
// refreshed <age> ago, applied <age> ago, config hash <hash>
// depends on base
{
id: "20093"
keepers: null
max: 20100
min: 20009
result: 20093
seed: "port"
}
The first line names the provider instance after as, the schema version and the revision of the node. refreshed and applied say when a provider last reported the object and when pravo last wrote it. config hash identifies the configuration that the provider last saw. depends on and needed by list the edges. A record that a read left a mark on has the lines // drifted AGE ago and, for an object that is gone, // gone. The attributes include sensitive values in the clear.
graph, dependents and query
An edge port -> base means that port depends on base.
port -> base
port
2
{
base: 9
port: 20093
}
The scope of query has three fields:
| Field | Content |
|---|---|
resources |
A struct keyed by address. Each node has mode, type, name, provider, attributes, dependencies, revision and the times. |
edges |
A list of {from, to}. |
outputs |
The recorded outputs by name. |
With --json, query prints JSON. pravo state graph --dot | dot -Tsvg draws the graph when Graphviz is installed.
log
Every apply, destroy, import and state edit is a transaction. A plan that refreshes or moves records is one too, and so is a plan that finds drift: the entry says drift. A plan that reads nothing and writes nothing leaves none.
<txn> apply applied <time>
pravo apply --auto-approve
2 added, 0 changed, 0 destroyed
create base r0 -> r1
create port r0 -> r1
Each entry has the identifier, the kind, the outcome, the start time and the duration. Below come the command line as it was run, the actor if any, a summary, and one line per node with the revision before and after. The log is evidence. It prevents nothing, and it is as writable as the state.
An apply is running from the moment it holds its locks. When its command is killed, the record in the state keeps that status, and the log lists the apply as interrupted once no lock that is still live carries its transaction.
mv and rm
mv renames a record. It does not check the new address against the specification, so a plan after a typo destroys the record and creates the resource again.
Moved port to gateway
base
gateway
# port (random_integer) will be created
# gateway (random_integer) will be destroyed
Plan: 1 to add, 0 to change, 1 to destroy.
Move it back. rm forgets a record without touching the object, and the next plan creates the resource again.
Moved gateway to port
Removed port
# port (random_integer) will be created
Plan: 1 to add, 0 to change, 0 to destroy.
The declarative forms are $from for a move and $import for an adoption. See Moves and imports.
locks and reservations
An apply locks the nodes it writes until it finishes. A plan with --reserve claims nodes for an actor. locks lists both, and unlock releases either.
Reserved 1 node until <time>.
port reserved by alice until <time>: pravo plan --reserve=10m --actor alice
Unlocked port
No locks or reservations held.
A running command renews its locks every 2 minutes. The next command reclaims a lock whose process is gone on the same host, or that was not renewed for 10 minutes, and locks marks such a lock with [stale]. unlock ADDRESS releases a lock by hand. unlock --stale releases every lock and reservation that may be reclaimed, and no other.
Another environment
A root can keep one state per environment. Here $settings.state.path depends on the tag env:
package env
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
env: *"dev" | "prod" @tag(env)
$settings: pravo.#Settings & {state: path: "state/\(env)"}
$random: rnd.#Provider
n: $random.#Resource.random_integer & {min: 1, max: 9, seed: "n"}
n
prod
Without -t the subcommands read state/dev, which does not exist and is not created. With -t env=prod they read the state that the apply wrote.
pull and push
Copied 10 objects from ./pravo.state to backup
locks
meta.json
outputs.json
resources
txn
push copies a local directory into the configured backend. To move a root to a bucket, add the state block to $settings, run pravo state push pravo.state, and archive the local directory. See Remote state.
import-tfstate
import-tfstate converts the state file of OpenTofu or legacy Terraform. It accepts format version 4 and does not change the file. Work in a second root.
OpenTofu 1.13.1 wrote the state file below for this HCL. The page does not run OpenTofu and does not print the file.
data "terraform_remote_state" "net" {
backend = "local"
config = {
path = "../net/terraform.tfstate"
}
}
module "net" {
source = "./modules/net"
}
resource "random_pet" "server" {
length = 2
}
resource "random_pet" "node" {
count = 2
length = 2
}
output "name" {
value = random_pet.server.id
}
output "token" {
value = "t-${random_pet.server.id}"
sensitive = true
}
package migrate
Imported 4 resources and 2 outputs from terraform.tfstate into ./migrate/pravo.state
Warning: data.terraform_remote_state.net: skipped, provider terraform.io/builtin/terraform is built into OpenTofu and pravo cannot install it (read the outputs of another root with pravo.#RemoteOutputs)
Renamed instances (these are the addresses now in the state; point each at its place in the specification with $from):
module.net.random_pet.main -> random_pet.net_main
random_pet.node[0] -> random_pet.node_0
random_pet.node[1] -> random_pet.node_1
Every instance of a managed resource or data source becomes one node and keeps its OpenTofu address. An instance made by count or for_each is named <name>_<key>. A resource in a module gets the module path as a prefix: module.net.random_pet.main becomes random_pet.net_main. A data source keeps data.<type>.<name>. The command lists each renamed instance. Point each node at its place in the specification with $from or pravo state mv.
The name joins the module path, each instance key and the resource name with _. Any character other than an ASCII letter, an ASCII digit, _ or - becomes _, so module.site["eu.west"].random_pet.p becomes random_pet.site_eu_west_p. Two instances with one name stop the command, and nothing is imported: random_pet.region["eu"] and random_pet.region_eu both become random_pet.region_eu.
The dependencies between converted records are kept. OpenTofu writes a dependency on a resource in a module without the key of the module instance. The conversion points it at the record in the same instance of that module, or at every instance when the dependent sits outside the module. A state in another format than 4 stops the command with unsupported state format version N.
Outputs are copied with their sensitive mark. A sensitive output is recorded as {"$sensitive": value}, listed as (sensitive) and left out of pravo.#RemoteOutputs.
name: "free-akita"
token: (sensitive)
Deposed objects are skipped with a warning, and a tainted instance is recorded as healthy. A record of the built-in provider, terraform.io/builtin/terraform, is skipped with a warning that names its replacement: pravo.#RemoteOutputs for terraform_remote_state. Without --force, the command refuses to replace a state that is not empty.
Convert the state shows the whole procedure.
See also
- State format describes the files of the state directory.
- State explains nodes, transactions and locks.
pravo driftreads the records and marks the ones that differ from their objects.