packages feed

batchd-0.1.1.0: src/Batchd/Daemon/Local.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Batchd.Daemon.Local where

import Control.Monad
import Control.Monad.Trans
import Control.Concurrent
import qualified Control.Exception as E
import Data.Conduit
import Data.Maybe (fromMaybe)
import Data.Int
import qualified Data.Conduit.Combinators as C
import qualified Data.Conduit.List as CL
import Data.Conduit.Binary (sourceHandle)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Data.Text.Lazy as TL
import Data.Text.Format.Heavy
import Data.Text.Format.Heavy.Parse.Shell
import Data.Time
import System.Process
import System.FilePath
import System.Directory
import System.IO
import System.Exit (ExitCode (..))
import System.Log.Heavy

import Batchd.Core.Common.Types
import Batchd.Core.Common.Config
import Batchd.Core.Daemon.Logging
import Batchd.Core.Daemon.Hosts
import Batchd.Common.Types
import Batchd.Common.Data
import Batchd.Daemon.Types

execLocalCommand :: LoggingTState -> T.Text -> IO ExitCode
execLocalCommand lts command = do
    infoIO lts $(here) "Executing on dispatcher host: {}" (Single command)
    let opts = (shell $ T.unpack command) {std_out = CreatePipe, std_err = CreatePipe}
    withCreateProcess opts $ \_ (Just stdout) (Just stderr) process -> do
      forkIO $ retrieveOutput "error" stderr
      retrieveOutput "output" stdout
      ec <- waitForProcess process
      infoIO lts $(here) "Exit code: {}" (Single $ show ec)
      return ec
  where
    retrieveOutput :: T.Text -> Handle -> IO ()
    retrieveOutput kind handle = do
      sourceHandle handle =$= C.decodeUtf8 =$= C.linesUnbounded $$ CL.mapM_ $ \line ->
        infoIO lts $(here) "{}> {}" (kind, line)
      hClose handle

execLocalCommands :: LoggingTState -> [T.Text] -> IO ExitCode
execLocalCommands _ [] = return ExitSuccess
execLocalCommands lts (command : commands) = do
  ec <- execLocalCommand lts command
  if ec == ExitSuccess
    then execLocalCommands lts commands
    else return ec

withLocalScript :: FilePath -> Int64 -> [T.Text] -> (String -> IO ()) -> IO ()
withLocalScript scriptsDir jobId commands actions = do
  if length commands == 1
    then actions (T.unpack $ head commands)
    else do
      let scriptText = formatScript commands
          scriptName = TL.unpack $ format "batchd_job_{}.script" (Single jobId)
          scriptPath = scriptsDir </> scriptName
          chmodX = do
            perm <- getPermissions scriptPath
            setPermissions scriptPath (setOwnerExecutable True perm)
      E.bracket_ (TIO.writeFile scriptPath scriptText >> chmodX)
                 (removeFile scriptPath)
                 (actions scriptPath)

processOnLocalhost :: JobInfo -> OnFailAction -> String -> ResultsChan -> IO ()
processOnLocalhost job onFail command resultChan = do
    let opts = (shell command) {std_out = CreatePipe, std_err = CreatePipe}
    withCreateProcess opts $ \_ (Just stdout) (Just stderr) process -> do
      forkIO $ retrieveOutput StderrLine stderr
      retrieveOutput StdoutLine stdout
      ec <- waitForProcess process
      writeChan resultChan (job, Exited ec onFail)
  where
    retrieveOutput cons handle = do
      sourceHandle handle =$= C.decodeUtf8 =$= C.linesUnbounded $$ CL.mapM_ $ \line ->
        writeChan resultChan (job, cons line)
      hClose handle