batchd-0.1.1.0: src/Batchd/Client/CmdLine.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
-- | This module contains definitions for client command-line parsing
-- by using optparse-applicative package.
module Batchd.Client.CmdLine where
import Data.Generics hiding (Generic)
import qualified Data.Map as M
import Data.Int
import Data.Time.LocalTime
import Data.Dates
import Data.Char (toLower)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Options.Applicative
import System.Log.Heavy
import qualified Text.Parsec as Parsec
import System.IO.Unsafe (unsafePerformIO)
import Batchd.Core.Common.Types
import Batchd.Common.Types
import Batchd.Common.Data (MoveAction (..))
import Batchd.Client.Types
data CmdLine = CmdLine {
cmdCommon :: CommonOpts,
cmdCommand :: Command
} deriving (Eq, Show)
data CommonOpts = CommonOpts {
managerUrl :: Maybe String,
username :: Maybe String,
password :: Maybe String,
logLevel :: Level
}
deriving (Eq, Show)
data Command =
Enqueue {
queueName :: Maybe String,
typeName :: Maybe String,
hostName :: Maybe String,
startTime :: Maybe (Maybe LocalTime),
jobNotes :: Maybe String,
jobCommand :: [T.Text],
parameters :: [String]
}
| List {
status :: Maybe String,
queueToList :: [String]
}
| Queue {
queueMode :: CrudMode,
queueObject :: String,
scheduleName :: Maybe String,
hostName :: Maybe String,
title :: Maybe String,
enabled :: Maybe Bool,
autostartCount :: Maybe (Maybe Int),
force :: Bool
}
| Job {
jobId :: Int,
queueName :: Maybe String,
prioritize :: Maybe MoveAction,
startTime :: Maybe (Maybe LocalTime),
status :: Maybe String,
hostName :: Maybe String,
jobNotes :: Maybe String,
viewDescription :: Bool,
viewResult :: Bool,
viewAll :: Bool,
jobMode :: CrudMode
}
| Schedule {
scheduleMode :: CrudMode,
scheduleNames :: [String],
periods :: [String],
weekdays :: [WeekDay],
force :: Bool
}
| Monitor {
metricPrefix :: Maybe String,
metricView :: MetricView,
metricTime :: MetricTime
}
| User {
userMode :: CrudMode,
objectUserName :: [String]
}
| Grant {
grantMode :: CrudMode,
grantUserName :: String,
grantPermissionId :: Maybe Int64,
permission :: Maybe Permission,
queueName :: Maybe String,
typeName :: Maybe String,
hostName :: Maybe String
}
| Type {
types :: [String]
}
| Stats {
queueToStat :: [String]
}
| Hosts
| RunShell
deriving (Eq, Show, Data, Typeable)
data MetricTime = CurrentSample | LastSample
deriving (Eq, Show, Data, Typeable)
data MetricView = Tree | Plain
deriving (Eq, Show, Data, Typeable)
defaultUrl :: String
defaultUrl = "http://localhost:" ++ show defaultManagerPort
defaultQueue :: String
defaultQueue = "default"
defaultType :: String
defaultType = "command"
-- managerUrlAnn = Nothing &= name "url" &= typ defaultUrl &= help "batchd manager API URL"
-- usernameAnn = Nothing &= name "user" &= typ "USER" &= help "batchd user name"
-- passwordAnn = Nothing &= name "password" &= typ "PASSWORD" &= help "batchd user password"
parser :: Parser CmdLine
parser = CmdLine <$> commonOpts <*> commands
commonOpts :: Parser CommonOpts
commonOpts = CommonOpts
<$> optional (strOption
( long "manager-url"
<> short 'M'
<> metavar "URL"
<> help "batchd manager API URL"))
<*> optional (strOption
( long "username"
<> short 'U'
<> metavar "NAME"
<> help "batchd user name"))
<*> optional (strOption
( long "password"
<> short 'P'
<> metavar "PASSWORD"
<> help "batchd user password"))
<*> verbosity
verbosity :: Parser Level
verbosity =
flag' debug_level (short 'd' <> long "debug" <> help "enable client debug output")
<|> flag' verbose_level (short 'v' <> long "verbose" <> help "be verbose")
<|> flag info_level disable_logging (short 'q' <> long "quiet" <> help "be quiet")
pMetricView :: Parser MetricView
pMetricView =
flag Plain Tree (short 't' <> long "tree" <> help "output data in tree view")
pMetricTime :: Parser MetricTime
pMetricTime =
flag CurrentSample LastSample (short 'l' <> long "last" <> help "retrieve last metrics sample from database")
required :: Read a => String -> Char -> String -> String -> Parser a
required longName shortName meta helpText =
option auto (long longName <> short shortName <> metavar meta <> help helpText)
optionalF :: Read a => String -> Char -> String -> String -> Parser (Maybe a)
optionalF longName shortName meta helpText =
optional $ required longName shortName meta helpText
requiredString :: String -> Char -> String -> String -> Parser String
requiredString longName shortName meta helpText =
strOption (long longName <> short shortName <> metavar meta <> help helpText)
optionalString :: String -> Char -> String -> String -> Parser (Maybe String)
optionalString longName shortName meta helpText =
optional $ requiredString longName shortName meta helpText
enqueue :: Parser Command
enqueue = Enqueue
<$> optionalString "queue" 'q' "QUEUE" "queue name"
<*> optionalString "type" 't' "TYPE" "job type name"
<*> optionalString "host" 'h' "HOST" "worker host name"
<*> (optional $ option timeReader (long "at" <> long "start" <> metavar "YYYY-MM-DD HH:MM:SS" <> help "set job start time"))
<*> optionalString "notes" 'n' "TEXT" "job description"
<*> many (strArgument (metavar "COMMAND"))
<*> many (requiredString "parameter" 'p' "NAME=VALUE" "job parameters specified by name")
list :: Parser Command
list = List
<$> (optionalString "status" 's' "STATUS" "list only jobs of specified status"
<|> flag Nothing (Just "all") (long "all" <> short 'a' <> help "synonym for --status=all"))
<*> many (strArgument (metavar "QUEUE" <> help "queue to list"))
crudMode :: [(CrudMode, String)] -> Parser CrudMode
crudMode [] = error "crudMode called with empty list"
crudMode modes@((dflt,_):_) = foldr1 (<|>) $ map go modes
where
go (mode, helpText) =
let (longName, shortName) = case mode of
View -> ("view", 'v')
Add -> ("add", 'a')
Update -> ("update", 'u')
Delete -> ("delete", 'd')
in flag dflt mode
( long longName
<> short shortName
<> help helpText )
priorityReader :: ReadM MoveAction
priorityReader = maybeReader (check . map toLower)
where
check s
| s `elem` ["first", "f"] = Just First
| s `elem` ["more", "up", "u"] = Just More
| s `elem` ["less", "down", "d"] = Just Less
| s `elem` ["last", "l"] = Just Last
| otherwise = Nothing
startupTime :: DateTime
startupTime = unsafePerformIO $ getCurrentDateTime
{-# NOINLINE startupTime #-}
timeReader :: ReadM (Maybe LocalTime)
timeReader = (maybeReader nothing) <|> (Just <$> toUtc <$> eitherReader readDateTime)
where
toUtc :: DateTime -> LocalTime
toUtc dt = LocalTime day time
where
day = dateTimeToDay dt
time = TimeOfDay (hour dt) (minute dt) (fromIntegral $ second dt)
nothing :: String -> Maybe (Maybe LocalTime)
nothing "any" = Just Nothing
nothing "now" = Just Nothing
nothing "anytime" = Just Nothing
nothing _ = Nothing
readDateTime :: String -> Either String DateTime
readDateTime str = case Parsec.runParser (do {t <- pDateTime startupTime; Parsec.eof; return t}) () "<start time>" str of
Left err -> Left (show err)
Right time -> Right time
queue :: Parser Command
queue = Queue
<$> crudMode [(Update, "modify queue"), (Add, "create new queue"), (Delete, "delete queue")]
<*> strArgument (metavar "QUEUE" <> help "queue to operate on")
<*> optionalString "schedule" 's' "SCHEDULE" "queue schedule name"
<*> optionalString "host" 'h' "HOST" "default host name for queue"
<*> optionalString "name" 'n' "TITLE" "set queue title"
<*> ( flag Nothing (Just True) (long "enable" <> short 'e' <> help "enable queue")
<|> flag Nothing (Just False) (long "disable" <> short 'D' <> help "disable queue")
)
<*> (optional $ option autostartReader (long "autostart" <> metavar "N"
<> help "automatically enable queue if it has N new jobs. Use `--autostart no' to disable."))
<*> switch (long "force" <> short 'f' <> help "force non-empty queue deletion")
autostartReader :: ReadM (Maybe Int)
autostartReader = (Just <$> auto) <|> maybeReader disable
where
disable :: String -> Maybe (Maybe Int)
disable "no" = Just Nothing
disable "disable" = Just Nothing
disable "false" = Just Nothing
disable _ = Nothing
job :: Parser Command
job = Job
<$> argument auto (metavar "ID" <> help "job ID")
<*> optionalString "move" 'm' "QUEUE" "move job to other queue"
<*> (optional $ option priorityReader (long "priority" <> short 'p' <> metavar "ACTION" <> help "change job priority. ACTION is one of: up, down, first, last"))
<*> (optional $ option timeReader (long "at" <> long "start" <> metavar "YYYY-MM-DD HH:MM:SS" <> help "set job start time. Use `any' to specify that job can be run at any time."))
<*> optionalString "status" 's' "STATUS" "set job status"
<*> optionalString "host" 'h' "HOST" "set job host"
<*> optionalString "notes" 'n' "TEXT" "job description"
<*> switch (long "description" <> help "view job description")
<*> switch (long "result" <> short 'r' <> help "view job result")
<*> switch (long "all" <> short 'a' <> help "view all job results")
<*> crudMode [(View, "view job description or result"), (Update, "modify job"), (Delete, "delete job")]
schedule :: Parser Command
schedule = Schedule
<$> crudMode [(View, "list available schedules"), (Add, "create new schedule"), (Update, "modify schedule"), (Delete, "delete (unused) schedule")]
<*> many (strArgument (metavar "SCHEDULE" <> help "schedules to operate on"))
<*> many (requiredString "period" 'p' "HH:MM:SS HH:MM:SS" "time of day period(s)")
<*> many (required "weekday" 'w' "WEEKDAY" "week day(s)")
<*> switch (long "force" <> short 'f' <> help "delete also all queues which use this schedule and their jobs")
user :: Parser Command
user = User
<$> crudMode [(View, "list existing users"), (Add, "create new user"), (Update, "change user password")]
<*> many (strArgument (metavar "NAME" <> help "name of user to operate on"))
grant :: Parser Command
grant = Grant
<$> crudMode [(View, "view permissions of user"), (Add, "add permissions to user"), (Delete, "revoke permission from user")]
<*> strArgument (metavar "NAME" <> help "name of user to operate on")
<*> optionalF "id" 'i' "ID" "permission ID to operate on. mandatory for revoke operation."
<*> optionalF "permission" 'p' "PERMISSION" "specify permission, for example ViewJobs"
<*> optionalString "queue" 'q' "QUEUE" "queue to grant permission to, by default - any"
<*> optionalString "type" 't' "TYPE" "job type to grant permission to, usable for CreateJobs permission"
<*> optionalString "host" 'h' "HOST" "name of host to grant permission to, usable for CreateJobs permission. Use `__default__' value to allow default host of queue."
typesList :: Parser Command
typesList = Type
<$> many (strArgument (metavar "TYPE"))
stats :: Parser Command
stats = Stats
<$> many (strArgument (metavar "QUEUE"))
monitor :: Parser Command
monitor = Monitor
<$> (optional $ strArgument (metavar "PREFIX" <> help "prefix of names of metrics to retrieve, for example batchd.wai"))
<*> pMetricView
<*> pMetricTime
shell :: Parser Command
shell = pure RunShell
hostsList :: Parser Command
hostsList = pure Hosts
parseParams :: [ParamDesc] -> Command -> JobParamInfo
parseParams desc e =
let posNames = map piName desc
ordered = M.fromList $ zip posNames (jobCommand e)
byName = M.fromList $ map parseOne (parameters e)
in M.union byName ordered
where
parseOne :: String -> (TL.Text, T.Text)
parseOne s = case break (== '=') s of
(key, (_:value)) -> (TL.pack key, T.pack value)
(key, []) -> (TL.pack key, T.empty)
commands :: Parser Command
commands = hsubparser
( cmd "enqueue" enqueue "put a new job into queue"
<> cmd "ls" list "list queues or jobs"
<> cmd "job" job "update or delete jobs"
<> cmd "queue" queue "create, update or delete queues"
<> cmd "schedule" schedule "create, update or delete schedules"
<> cmd "monitor" monitor "retrieve monitoring metrics"
<> cmd "type" typesList "view job types"
<> cmd "host" hostsList "view defined hosts"
<> cmd "stats" stats "print statistics on queue or on all jobs"
<> cmd "user" user "create, update or delete users"
<> cmd "grant" grant "create, update or delete user permissions"
<> cmd "shell" shell "run interactive shell" )
where
cmd name func helpText = command name (info func (progDesc helpText))
parserInfo :: ParserInfo CmdLine
parserInfo = info (parser <**> helper)
(fullDesc
<> header "batch - the batchd toolset command-line client program"
<> progDesc "operate on batch jobs, queues, schedules etc.")
getCmdArgs :: IO CmdLine
getCmdArgs = execParser parserInfo