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:

  1. ApplyFns::check is run for all items.
  2. Of the ones that return ApplyCheck::ExecRequired, Item::apply is run.
  3. 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


%3fn1fn1fn3fn3fn1->fn3fn2fn2fn2->fn3fn4fn4fn2->fn4fn3->fn4
// Item1
ApplyCheck::ExecRequired { .. }

// Item2
ApplyCheck::ExecNotRequired

// Item3
ApplyCheck::ExecRequired { .. }

// Item4
ApplyCheck::ExecRequired { .. }

Item::apply


%3fn1fn1fn3fn3fn1->fn3fn2fn2fn2->fn3fn4fn4fn2->fn4fn3->fn4

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.