wolf-0.2.0: src/main/Act.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Act
( main
) where
import Control.Exception
import Control.Monad
import Control.Monad.Trans.Resource
import Control.Monad.IO.Class
import Data.Aeson.Encode
import Data.ByteString ( length )
import Data.ByteString.Lazy ( fromStrict )
import Data.Text ( Text, pack, append, words, strip )
import Data.Text.Lazy ( toStrict )
import Data.Text.Lazy.Builder
import Data.Yaml
import Network.AWS.Data.Crypto
import Network.AWS.Flow
import Options.Applicative hiding ( action )
import Shelly hiding ( FilePath )
import Prelude hiding ( length, readFile, words, writeFile )
data Args = Args
{ aConfig :: FilePath
, aQueue :: Queue
, aContainer :: FilePath
} deriving ( Eq, Read, Show )
argsPI :: ParserInfo Args
argsPI =
info ( helper <*> argsP )
( fullDesc
<> header "act: Workflow activity"
<> progDesc "Workflow activity" ) where
argsP = args
<$> strOption
( long "config"
<> short 'c'
<> metavar "FILE"
<> help "AWS SWF Service Flow config" )
<*> strOption
( long "queue"
<> short 'q'
<> metavar "NAME"
<> help "AWS SWF Service Flow queue" )
<*> strOption
( long "container"
<> short 'x'
<> metavar "FILE"
<> help "AWS SWF Service Flow worker container" ) where
args config queue container = Args
{ aConfig = config
, aQueue = pack queue
, aContainer = container
}
data Container = Container
{ cImage :: Text
, cCommand :: Text
, cVolumes :: [Text]
, cDevices :: [Text]
, cEnvironment :: [Text]
} deriving ( Eq, Read, Show )
instance FromJSON Container where
parseJSON (Object v) =
Container <$>
v .: "image" <*>
v .: "command" <*>
v .:? "volumes" .!= [] <*>
v .:? "devices" .!= [] <*>
v .:? "environment" .!= []
parseJSON _ = mzero
data Control = Control
{ cUid :: Uid
} deriving ( Eq, Read, Show )
instance ToJSON Control where
toJSON Control{..} = object
[ "run_uid" .= cUid
]
encodeText :: ToJSON a => a -> Text
encodeText =
toStrict . toLazyText . encodeToTextBuilder . toJSON
exec :: MonadIO m => Container -> Uid -> Metadata -> m (Metadata, [Artifact])
exec container uid metadata =
shelly $ withDir $ \dataDir storeDir -> do
control dataDir $ encodeText $ Control uid
input dataDir metadata
docker dataDir storeDir container
result <- output dataDir
artifacts <- store storeDir
return (result, artifacts) where
withDir action =
withTmpDir $ \dir -> do
mkdir $ dir </> pack "data"
mkdir $ dir </> pack "store"
action (dir </> pack "data") (dir </> pack "store")
control dir =
writefile (dir </> pack "control.json")
input dir =
maybe_ (writefile $ dir </> pack "input.json") where
maybe_ =
maybe (return ())
output dir =
catch_sh_maybe (readfile $ dir </> pack "output.json") where
catch_sh_maybe action =
catch_sh (liftM Just action) $ \(_ :: SomeException) -> return Nothing
store dir = do
artifacts <- findWhen test_f dir
forM artifacts $ \artifact -> do
key <- relativeTo dir artifact
blob <- readBinary artifact
return ( toTextIgnore $ uid </> key
, hash blob
, fromIntegral $ length blob
, fromStrict blob
)
docker dataDir storeDir Container{..} = do
devices <- forM cDevices $ \device ->
liftM strip $ run "readlink" ["-f", device]
run_ "docker" $ concat
[["run"]
, concatMap (("--device" :) . return) devices
, concatMap (("--env" :) . return) cEnvironment
, concatMap (("--volume" :) . return) $
append (toTextIgnore dataDir) ":/app/data" :
append (toTextIgnore storeDir) ":/app/store" : cVolumes
, [cImage]
, words cCommand
]
main :: IO ()
main =
execParser argsPI >>= call where
call Args{..} = do
config <- decodeFile aConfig >>= maybeThrow (userError "Bad Config")
container <- decodeFile aContainer >>= maybeThrow (userError "Bad Container")
env <- flowEnv config
forever $
runResourceT $ runFlowT env $
act aQueue $ exec container