{-# LANGUAGE TupleSections #-} module Devops.Actions ( concurrentTurnup , concurrentTurndown , concurrentUpkeep , checkStatuses , sequentialTurnup, sequentialTurnDown , display , defaultDotify , dotifyWithStatuses , listUniqNodes ) where import Control.Concurrent.Async (waitCatch) import Control.Concurrent.STM (STM, atomically) import Control.Concurrent.STM.TVar (TVar, readTVar) import Control.Lens (view) import Data.Graph (edges, transposeG, vertices) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Monoid ((<>)) import qualified Data.Text as Text import Data.Tree (Tree (..), drawForest, flatten) import Text.Dot (Dot, edge, showDot, userNode, userNodeId) import Devops.Base (CheckResult (..), Op (..), OpDescription (..), OpUniqueId, PreOp, opUniqueId, preOpUniqueId, runPreOp) import Devops.Graph -- * Concurrent Operations -- | Turns up a graph concurrently. concurrentTurnup :: OpGraph -> IO () concurrentTurnup graph = do let s = snapshot TurnedUp graph emptyIntents sm <- atomically $ makeStatusesMap s asyncTurnupGraph noBroadcast sm s graph -- | Keeps a graph up concurrently. concurrentUpkeep :: OpGraph -> IO () concurrentUpkeep graph = do let s = snapshot TurnedUp graph emptyIntents sm <- atomically $ makeStatusesMap s upkeepGraph noBroadcast sm s graph defaultUpKeepFSM defaultDownKeepFSM -- | Turns down a graph concurrently. concurrentTurndown :: OpGraph -> IO () concurrentTurndown (g,f1,f2) = do let graph = (transposeG g, f1, f2) let s = snapshot TurnedDown graph emptyIntents sm <- atomically $ makeStatusesMap s asyncTurndownGraph noBroadcast sm s graph -- | Checks the graph and returns a dot formatted graph. checkStatuses :: OpGraph -> IO (Map OpUniqueId CheckResult) checkStatuses graph = do let s = snapshot TurnedUp graph emptyIntents statuses <- atomically $ makeStatusesMap s asyncs <- checkWholeGraph noBroadcast statuses s graph _ <- traverse waitCatch asyncs atomically $ extractStatuses statuses where extractStatuses :: OpStatusesMap -> STM (Map OpUniqueId CheckResult) extractStatuses statuses = Map.fromList <$> traverse extractOne (Map.toList statuses) extractOne :: (OpUniqueId, TVar OpStatus) -> STM (OpUniqueId, CheckResult) extractOne (opId, tvar) = do status <- readTVar tvar return $ (opId, view opCheckResult status) -- * Sequential Operations sequentialTurnup :: OpGraph -> IO () sequentialTurnup (g,f1,f2) = syncTurnupGraph noBroadcast (transposeG g, f1, f2) sequentialTurnDown :: OpGraph -> IO () sequentialTurnDown = syncTurnDownGraph noBroadcast -- | Display a forest of operations. display :: [Tree PreOp] -> IO () display = putStrLn . drawForest . (fmap . fmap) (show . opDescription . runPreOp) -- | Lists the uniq list of nodes. listUniqNodes :: [Tree PreOp] -> IO () listUniqNodes forest = let uniq f xs = Map.toList . Map.fromList $ zip (map f xs) xs in putStrLn . unlines . map (\(k,v) -> show (k, opName $ opDescription v)) . uniq opUniqueId . map runPreOp . concatMap flatten $ forest -- | Returns a .dot formatted string of a graph using a projection function to -- format every PreOp using .dot valid key/value node attributes. dotifyWith :: (PreOp -> [(String,String)]) -> OpGraph -> String dotifyWith attributes (g,lookupF,_) = showDot dotted where dotted :: Dot () dotted = do let node v = y where (y,_,_) = lookupF v let vs = vertices g let es = filter (uncurry (/=)) $ edges g mapM_ (\i -> userNode (userNodeId i) (attributes (node i))) vs mapM_ (\(i,j) -> edge (userNodeId i) (userNodeId j) []) es -- | Builds a dot-formatted representation of the graph defaultDotify :: OpGraph -> String defaultDotify = dotifyWith nameAttributes -- | Same as dotify but also colorize based on statuses passed in second argument. dotifyWithStatuses :: OpGraph -> Map OpUniqueId CheckResult -> String dotifyWithStatuses graph x = let allAttributes = nameAttributes <> colorFromStatusAttributes x in dotifyWith allAttributes graph nameAttributes :: PreOp -> [(String, String)] nameAttributes preOp = let o = runPreOp preOp in [("label", Text.unpack $ opName $ opDescription o)] colorFromStatusAttributes :: Map OpUniqueId CheckResult -> PreOp -> [(String, String)] colorFromStatusAttributes c op = let status = Map.lookup (preOpUniqueId op) c in maybe unknownStatusLabels labelsFromStatus status where labelsFromStatus :: CheckResult -> [(String, String)] labelsFromStatus Success = [("color", "green")] labelsFromStatus Skipped = [("color", "yellow")] labelsFromStatus Unknown = [("color", "blue")] labelsFromStatus _ = [("color", "red")] unknownStatusLabels :: [(String, String)] unknownStatusLabels = [("shape", "egg")]