wolf-0.2.1: main/Act.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Act
( main
) where
import BasicPrelude hiding ( ByteString, (</>), hash, length, readFile, find )
import Control.Monad.Trans.Resource
import Data.Aeson.Encode
import Data.ByteString ( length )
import qualified Data.ByteString.Lazy as BL
import Data.Text ( pack, strip )
import Data.Text.Lazy ( toStrict )
import Data.Text.Lazy.Builder hiding ( fromText )
import Data.Yaml hiding ( Parser )
import Network.AWS.Data.Crypto
import Network.AWS.Flow
import Options
import Options.Applicative hiding ( action )
import Shelly hiding ( FilePath, bash )
data Args = Args
{ aConfig :: FilePath
, aQueue :: Queue
, aContainer :: FilePath
, aContainerless :: Maybe String
} deriving ( Eq, Read, Show )
args :: Parser Args
args = Args <$> configFile <*> (pack <$> queue) <*> containerFile <*> containerless
parser :: ParserInfo Args
parser =
info ( helper <*> args ) $ fullDesc
<> header "act: Workflow activity"
<> progDesc "Workflow activity"
data Container = Container
{ cImage :: Text
, cCommand :: Text
, cVolumes :: [Text]
, cDevices :: [Text]
, cEnvironment :: [Text]
, cLink :: [Text]
} deriving ( Eq, Read, Show )
instance FromJSON Container where
parseJSON (Object v) =
Container <$>
v .: "image" <*>
v .: "command" <*>
v .:? "volumes" .!= [] <*>
v .:? "devices" .!= [] <*>
v .:? "environment" .!= [] <*>
v .:? "links" .!= []
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 -> Maybe String -> Uid -> Metadata -> [Blob] -> m (Metadata, [Artifact])
exec container dockerless uid metadata blobs =
shelly $ withDir $ \dir dataDir storeDir -> do
control $ dataDir </> pack "control.json"
storeInput $ storeDir </> pack "input"
dataInput $ dataDir </> pack "input.json"
maybe (docker dataDir storeDir container) (bash dir container) dockerless
-- if dockerless then
-- bash dir container
-- else
-- docker dataDir storeDir container
result <- dataOutput $ dataDir </> pack "output.json"
artifacts <- storeOutput $ storeDir </> pack "output"
return (result, artifacts) where
withDir action =
withTmpDir $ \dir -> do
mkdir $ dir </> pack "data"
mkdir $ dir </> pack "store"
mkdir $ dir </> pack "store/input"
mkdir $ dir </> pack "store/output"
action dir (dir </> pack "data") (dir </> pack "store")
control file =
writefile file $ encodeText $ Control uid
dataInput file =
maybe (return ()) (writefile file) metadata
dataOutput file =
catch_sh_maybe (readfile file) where
catch_sh_maybe action =
catch_sh (liftM Just action) $ \(_ :: SomeException) -> return Nothing
storeInput dir =
forM_ blobs $ \(key, blob) -> do
paths <- liftM strip $ run "dirname" [key]
mkdir_p $ dir </> paths
writeBinary (dir </> key) (BL.toStrict blob)
storeOutput dir = do
artifacts <- findWhen test_f dir
forM artifacts $ \artifact -> do
key <- relativeTo dir artifact
blob <- readBinary artifact
return ( toTextIgnore key
, hash blob
, fromIntegral $ length blob
, BL.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 (("--link" :) . return) cLink
, concatMap (("--volume" :) . return) $
toTextIgnore dataDir <> ":/app/data" :
toTextIgnore storeDir <> ":/app/store" : cVolumes
, [cImage]
, words cCommand
]
bash dir Container{..} bashDir = do
files <- ls $ fromText $ pack bashDir
forM_ files $ flip cp_r dir
cd dir
maybe (return ()) (uncurry $ run_ . fromText) $ uncons $ words cCommand
call :: Args -> IO ()
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 aContainerless
main :: IO ()
main = execParser parser >>= call