§ Reference · Commands

pravo apply

Plan, ask for confirmation and apply, or apply a saved plan exactly as it was reviewed, in whole or in closed parts.

View as Markdown

On this page

Quick reference

Synopsis pravo apply [flags]
pravo apply PLANFILE [--only ADDR,...] [--policy FILE]...
Changes infrastructure Yes.
Confirmation A plain apply prints the plan and asks Do you want to perform these actions?. Only yes is accepted. A saved plan asks nothing.
Locks The nodes the plan writes are locked from the start of the apply. An apply whose nodes another process holds is rejected before it changes anything.
Taught in B3, Apply, outputs, state and B7, Changing live infrastructure.

Flags

Flag Default Meaning
--auto-approve off Apply without asking.
--refresh=age|expired|false|all age As for pravo plan. Refused with a plan file.
--full off As for pravo plan. Refused with a plan file.
--replace ADDR none As for pravo plan. Refused with a plan file: the replacement is decided when the plan is made.
--only ADDR,... none With a plan file: apply these changes now and leave the rest in the file. Each name must be a change in the file: a node that the plan left alone has none. The selection must be closed under dependencies. Repeatable, or comma-separated.
--policy FILE none A CUE file the saved plan must satisfy before anything is applied. Repeatable. Without a plan file the command is refused.
-t, --tag key=value, -T none Tags, as for pravo plan. Refused with a plan file: the plan carries its own tags.

The global flags apply. With --json, a plain apply needs --auto-approve.

Exit codes

Code Meaning
0 Applied, or the saved plan has nothing left to apply.
1 An error: the prompt was answered with anything but yes, a provider failed, the apply was interrupted, a flag does not combine with the form, --only names a node that has no change in the plan file, or --json without --auto-approve.
3 The saved plan is stale, or an apply found that a resource is not what the plan said: a node that another command wrote since, or a provider that plans the resource differently. The reasons follow the error.
4 Rejected: a node is locked or reserved by another actor, before anything changes, or a lock was taken over while the apply ran (lock lost).
5 A policy refused the plan, a --policy file cannot be used, --policy was given without a plan file, or a module does not match its lock.
6 The specification does not load or evaluate.

Examples

Work in an empty directory named demo. The specification has two resources, and port reads base.

main.cue
package demo

import 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"
}

Apply with a prompt

terminal
$pravo init > /dev/null
$echo yes | pravo apply
pravo will perform the following actions:

  # base (random_integer) will be created
  + base: {
  +     id:     (known after apply)
  +     max:    9
  +     min:    1
  +     result: (known after apply)
  +     seed:   "base"
    }

  # port (random_integer) will be created
  + port: {
  +     id:     (known after apply)
  +     max:    20100
  +     min:    (known after apply)
  +     result: (known after apply)
  +     seed:   "port"
    }

Plan: 2 to add, 0 to change, 0 to destroy.
Graph: planned 2 of 2.

Do you want to perform these actions?
  Only 'yes' will be accepted to approve.

  Enter a value:
base: Creating...
base: Creation complete after 0s [id=9]
port: Creating...
port: Creation complete after 0s [id=20093]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed. [txn <txn>]

The plan comes first, as for pravo plan. The progress lines carry the address. The last line counts the actions and names the transaction. pravo state log shows it later.

Any answer but yes cancels. Nothing changes.

Change the specification

Narrow base and add solo. The plan replaces base and port, and creates solo.

main.cue
package demo

import rnd "pravo.dev/providers/hashicorp/random"

$random: rnd.#Provider

base: $random.#Resource.random_integer & {min: 1, max: 8, seed: "base"}

port: $random.#Resource.random_integer & {
	min:  20000 + base.result
	max:  20100
	seed: "port"
}

solo: $random.#Resource.random_integer & {min: 1, max: 99, seed: "solo"}
terminal
$echo no | pravo apply > answer.txt
$echo "exit $?"
$tail -n 3 answer.txt
exit 1
  Enter a value:

Error: apply cancelled

Apply a saved plan

terminal
$pravo plan --out change.plan > /dev/null

A saved plan runs without a prompt. Before it applies anything, pravo checks that the specification, the provider versions and every state node the plan touches are as they were when the plan was made. Each resource is planned again by its provider and compared with the saved change. A provider that plans something else at that point stops the apply with exit code 3. A data source that the plan read is not read again. A saved plan whose only change is an output, or the removal of one, is applied too: the apply records it, and a second apply of the file has nothing left to apply.

Apply a part of a saved plan

--only takes a set that is closed under dependencies. port reads base, so port alone is refused. The message names the smallest set that works.

terminalexit 1
$pravo apply change.plan --only port
Error: the selection is not closed under dependencies; it also needs base
  (the smallest closed selection is -only base,port)

A name that is not a change in the file is refused. A node that the plan left alone has no entry in the file, so naming it is refused too.

terminalexit 1
$pravo apply change.plan --only absent
Error: absent is not part of the saved plan

Judge a plan before applying it

A policy is judged against a saved plan. A plain apply plans again while it runs, and that second plan is not the one that was judged.

policy/small.cue
summary: add: <=1
terminalexit 5
$pravo apply --policy policy/small.cue
Error: a policy is judged against a saved plan: run "pravo plan -out <file>", then "pravo apply <file>"

The saved plan is judged whole, whatever --only selects. solo adds one resource, and the plan adds three. A limit on the size of a plan cannot be passed by applying it in stages.

terminalexit 5
$pravo apply change.plan --only solo --policy policy/small.cue
Error: the plan violates the policy policy/small.cue (sha256:267ca1f7c1b4):
summary.add: invalid value 3 (out of bound <=1):
    policy/small.cue:1:15
    plan:1:<col>

Nothing was applied. See Policies for what a policy can say.

Apply the closed part

solo depends on nothing. A policy that allows three additions holds for the whole plan, and the command names it before the first change.

policy/enough.cue
summary: add: <=3
terminal
$pravo apply change.plan --only solo --policy policy/enough.cue
Policy policy/enough.cue (sha256:6a60ce99537a) is satisfied.
Applying 1 of 3 remaining changes from change.plan.
solo: Creating...
solo: Creation complete after 0s [id=93]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <txn>]
2 changes remain in change.plan; apply with "pravo apply change.plan".

The rest stays in the file. pravo show change.plan marks the applied change with (already applied in txn ...). Running pravo apply change.plan again applies what is left.

Parts of one file may be applied at the same time when they share no node: each apply marks its own changes in the file, which it locks and writes whole, and the file records the marks of all of them. A part that shares a node with one that is running is refused with exit code 4, as any two overlapping applies are. A file that was replaced by another plan while a part was applied is not marked, and the apply reports that.

Outputs of a part

An output that reads a resource that the selection leaves out, and that does not exist yet, has no value. A partial apply records the outputs that have one and leaves out the others. The apply of the rest records them. The root outs has two resources and an output for each:

outs/main.cue
package outs

import (
	"pravo.dev/pravo"
	rnd "pravo.dev/providers/hashicorp/random"
)

$random: rnd.#Provider

first:  $random.#Resource.random_integer & {min: 1, max: 9, seed: "first"}
second: $random.#Resource.random_integer & {min: 1, max: 9, seed: "second"}

first_value:  pravo.#Output & {value: first.result}
second_value: pravo.#Output & {value: second.result}
terminal
$pravo -C outs init > /dev/null
$pravo -C outs plan --out part.plan > /dev/null
$pravo -C outs apply part.plan --only first
Applying 1 of 2 remaining changes from part.plan.
first: Creating...
first: Creation complete after 0s [id=7]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <txn>]
1 change remains in part.plan; apply with "pravo apply part.plan".

Outputs:
  first_value: 7

The second change is still in the plan file. Applying it records the other output:

terminal
$pravo -C outs apply part.plan
second: Creating...
second: Creation complete after 0s [id=1]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <txn>]

Outputs:
  first_value: 7
  second_value: 1

Outputs of a partial destroy

A saved destroy plan, made with pravo plan --destroy --out, is applied in parts like any other. A part leaves resources behind, and their outputs stay with them. After a part, an output that reads none of the resources the part destroyed stays as it is recorded. An output that reads one of them is removed. The part that destroys the last resources removes every output that is left, including an output that reads no resource. pravo destroy has one part and removes them all.

The root outs from above has both resources and both outputs. Save the destroy plan and destroy second alone:

terminal
$pravo -C outs plan --destroy --out down.plan > /dev/null
$pravo -C outs apply down.plan --only second
$pravo -C outs output
Applying 1 of 2 remaining changes from down.plan.
second: Destroying... [id=1]
second: Destruction complete after 0s

Destroy complete! Resources: 0 added, 0 changed, 1 destroyed. [txn <txn>]
1 change remains in down.plan; apply with "pravo apply down.plan".
first_value: 7

second_value read second and is gone. first_value reads first, which remains, and keeps its value. The rest of the plan destroys first and with it the last output:

terminal
$pravo -C outs apply down.plan | tail -n 1
$pravo -C outs output --json
Destroy complete! Resources: 0 added, 0 changed, 1 destroyed. [txn <txn>]
{}

A stale plan

Any change to a file that was loaded for the root makes the plan stale. A comment counts.

terminalexit 3
$echo "// edited" >> main.cue
$pravo apply change.plan
Error: saved plan is stale; run "pravo plan" again:
  - the configuration changed since the plan was made

Plan again. The new plan holds what is left, and applies in one step.

terminal
$pravo plan --out change.plan > /dev/null
$pravo apply change.plan
base: Destroying... [id=9]
base: Creating replacement...
base: Replacement complete after 0s [id=3]
port: Destroying... [id=20093]
port: Creating replacement...
port: Replacement complete after 0s [id=20061]

Apply complete! Resources: 2 added, 0 changed, 2 destroyed. [txn <txn>]

Interrupting an apply

A node that fails stops the apply from starting any other node. A node whose provider is already at work finishes, its record is written, and only then are the locks released. The root flight has a resource that cannot be written and one that takes a second. bad waits for two other resources and then cannot create its file, because blocker is a file and nothing can be created below it. slow waits for nothing, and its file is a named pipe: opening a named pipe for writing waits until something opens it for reading. The command in the background does that after a second.

flight/main.cue
package flight

import loc "pravo.dev/providers/hashicorp/local"

$local: loc.#Provider

first:  $local.#Resource.local_file & {filename: "first.txt", content: "first"}
second: $local.#Resource.local_file & {filename: "second.txt", content: "after \(first.id)"}
bad:    $local.#Resource.local_file & {filename: "blocker/x.txt", content: "after \(second.id)"}
slow:   $local.#Resource.local_file & {filename: "pipe", content: "slow"}
terminal
$pravo -C flight init > /dev/null
$echo blocker > flight/blocker
$mkfifo flight/pipe
$(sleep 1; cat flight/pipe > /dev/null) &
$pravo -C flight apply --auto-approve > flight.txt 2>&1
$echo "exit $?"
$grep -E 'slow: Creation complete|^Error' flight.txt
$pravo -C flight state list
exit 1
slow: Creation complete after 0s [id=57e8a7776d6892a83f2a49678f8141f4fb883e62]
Error: bad: Create local file error: An unexpected error occurred while writing the file
first
second
slow

bad failed while slow was at work, and slow has a record. A signal stops the apply in the same way, in three stages.

Signal What pravo does
The first SIGINT or SIGTERM No node is started. pravo prints Interrupted. Waiting for N operations; interrupt again to ask the providers to stop. and waits for the providers that are at work. Their results are recorded. The apply fails with interrupted: M of T tasks were not started when it had nodes left to start.
The second pravo asks every provider to stop what it is doing, and waits up to 30 seconds. A provider that stops returns the state it had reached, and the apply records it.
The third, or the end of the 30 seconds pravo exits without waiting for the providers, with exit code 1.

The records of the changes that finished, the transaction and the release of the locks are written to the state after a signal as before it, also when the state is in a bucket. When the third signal comes and the state does not answer in 20 seconds, pravo exits anyway and says so.

A change whose result is not known is named in the error: the call to its provider failed, or the provider answered an update or a destruction with an error and no state, or the provider was asked to stop and returned no state, or pravo exited without waiting. The message reads creation of NODE was in flight; its result is unknown; check the provider. The verb is creation, update or destruction. If the node has a record, the apply writes it again without refreshed_at: the revision moves, so a saved plan made against the record is stale, and the next plan reads the node whatever its refresh age, also when the plan does not reach it. pravo state log lists it as unsettled. A creation that has no record has none to mark, and the message is all that is left of it. A creation that the provider refused with an error and no state is not named: the object does not exist.

A change that the provider finished and that the state could not record is named too: creation of NODE finished, but the state does not record it; check the provider. The record of the node, if it has one, is marked in the same way. The message follows the error of the write, which names the node.

pravo releases the locks after the records are written, and when it exits without waiting. A command that is killed releases nothing. Its locks go stale, pravo state locks marks them, and the next plan reads the nodes they held whatever their refresh age, as Plans describes.

JSON

With --json, a plain apply prints the plan document, then an error object, unless --auto-approve is given. The progress and the result are newline-delimited events.

terminal
$pravo apply --json --auto-approve --replace solo 2>/dev/null | grep -E '"kind":"(replace|apply)"'
{"time":"<time>","kind":"replace","addr":"solo","phase":"done","message":"solo: Replacement complete after 0s [id=93]","id":"93","txn":"<txn>"}
{"added":1,"changed":0,"destroyed":1,"kind":"apply","outputs":{},"phase":"done","planfile":"","remaining":0,"txn":"<txn>"}

The last line has "kind":"apply", the counts, the outputs and the name of the plan file when there is one. The event fields are in Global flags.

See also