How does the Peace framework shape automation to be resilient and provide a good user experience?
Automation Model
The following is a simplified model of what is used in the Peace framework, and is for teaching, not for accuracy.
-
Imagine we have the following automation steps:
- Compile an application.
- Launch a server.
- Upload the application to the server.
-
Each of these steps is a write function, connected together:
fn app_compile() -> _ { sh!("cargo build"); .. } fn srvr_launch() -> _ { sh!("ec2 run-instances .."); .. } fn file_upload(..) -> _ { sh!("scp {src} user@{ip}:{dest}"); .. } // Invoke the functions let path = app_compile(); let ip = server_launch(); let _ = file_upload(path, dest, ip);
-
Instead of only having a write function for each step, we define read functions:
The following show 3 functions for each step:
- Current state.
- Goal state.
- Logic to change the current state into the goal state.
// Application fn app_last_ts(..) -> Time { sh!("stat -c '%Y' target/debug/app"); .. } fn app_src_ts(..) -> Time { sh!("stat -c '%Y' **/*.rs"); .. } fn app_compile(..) -> _ { sh!("cargo build"); .. } // Server fn srvr_status(..) -> Srvr { sh!("ec2 describe-instances .."); .. } fn srvr_spec(..) -> Srvr { sh!("cat server.yaml"); .. } fn srvr_launch(..) -> _ { sh!("ec2 run-instances .."); .. } // File Upload fn file_dest(..) -> Md5Sum { sh!("ssh user@{ip} -C 'md5sum {dest}'"); .. } fn file_src(..) -> Md5Sum { sh!("md5sum {src}"); .. } fn file_upload(..) -> _ { sh!("scp {src} user@{ip}:{dest}"); .. }
-
With the current and goal states, we can calculate a diff, and whether we need to do work:
// Application fn app_ts_diff(Time, Time) -> TimeDiff { .. } fn app_compile_needed(TimeDiff) -> bool { .. } // Server fn srvr_diff(Srvr, Srvr) -> SrvrDiff { .. } fn srvr_launch_needed(SrvrDiff) -> bool { .. } // File Upload fn file_diff(Md5Sum, Md5Sum) -> Md5SumDiff { .. } fn file_upload_needed(Md5SumDiff) -> bool { .. }
-
Put these functions and data types into a trait:
// Specific functions fn file_dest(..) -> Md5Sum { sh!("ssh user@{ip} -C 'md5sum {dest}'"); .. } fn file_src(..) -> Md5Sum { sh!("md5sum {src}"); .. } fn file_diff(Md5Sum, Md5Sum) -> Md5SumDiff { .. } fn file_upload_needed(Md5SumDiff) -> bool { .. } fn file_upload(..) -> _ { sh!("scp {src} user@{ip}:{dest}"); .. } // Genericized grouping of step functions, and data. trait ItemSpec { type State; type StateDiff; fn state_current() -> Self::State; fn state_goal() -> Self::State; fn state_diff(Self::State, Self::State) -> Self::StateDiff; fn check(Self::StateDiff) -> bool; fn apply(Self::State, Self::State, Self::StateDiff); }
-
Peace groups all the items into a graph:
-
Peace provides commands to run different combinations of each item's functions:
StatesDiscoverCmd
: Runsstate_current
for all items.ApplyCmd
: Given a target state, runs the following for all items:state_current
state_goal
diff
check
apply
Ending Note
Peace implements other concepts to provide resilience and good user experience, and these will be written in future posts.
Peace is still evolving, and is not ready for general adoption.