module Azubi.Core.StateExecutors.LocalUnixStateExecutor where
import Azubi.Core.Model
import Azubi.Core.StateExecutor
import System.Directory
import System.Process hiding (runCommand)
import System.Exit
import System.Posix.Files (createSymbolicLink)
import System.FilePath.Posix
import Data.Algorithm.Diff
import Data.Algorithm.DiffOutput
data Verbosity = Verbose | Silent
data UnixSystem = UnixSystem { verbose :: Verbosity }
data PreProcessors =
PreProcessors { homeUpdate :: (String -> String) }
homeReplacement :: String -> String -> String
homeReplacement home path =
if ((head (splitDirectories path)) == "~")
then do
joinPath $ home : (drop 1 $ splitDirectories path)
else
path
instance LocalStateExecute UnixSystem where
prePorcessState _ (State checks commands comment) = do
home <- getHomeDirectory
let preProcessors = PreProcessors (homeReplacement home)
let newChecks = (map (prePorcessCheck preProcessors) checks)
let newCommands = (map (preProcessCommand preProcessors) commands)
return $ State newChecks newCommands comment
prePorcessState systemConfig (States checks states comment) = do
home <- getHomeDirectory
let preProcessors = PreProcessors (homeReplacement home)
let newChecks = (map (prePorcessCheck preProcessors) checks)
newStates <- sequence $ map (prePorcessState systemConfig) states
return $ States newChecks newStates comment
executeState systemConfig (State checks commands comment) = do
stateComment' comment
checkResult <- collectCheckResults systemConfig checks
case checkResult of
Yes -> return Fulfilled
No -> do
commandResult <- collectRunResults systemConfig commands
case commandResult of
Success -> return Fulfilled
Failure -> return Unfulfilled
executeState systemConfig (States check states comment) = do
stateComment' comment
result <- collectCheckResults systemConfig check
case result of
Yes -> return Fulfilled
No -> collectStateResults states
where
collectStateResults :: [State] -> IO StateResult
collectStateResults [] = return Fulfilled
collectStateResults (x:xs) = do
result <- executeState systemConfig x
case result of
Unfulfilled -> return Unfulfilled
Fulfilled -> collectStateResults xs
preProcessCommand ::PreProcessors -> Command -> Command
preProcessCommand _ (Run command arguments comment) =
Run command arguments comment
preProcessCommand preProcessors (FileContent path content) =
FileContent
((homeUpdate preProcessors) path)
content
preProcessCommand preProcessors (CreateSymlink path target) =
CreateSymlink
((homeUpdate preProcessors) path)
((homeUpdate preProcessors) target)
preProcessCommand preProcessors (CreateFolder path) =
CreateFolder
((homeUpdate preProcessors) path)
preProcessCommand preProcessors (Remove path) =
Remove
((homeUpdate preProcessors) path)
prePorcessCheck :: PreProcessors -> Check -> Check
prePorcessCheck _ (Check command arguments comment) =
Check command arguments comment
prePorcessCheck _ AlwaysYes =
AlwaysYes
prePorcessCheck preProcessors (Not check) =
Not (prePorcessCheck preProcessors check)
prePorcessCheck preProcessors (HasFileContent path content) =
HasFileContent
((homeUpdate preProcessors) path)
content
prePorcessCheck preProcessors (SymlinkExists path target) =
SymlinkExists
((homeUpdate preProcessors) path)
((homeUpdate preProcessors) target)
prePorcessCheck preProcessors (FolderExists path) =
FolderExists
((homeUpdate preProcessors) path)
prePorcessCheck preProcessors (DoesExist path) =
DoesExist
((homeUpdate preProcessors) path)
collectCheckResults :: UnixSystem -> [Check] -> IO CheckResult
collectCheckResults _ [] = return Yes
collectCheckResults systemConfig (check:rest) = do
result <- runCheck systemConfig check
case result of
Yes -> collectCheckResults systemConfig rest
No -> return No
collectRunResults :: UnixSystem -> [Command] -> IO CommandResult
collectRunResults _ [] = return Success
collectRunResults systemConfig (command:rest) = do
result <- runCommand systemConfig command
case result of
Success -> collectRunResults systemConfig rest
Failure -> return Failure
runCommand :: UnixSystem -> Command -> IO CommandResult
runCommand systemConfig (CreateFolder path') = do
path <- goodPath path'
logger' systemConfig commandComment' ["create directory ", path]
createDirectoryIfMissing True path
return Success
runCommand systemConfig (FileContent path' content) = do
path <- goodPath path'
logger' systemConfig commandComment' ["write content to ", path]
writeFile path $ unlines content
return Success
runCommand systemConfig (CreateSymlink path' target) = do
path <- goodPath path'
logger' systemConfig commandComment' ["create link", path, " to ", target]
createSymbolicLink target path
return Success
runCommand systemConfig (Run command arguments comment) = do
commandComment' comment
logger' systemConfig commandComment' ["run shell command", command, show arguments]
result <- runProcess' systemConfig [command] arguments
case result of
ExitSuccess -> return Success
_ -> return Failure
runCommand systemConfig (Remove path') = do
path <- goodPath path'
logger' systemConfig commandComment' ["remove", path]
removePathForcibly path
return Success
runCheck :: UnixSystem -> Check -> IO CheckResult
runCheck systemConfig (FolderExists path) = do
behind <- whatIsBehind' path
case behind of
IsFolder -> do
logger' systemConfig checkComment' ["FolderExists", path, ": YES"]
return Yes
_ -> do
logger' systemConfig checkComment' ["FolderExists", path, ": NO"]
return No
runCheck systemConfig (SymlinkExists path target) = do
goodTarget <- goodPath target
behind <- whatIsBehind' path
case behind of
IsSymlink behindTarget -> do
if behindTarget == goodTarget
then do
logger' systemConfig checkComment' ["SymlinkExists", path, "->", target, ": YES"]
return Yes
else do
logger' systemConfig checkComment' ["SymlinkExists", path, "->", target, ": NO"]
return No
_ -> do
logger' systemConfig checkComment' ["SymlinkExists", path, "->", target, ": NO"]
return No
runCheck systemConfig (HasFileContent path' content) = do
path <- goodPath path'
behind <- whatIsBehind' path
case behind of
IsFile -> checkContent
_ -> do
logger' systemConfig checkComment' ["HasFileContent", path, ": NO"]
return No
where
checkContent = do
path <- goodPath path'
file <- readFile path
currentContent <- return $ lines file
diff <- return $ getGroupedDiff currentContent content
case diff of
(Both _ _):[] -> do
logger' systemConfig checkComment' ["HasFileContent", path, ": YES"]
return Yes
_ -> do
logger' systemConfig checkComment' ["HasFileContent", path, ": NO"]
echo' [ppDiff diff]
return No
runCheck systemConfig (Check command args comment) = do
checkComment' comment
result <- runProcess' systemConfig [command] args
case result of
ExitSuccess -> do
logger' systemConfig checkComment' ["Shell Command Check", command , show args , ": YES"]
return Yes
_ -> do
logger' systemConfig checkComment' ["Shell Command Check", command , show args , ": NO"]
return No
runCheck systemConfig (Not check ) = do
result <- runCheck systemConfig check
case result of
No -> return Yes
Yes -> return No
runCheck _ AlwaysYes = return Yes
runCheck systemConfig (DoesExist path) = do
behind <- whatIsBehind' path
case behind of
DoesNotExist -> do
logger' systemConfig checkComment' ["DoesExist",path, ": NO"]
return No
_ -> do
logger' systemConfig checkComment' ["DoesExist",path, ": YES"]
return Yes
data FileType = IsFile
| DoesNotExist
| IsSymlink Path
| IsFolder
deriving (Show, Eq)
whatIsBehind' :: String -> IO FileType
whatIsBehind' path' = do
path <- goodPath path'
exists <- doesPathExist path
if exists
then figureOutFileType path
else return DoesNotExist
where
figureOutFileType path = do
checkFolder <- doesDirectoryExist path
checkSymlink <- pathIsSymbolicLink path
case (checkSymlink, checkFolder) of
(True, _) -> do
target <- getSymbolicLinkTarget path
goodTarget <- goodPath target
return $ IsSymlink goodTarget
(False, True) -> return IsFolder
(False, False) -> return IsFile
runProcess' :: UnixSystem -> [String] -> [String] -> IO ExitCode
runProcess' systemConfig command args = do
(_, _ , _ , checkHandle ) <- createProcess (shell $ unwords $ command ++ args ){ std_out = stdOutHandle }
waitForProcess checkHandle
where
stdOutHandle :: StdStream
stdOutHandle =
case (verbose systemConfig) of
Verbose -> Inherit
Silent -> NoStream
goodPath :: String -> IO String
goodPath path = if ((head (splitDirectories path)) == "~")
then do
home <- getHomeDirectory
return $ joinPath $ home : (drop 1 (splitDirectories path))
else
return path
echo' :: [String] -> IO ()
echo' text = putStrLn $ unwords $ "[Azubi]":text
stateComment' :: Maybe Comment -> IO ()
stateComment' (Just comment) = echo' ["[State]", comment]
stateComment' Nothing = return ()
commandComment' :: Maybe Comment -> IO ()
commandComment' (Just comment) = echo' ["[Run]", comment]
commandComment' Nothing = return ()
checkComment' :: Maybe Comment -> IO ()
checkComment' (Just comment) = echo' ["[Check]", comment]
checkComment' Nothing = return ()
logger' :: UnixSystem -> (Maybe Comment -> IO ()) -> [Comment] -> IO ()
logger' _ _ [] = return ()
logger' systemConfig messager comment =
case (verbose systemConfig) of
Verbose -> messager $ Just $ unwords comment
Silent -> return ()