propellor/Propellor/SimpleSh.hs

113 lines
3.2 KiB
Haskell
Raw Normal View History

-- | Simple server, using a named pipe. Client connects, sends a command,
-- and gets back all the output from the command, in a stream.
--
-- This is useful for eg, docker.
module Propellor.SimpleSh where
import Network.Socket
2014-05-11 13:30:45 +00:00
import Control.Concurrent
import Control.Concurrent.Async
import System.Process (std_in, std_out, std_err)
import Propellor
import Utility.FileMode
import Utility.ThreadScheduler
data Cmd = Cmd String [String]
deriving (Read, Show)
data Resp = StdoutLine String | StderrLine String | Done
deriving (Read, Show)
simpleSh :: FilePath -> IO ()
simpleSh namedpipe = do
nukeFile namedpipe
let dir = takeDirectory namedpipe
createDirectoryIfMissing True dir
modifyFileMode dir (removeModes otherGroupModes)
s <- socket AF_UNIX Stream defaultProtocol
2014-04-10 04:51:12 +00:00
bindSocket s (SockAddrUnix namedpipe)
listen s 2
forever $ do
(client, _addr) <- accept s
2014-05-11 13:30:45 +00:00
forkIO $ do
h <- socketToHandle client ReadWriteMode
maybe noop (run h) . readish =<< hGetLine h
where
run h (Cmd cmd params) = do
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params]
chan <- newChan
let runwriter = do
v <- readChan chan
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "writer got:", show v]
hPutStrLn h (show v)
2014-04-20 15:38:50 +00:00
hFlush h
case v of
Done -> noop
_ -> runwriter
writer <- async runwriter
2014-05-09 15:02:05 +00:00
flip catchIO (\_e -> writeChan chan Done) $ do
let p = (proc cmd params)
{ std_in = Inherit
, std_out = CreatePipe
, std_err = CreatePipe
}
(Nothing, Just outh, Just errh, pid) <- createProcess p
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "started"]
2014-05-09 15:02:05 +00:00
let mkreader t from = maybe noop (const $ mkreader t from)
=<< catchMaybeIO (writeChan chan . t =<< hGetLine from)
void $ concurrently
(mkreader StdoutLine outh)
(mkreader StderrLine errh)
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "waiting for process"]
2014-05-09 15:02:05 +00:00
void $ tryIO $ waitForProcess pid
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "sending Done"]
2014-05-09 15:02:05 +00:00
writeChan chan Done
2014-05-09 15:02:05 +00:00
hClose outh
hClose errh
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "wait writer"]
2014-05-09 15:02:05 +00:00
wait writer
2014-05-11 13:34:10 +00:00
debug ["simplesh run", cmd, show params, "wait writer complete"]
hClose h
2014-05-11 13:22:59 +00:00
debug ["simplesh run", cmd, show params, "fully done"]
simpleShClient :: FilePath -> String -> [String] -> ([Resp] -> IO a) -> IO a
simpleShClient namedpipe cmd params handler = do
2014-04-30 00:05:39 +00:00
debug ["simplesh connecting"]
s <- socket AF_UNIX Stream defaultProtocol
connect s (SockAddrUnix namedpipe)
h <- socketToHandle s ReadWriteMode
hPutStrLn h $ show $ Cmd cmd params
2014-04-20 15:38:50 +00:00
hFlush h
2014-04-30 00:05:39 +00:00
debug ["simplesh sent command"]
resps <- catMaybes . map readish . lines <$> hGetContents h
2014-04-30 00:05:39 +00:00
v <- hClose h `after` handler resps
debug ["simplesh processed response"]
return v
simpleShClientRetry :: Int -> FilePath -> String -> [String] -> ([Resp] -> IO a) -> IO a
simpleShClientRetry retries namedpipe cmd params handler = go retries
where
run = simpleShClient namedpipe cmd params handler
go n
| n < 1 = run
| otherwise = do
v <- tryIO run
case v of
Right r -> return r
2014-04-30 00:05:39 +00:00
Left e -> do
debug ["simplesh connection retry", show e]
threadDelaySeconds (Seconds 1)
go (n - 1)
getStdout :: Resp -> Maybe String
getStdout (StdoutLine s) = Just s
getStdout _ = Nothing