§ B9 · Track B · pravo
Drift: find and reconcile changes
Change a resource outside pravo, find the change with pravo drift, and reconcile it in both directions.
On this page
A colleague deletes a file, a bucket or a rule from the console. Which command tells you, and how soon?
Drift is a difference between the state and the real world that no pravo command caused. OpenTofu reports it with a refresh-only plan. pravo has a command for it. A plan reads the resources it reaches and the resources they read. It reads a resource only when its record is older than the refresh age. This lesson shows how that can hide a deletion.
tofu plan -refresh-only -detailed-exitcode
pravo drift
pravo drift reads the real state of every resource, whatever its refresh age, and reports the ones that changed. This lesson causes drift with a shell and reads it. No cloud account is involved: the resources are files.
Set a refresh age
Work in an empty directory. A plan reads a resource from its provider only when it looks at the resource, which means that it reaches it or reaches a resource that reads it, and its record is older than the refresh age. The default is zero, so a plan reads every resource it looks at. This root sets one hour. A longer refresh age makes a plan read fewer records and trust older ones.
package main
import (
"pravo.dev/pravo"
loc "pravo.dev/providers/hashicorp/local"
)
$settings: pravo.#Settings & {refresh: "1h"}
$local: loc.#Provider
motd: $local.#Resource.local_file & {
filename: "motd.txt"
content: "welcome\n"
}
banner: $local.#Resource.local_file & {
filename: "banner.txt"
content: "release 1\n"
}
Configuration does not load yet; generating schemas for the declared providers first.
Created cue.mod/module.cue
Providers:
local = hashicorp/local v2.9.1
...
pravo has been initialised. Run "pravo plan" to see what it would do.
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. [txn <txn>]
No drift. 2 resources refreshed; 0 pending changes from the specification.
No drift and exit code 0. The state and the files agree.
Break the world
Delete motd.txt, as someone with a shell does.
The records are a few seconds old and the refresh age is one hour.
Predict Does pravo plan notice that the file is gone?
No. The plan trusts a record that is within the refresh age and does not ask the provider.
No changes. Your infrastructure matches the configuration.
Graph: planned 0 of 2, 2 unchanged and skipped, 2 within refresh age.
The Graph: line says 2 within refresh age. Neither record was read.
--refresh=all reads every resource, whatever its age. The other values are age, the default, expired and false. expired reads every record that is past its age, and false reads nothing.
...
motd: no longer exists; it will be created again
...
# motd (local_file) will be created
+ motd: {
+ content: "welcome\n"
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
Graph: planned 1 of 2, 1 unchanged and skipped, 2 refreshed, 1 drifted.
The plan creates motd, and the Graph: line counts one drifted resource. pravo drift always reads everything and answers with an exit code.
Drift. 1 of 2 resources changed outside pravo:
motd: create
- refresh found the real state differs from the state (drift)
Exit code 2 means drift was found. Exit code 0 means none. A CI job reads the code and the JSON.
{
"time": "<time>",
"root": ".",
"drifted": [
{
"addr": "motd",
"action": "create",
"reasons": [
"refresh found the real state differs from the state (drift)"
],
"gone": true
}
],
"pending_changes": 1,
"summary": {
"add": 1,
"change": 0,
"destroy": 0,
"read": 0,
"blast_radius": 1
}
}
gone means the provider found no object. pending_changes is the number of changes in the plan.
Reconcile: restore
The specification is right and the world is wrong. pravo drift left a mark on motd: the record says that the file is gone, and the plan acts on that without reading the file again.
Plan: 1 to add, 0 to change, 0 to destroy.
Graph: planned 1 of 2, 1 unchanged and skipped, 2 within refresh age.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <txn>]
No drift. 2 resources refreshed; 0 pending changes from the specification.
Before pravo drift read the world, the same apply would have printed No changes: the records said that the file exists, and they were young. The mark is what makes the plan act. It stays until an apply writes the resource.
Not every change is drift
Drift is what the provider reports when it reads the resource. The local provider reads whether the file exists and what it contains. It does not read the file mode.
No drift. 2 resources refreshed; 0 pending changes from the specification.
A change that the provider cannot see is not drift. That includes this one.
Exercise
Reconcile by accepting
Someone rewrote banner.txt. Make the specification say what the file says, and bring the state in line without losing the edit.
echo "release 2" > banner.txt
Hint 1
Run pravo drift first. The provider treats a file whose content differs as gone, so the report says create.
Hint 2
Change content of banner to the new text. Then apply with --refresh=all, and run pravo drift again.
Solution
package main
import (
"pravo.dev/pravo"
loc "pravo.dev/providers/hashicorp/local"
)
$settings: pravo.#Settings & {refresh: "1h"}
$local: loc.#Provider
motd: $local.#Resource.local_file & {
filename: "motd.txt"
content: "welcome\n"
}
banner: $local.#Resource.local_file & {
filename: "banner.txt"
content: "release 2\n"
}
...
banner: no longer exists; it will be created again
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <txn>]
release 2
Both reconciliations end the same way: the specification, the state and the world agree, and pravo drift exits 0.
Clean up
Destroy complete! Resources: 0 added, 0 changed, 2 destroyed. [txn <txn>]
Recap
- Drift is a change to a resource that no pravo command made.
pravo driftreads every resource and exits 2 when it finds one. - A plan reads the resources it reaches, and the resources they read, when their records are older than the refresh age.
--refresh=expiredreads every record that is past its age, and--refresh=allreads everything. - A read that finds drift leaves a mark on the record. The next plan, saved plan and apply act on it until an apply writes the resource.
pravo drift --due --limit Nreads the oldest records that are past their refresh age, so a scheduled job covers a large estate in turns.- Reconcile by applying the specification, or by changing the specification to match the world.
Check yourself
What exit codes does pravo drift return?
0 when it finds no drift and 2 when at least one resource changed outside pravo.
Why does pravo plan print No changes after a file is deleted?
The plan does not reach the resource, and the record is within the refresh age, so the plan does not read the file.
The guide CI shows how a pipeline reads these exit codes. B10 turns to rules about what a plan may do.