Conceptual Introduction
2023-04-03
concepts

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.

  1. Imagine we have the following automation steps:

    1. Compile an application.
    2. Launch a server.
    3. Upload the application to the server.
  2. 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);
    
  3. Instead of only having a write function for each step, we define read functions:

    The following show 3 functions for each step:

    1. Current state.
    2. Goal state.
    3. 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}"); .. }
    
  4. 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       { .. }
    
  5. 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);
    }
    
  6. Peace groups all the items into a graph:

  7. Peace provides commands to run different combinations of each item's functions:

    • StatesDiscoverCmd: Runs state_current for all items.
    • ApplyCmd: Given a target state, runs the following for all items:
      1. state_current
      2. state_goal
      3. diff
      4. check
      5. 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.

< Prev
Next >