State Ensure
When applying a change, the change should converge the current state to the goal state.
let graph = /* .. */;
let resources = /* .. */;
let resources = ApplyCmd::exec(graph, resources).await?;
Note that the Item::apply
requires implementers to return StatePhysical
, which is the state information generated during the exec
logic, but not necessarily within the implementers' control.
Method
To discover the current state of all items, the following method is used:
ApplyFns::check
is run for all items.- Of the ones that return
ApplyCheck::ExecRequired
,Item::apply
is run. - Finally,
Item::state_current
is run so that the end state can be compared with the goal state to confirm that they match.
ApplyFns::check
// Item1
ApplyCheck::ExecRequired { .. }
// Item2
ApplyCheck::ExecNotRequired
// Item3
ApplyCheck::ExecRequired { .. }
// Item4
ApplyCheck::ExecRequired { .. }
Item::apply
Items 1, 3, and 4 need to be executed, but Item2
's Item::apply
is skipped as check
indicated it isn't needed.
// Item1
()
// Item2
IpAddr::from_str("1.2.3.4")
// Item3
() // Note: version is logical state
// Item4
Revision::new("abcdef0123456")
Dry Run
let resources = Item::apply_dry(graph, resources).await?;
Similar to the Item::apply
, Item::apply_dry
is meant to simulate what would happen, and allow users to correct mistakes before actual execution.
Implementers must replace all write logic with mocks. These include:
- File writes
- Web requests
It is also recommended that read requests to external services are minimized to decrease the time to return feedback to the user. If possible, move read request logic to Item::state_current
so that it is stored by the StatesDiscoverCmd
.
Convergence / Non-Transactional Execution Recovery
Since these processes happen over distributed systems, and errors can happen at any point in the process, it is realistic to assume that the process doesn't happen transactionally.
ApplyFns
has been designed so that implementers will consider transitions from non-atomic states to the goal state. In simpler terms, if the goal state is "10 servers must exist", then:
- When the current state is 0 servers, then 10 servers should be launched.
- When the current state is 6 servers, then 4 servers should be launched.
As time and effort is saved by reusing existing useful state and not requiring complete clean up, recoverability and user experience is improved.