packages feed

salmon-ops-0.1.0.0: src/Salmon/Builtin/Nodes/CronTask.hs

{-# LANGUAGE DeriveGeneric #-}

module Salmon.Builtin.Nodes.CronTask where

import Data.Aeson (FromJSON, ToJSON)
import qualified Data.ByteString as ByteString
import GHC.Generics (Generic)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Salmon.Builtin.Extension
import Salmon.Op.Ref
import Salmon.Op.Track
import System.Directory (removeFile)
import System.FilePath ((</>))

type Minute = Text
type Hour = Text
type DOM = Text
type Month = Text
type DOW = Text

data Schedule
    = Schedule
    { minute :: Minute
    , hour :: Hour
    , dayOfMonth :: DOM
    , month :: Month
    , dayOfWeek :: DOW
    }
    deriving (Eq, Show, Generic)

instance FromJSON Schedule
instance ToJSON Schedule

everyMinute :: Schedule
everyMinute = Schedule "*" "*" "*" "*" "*"

-- | Every hour, on the given minute.
hourlyAt :: Minute -> Schedule
hourlyAt m = Schedule m "*" "*" "*" "*"

{- | Once a day, at the given hour and minute.

Both are taken rather than defaulted because a fleet of boxes all backing up
at @0 0@ is a self-inflicted thundering herd against whatever the dumps are
copied to.
-}
dailyAt :: Hour -> Minute -> Schedule
dailyAt h m = Schedule m h "*" "*" "*"

-- | Once a week, on a given day (@0@ or @7@ is Sunday).
weeklyAt :: DOW -> Hour -> Minute -> Schedule
weeklyAt d h m = Schedule m h "*" "*" d

data CronTask
    = CronTask
    { name :: Text
    , user :: Text
    , schedule :: Schedule
    , command :: FilePath
    , commandArgs :: [Text]
    }

platformCronPath :: FilePath
platformCronPath = "/etc/cron.d"

crontask :: Track' CronTask -> CronTask -> Op
crontask t task =
    op "crontask" (deps [run t task]) $ \actions ->
        actions
            { help = "setup " <> cmd <> " at " <> Text.pack path
            , ref = mkRef "crontask" path
            , up = up
            , down = removeFile path
            }
  where
    path :: FilePath
    path = platformCronPath </> Text.unpack (mconcat ["salmon-", task.name])

    cmd :: Text
    cmd = Text.pack task.command

    up :: IO ()
    up = ByteString.writeFile path $ Text.encodeUtf8 contents

    contents :: Text
    contents =
        Text.unlines
            [ "# salmon-task: " <> task.name
            , renderTask task
            ]

    renderTask :: CronTask -> Text
    renderTask task =
        Text.unwords
            [ renderSchedule task.schedule
            , task.user
            , cmd
            , Text.unwords task.commandArgs
            ]

    renderSchedule :: Schedule -> Text
    renderSchedule sched =
        Text.unwords
            [ sched.minute
            , sched.hour
            , sched.dayOfMonth
            , sched.month
            , sched.dayOfWeek
            ]