azubi-0.2.0.3: src/Azubi/Core/Interpreter/LocalUnixInterpreter.hs
{-|
Module : Azubi.Core.Interpreter.LocalUnixInterpreter
Description : 'Interpreter' for Unix machines
Copyright : (c) Ingolf Wagner, 2017
License : GPL-3
Maintainer : azubi@ingolf-wagner.de
Stability : experimental
Portability : POSIX
Run 'State's on a Unix machine.
-}
module Azubi.Core.Interpreter.LocalUnixInterpreter where
import Azubi.Core.Model
import Azubi.Core.Interpreter
import System.Directory
import System.Exit
import System.Process hiding ( runCommand )
import System.Posix.Files ( createSymbolicLink )
import Data.Algorithm.Diff
import Data.Algorithm.DiffOutput
import qualified Azubi.Core.Interpreter.UnixUtils as Util
{-|
Unix System like Linux, AIX or OSX
<https://en.wikipedia.org/wiki/Unix>
-}
newtype UnixSystem = UnixSystem
{ verbose :: Verbosity
}
data Verbosity
= Verbose
| Silent
instance LocalInterpreter UnixSystem where
prePorcessState _ state@State {stateChecks = checks, stateCommands = commands} = do
preProcessors <- Util.preProcessors
let checkPreprocessor = prePorcessCheck preProcessors
let commandPreprocessor = preProcessCommand preProcessors
let processedChecks = map checkPreprocessor checks
let processedCommands = map commandPreprocessor commands
return
state {stateChecks = processedChecks, stateCommands = processedCommands}
prePorcessState systemConfig states@States { stateChecks = checksToPreprocess
, subStates = statesToPreProcess
} = do
preProcessors <- Util.preProcessors
let checkPreprocessor = prePorcessCheck preProcessors
let statePreprocessor = prePorcessState systemConfig
let processedChecks = map checkPreprocessor checksToPreprocess
processedSubStates <- mapM statePreprocessor statesToPreProcess
return
states {stateChecks = processedChecks, subStates = processedSubStates}
executeState systemConfig State { stateChecks = checks
, stateCommands = commands
, stateComment = 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 :: Util.PreProcessors -> Command -> Command
preProcessCommand _ run@Run {} = run
preProcessCommand preProcessors content@FileContent {filePath = path} =
content {filePath = Util.homeUpdate preProcessors path}
preProcessCommand preProcessors (CreateSymlink path target) =
CreateSymlink
(Util.homeUpdate preProcessors path)
(Util.homeUpdate preProcessors target)
preProcessCommand preProcessors (CreateFolder path) =
CreateFolder (Util.homeUpdate preProcessors path)
preProcessCommand preProcessors (Remove path) =
Remove (Util.homeUpdate preProcessors path)
prePorcessCheck :: Util.PreProcessors -> Check -> Check
prePorcessCheck _ check@Check {} = check
prePorcessCheck _ SkipChecks = SkipChecks
prePorcessCheck preProcessors (Not check) =
Not (prePorcessCheck preProcessors check)
prePorcessCheck preProcessors content@HasFileContent {pathToCheck = path} =
content {pathToCheck = Util.homeUpdate preProcessors path}
prePorcessCheck preProcessors (SymlinkExists path target) =
SymlinkExists
(Util.homeUpdate preProcessors path)
(Util.homeUpdate preProcessors target)
prePorcessCheck preProcessors (FolderExists path) =
FolderExists (Util.homeUpdate preProcessors path)
prePorcessCheck preProcessors (DoesExist path) =
DoesExist (Util.homeUpdate preProcessors path)
-- | unroll a number of Check(s)
-- | If one fail, they all fail
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
-- | unroll a number of Run Commands
-- | if one fail, they all fail
collectRunResults :: UnixSystem -> [Command] -> IO CommandResult
collectRunResults _ [] = return Success
collectRunResults systemConfig (commandToRun:rest) = do
result <- runCommand systemConfig commandToRun
case result of
Success -> collectRunResults systemConfig rest
Failure -> return Failure
-- | Run a command
runCommand :: UnixSystem -> Command -> IO CommandResult
runCommand systemConfig (CreateFolder path) = do
logger' systemConfig commandComment' ["create directory ", path]
createDirectoryIfMissing True path
return Success
runCommand systemConfig (FileContent path content) = do
logger' systemConfig commandComment' ["write content to ", path]
writeFile path $ unlines content
return Success
runCommand systemConfig (CreateSymlink path target) = do
logger' systemConfig commandComment' ["create link", path, " to ", target]
createSymbolicLink target path
return Success
runCommand systemConfig (Run commandToRun arguments comment) = do
commandComment' comment
logger'
systemConfig
commandComment'
["run shell command", commandToRun, show arguments]
result <- runProcess' systemConfig commandToRun arguments
case result of
ExitSuccess -> return Success
_ -> return Failure
runCommand systemConfig (Remove path) = do
logger' systemConfig commandComment' ["remove", path]
removePathForcibly path
return Success
-- | Run a Check
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
behind <- whatIsBehind' path
case behind of
IsSymlink behindTarget ->
if behindTarget == target
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
behind <- whatIsBehind' path
case behind of
IsFile -> checkContent
_ -> do
logger' systemConfig checkComment' ["HasFileContent", path, ": NO"]
return No
where
checkContent = do
file <- readFile path
let currentContent = lines file
let diff = 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 commandToRun args comment) = do
checkComment' comment
result <- runProcess' systemConfig commandToRun args
case result of
ExitSuccess -> do
logger'
systemConfig
checkComment'
["Shell Command Check", commandToRun, show args, ": YES"]
return Yes
_ -> do
logger'
systemConfig
checkComment'
["Shell Command Check", commandToRun, show args, ": NO"]
return No
runCheck systemConfig (Not check) = do
result <- runCheck systemConfig check
case result of
No -> return Yes
Yes -> return No
-- returns `No` to make the State run always the Commands
runCheck _ SkipChecks = return No
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)
-- | helper function to check whats behind a path
whatIsBehind' :: String -> IO FileType
whatIsBehind' path' = do
preProcessors <- Util.preProcessors
let path = Util.homeUpdate preProcessors 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
return $ IsSymlink target
(False, True) -> return IsFolder
(False, False) -> return IsFile
{-|
run a process and wait until it's finished
return the exit code
-}
runProcess' :: UnixSystem -> String -> [String] -> IO ExitCode
runProcess' systemConfig commandToRun args = do
(_, _, _, checkHandle) <- createProcess shellProcess {std_out = stdOutHandle}
waitForProcess checkHandle
where
shellCommand = unwords $ commandToRun : args
shellProcess = shell shellCommand
stdOutHandle :: StdStream
stdOutHandle =
case verbose systemConfig of
Verbose -> Inherit
Silent -> NoStream
-- | simple print function
echo' :: [String] -> IO ()
echo' text = putStrLn $ unwords $ "[Azubi]" : text
-- | render state comments
stateComment' :: Maybe Comment -> IO ()
stateComment' (Just comment) = echo' ["[State]", comment]
stateComment' Nothing = return ()
-- | render command comments
commandComment' :: Maybe Comment -> IO ()
commandComment' (Just comment) = echo' ["[Run]", comment]
commandComment' Nothing = return ()
-- | render check comments
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 ()