module Ecluse.Boot (
BootEnv (..),
withBootEnv,
BootAborted (..),
orExit,
logBootWarning,
logBootInfo,
logRuleBootOrder,
buildMirrorQueue,
) where
import Data.ByteString qualified as BS
import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Katip (Environment (Environment), LogEnv, Severity (InfoS, WarningS), logFM, ls)
import Katip.Monadic (runKatipContextT)
import System.Environment (getEnvironment)
import System.IO.Error (isDoesNotExistError)
import UnliftIO (throwIO, tryJust)
import Ecluse.Composition (
MirrorQueuePlan (MemoryBackend, SqsBackend),
memoryQueueDropWarning,
mirrorQueuePlanWarning,
)
import Ecluse.Config (
AppConfig (cfgCores, cfgLogFormat, cfgMaxHeapBytes, cfgTelemetry),
Config (configApp),
loadConfig,
renderConfigError,
)
import Ecluse.Core.Queue (MirrorQueue, newBoundedInMemoryQueue)
import Ecluse.Core.Queue.Sqs (newSqsQueue)
import Ecluse.Core.Rules (renderBootOrder)
import Ecluse.Core.Server.Context (PackumentDeps (pdRules))
import Ecluse.Log (moduleField, newLogEnv)
import Ecluse.Runtime (applyRuntimePosture)
import Ecluse.Server (MountBinding (bindingPackumentDeps, bindingPrefix))
import Ecluse.Telemetry (Telemetry, TelemetrySwitch (TelemetryOff, TelemetryOn), withTelemetry)
import Ecluse.Telemetry.Resolve (prepareTelemetry)
data BootEnv = BootEnv
{ BootEnv -> AppConfig
beConfig :: AppConfig
, BootEnv -> LogEnv
beLogEnv :: LogEnv
, BootEnv -> Telemetry
beTelemetry :: Telemetry
, BootEnv -> Config
beConfigFull :: Config
}
withBootEnv :: (BootEnv -> IO ()) -> IO ()
withBootEnv :: (BootEnv -> IO ()) -> IO ()
withBootEnv BootEnv -> IO ()
action = do
envVars <- IO [(String, String)]
getEnvironment
mDocBlob <- tryJust (guard . isDoesNotExistError) (BS.readFile "/etc/ecluse/config.yaml")
let docBlob = (() -> Maybe ByteString)
-> (ByteString -> Maybe ByteString)
-> Either () ByteString
-> Maybe ByteString
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Maybe ByteString -> () -> Maybe ByteString
forall a b. a -> b -> a
const Maybe ByteString
forall a. Maybe a
Nothing) ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just Either () ByteString
mDocBlob
config <- orExit (T.unlines . map renderConfigError) (loadConfig envVars docBlob)
let env = Config -> AppConfig
configApp Config
config
logEnv <- newLogEnv (cfgLogFormat env) (Environment "production")
applyRuntimePosture (logBootInfo logEnv) (logBootWarning logEnv) (cfgCores env) (cfgMaxHeapBytes env)
logBootInfo logEnv ("Loaded configuration: " <> show config)
prepareTelemetryBoot (cfgTelemetry env) logEnv
withTelemetry (cfgTelemetry env) logEnv $ \Telemetry
telemetry ->
BootEnv -> IO ()
action
BootEnv
{ beConfig :: AppConfig
beConfig = AppConfig
env
, beLogEnv :: LogEnv
beLogEnv = LogEnv
logEnv
, beTelemetry :: Telemetry
beTelemetry = Telemetry
telemetry
, beConfigFull :: Config
beConfigFull = Config
config
}
buildMirrorQueue :: LogEnv -> MirrorQueuePlan -> IO MirrorQueue
buildMirrorQueue :: LogEnv -> MirrorQueuePlan -> IO MirrorQueue
buildMirrorQueue LogEnv
logEnv MirrorQueuePlan
plan = do
Maybe Text -> (Text -> IO ()) -> IO ()
forall (f :: * -> *) a.
Applicative f =>
Maybe a -> (a -> f ()) -> f ()
whenJust (MirrorQueuePlan -> Maybe Text
mirrorQueuePlanWarning MirrorQueuePlan
plan) (LogEnv -> Text -> IO ()
logBootWarning LogEnv
logEnv)
case MirrorQueuePlan
plan of
SqsBackend SqsConfig
sqsConfig -> SqsConfig -> IO MirrorQueue
newSqsQueue SqsConfig
sqsConfig
MemoryBackend MemoryQueueConfig
memoryConfig ->
MemoryQueueConfig -> (Int -> IO ()) -> IO MirrorQueue
newBoundedInMemoryQueue MemoryQueueConfig
memoryConfig (LogEnv -> Text -> IO ()
logBootWarning LogEnv
logEnv (Text -> IO ()) -> (Int -> Text) -> Int -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Text
memoryQueueDropWarning)
logBootWarning :: LogEnv -> Text -> IO ()
logBootWarning :: LogEnv -> Text -> IO ()
logBootWarning LogEnv
logEnv Text
message =
LogEnv
-> SimpleLogPayload -> Namespace -> KatipContextT IO () -> IO ()
forall c (m :: * -> *) a.
LogItem c =>
LogEnv -> c -> Namespace -> KatipContextT m a -> m a
runKatipContextT LogEnv
logEnv (Text -> SimpleLogPayload
moduleField Text
"Ecluse") Namespace
forall a. Monoid a => a
mempty (Severity -> LogStr -> KatipContextT IO ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
WarningS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls Text
message))
logBootInfo :: LogEnv -> Text -> IO ()
logBootInfo :: LogEnv -> Text -> IO ()
logBootInfo LogEnv
logEnv Text
message =
LogEnv
-> SimpleLogPayload -> Namespace -> KatipContextT IO () -> IO ()
forall c (m :: * -> *) a.
LogItem c =>
LogEnv -> c -> Namespace -> KatipContextT m a -> m a
runKatipContextT LogEnv
logEnv (Text -> SimpleLogPayload
moduleField Text
"Ecluse") Namespace
forall a. Monoid a => a
mempty (Severity -> LogStr -> KatipContextT IO ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls Text
message))
logRuleBootOrder :: LogEnv -> [MountBinding] -> IO ()
logRuleBootOrder :: LogEnv -> [MountBinding] -> IO ()
logRuleBootOrder LogEnv
logEnv = (MountBinding -> IO ()) -> [MountBinding] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ MountBinding -> IO ()
logMount
where
logMount :: MountBinding -> IO ()
logMount MountBinding
binding = Maybe PackumentDeps -> (PackumentDeps -> IO ()) -> IO ()
forall (f :: * -> *) a.
Applicative f =>
Maybe a -> (a -> f ()) -> f ()
whenJust (MountBinding -> Maybe PackumentDeps
bindingPackumentDeps MountBinding
binding) ((PackumentDeps -> IO ()) -> IO ())
-> (PackumentDeps -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \PackumentDeps
deps -> do
let label :: Text
label = Text -> [Text] -> Text
T.intercalate Text
"/" (NonEmpty Text -> [Text]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (MountBinding -> NonEmpty Text
bindingPrefix MountBinding
binding))
LogEnv -> Text -> IO ()
logBootInfo LogEnv
logEnv (Text
"rule boot order for mount " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
label Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":")
(Text -> IO ()) -> [Text] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ (LogEnv -> Text -> IO ()
logBootInfo LogEnv
logEnv) ([PreparedRule] -> [Text]
renderBootOrder (PackumentDeps -> [PreparedRule]
pdRules PackumentDeps
deps))
data BootAborted = BootAborted
deriving stock (BootAborted -> BootAborted -> Bool
(BootAborted -> BootAborted -> Bool)
-> (BootAborted -> BootAborted -> Bool) -> Eq BootAborted
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BootAborted -> BootAborted -> Bool
== :: BootAborted -> BootAborted -> Bool
$c/= :: BootAborted -> BootAborted -> Bool
/= :: BootAborted -> BootAborted -> Bool
Eq, Int -> BootAborted -> ShowS
[BootAborted] -> ShowS
BootAborted -> String
(Int -> BootAborted -> ShowS)
-> (BootAborted -> String)
-> ([BootAborted] -> ShowS)
-> Show BootAborted
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BootAborted -> ShowS
showsPrec :: Int -> BootAborted -> ShowS
$cshow :: BootAborted -> String
show :: BootAborted -> String
$cshowList :: [BootAborted] -> ShowS
showList :: [BootAborted] -> ShowS
Show)
instance Exception BootAborted
orExit :: (e -> Text) -> Either e a -> IO a
orExit :: forall e a. (e -> Text) -> Either e a -> IO a
orExit e -> Text
render = \case
Right a
a -> a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
a
Left e
err -> Handle -> Text -> IO ()
TIO.hPutStrLn Handle
stderr (e -> Text
render e
err) IO () -> IO a -> IO a
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> BootAborted -> IO a
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO BootAborted
BootAborted
prepareTelemetryBoot :: TelemetrySwitch -> LogEnv -> IO ()
prepareTelemetryBoot :: TelemetrySwitch -> LogEnv -> IO ()
prepareTelemetryBoot TelemetrySwitch
switch LogEnv
logEnv = case TelemetrySwitch
switch of
TelemetrySwitch
TelemetryOff -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
TelemetrySwitch
TelemetryOn -> do
environment <- IO [(String, String)]
getEnvironment
prepareTelemetry logEnv environment