module Ecluse.CheckConfig (runCheckConfig) where
import Data.Text.IO qualified as TIO
import System.Environment (getEnvironment)
import System.Exit (ExitCode (ExitFailure))
import Ecluse.Boot (applySecretFileIndirection, readConfigDocument)
import Ecluse.Composition (validateComposition)
import Ecluse.Composition.BootError (BootError (MemoryPlanOverrideUnsafe), renderBootError)
import Ecluse.Composition.MemoryPlan (
MemoryPlan (mpDegradations, mpOverrideViolations, mpQueueMemoryMaxDepth),
)
import Ecluse.Composition.MirrorQueue (
MirrorQueuePlan (MemoryBackend, SqsBackend),
MirrorRuntimePlan (MirrorWith, NoMirroring),
memoryQueueBootWarning,
planMirrorRuntime,
)
import Ecluse.Composition.Plan (resolveMemoryPlanFor)
import Ecluse.Composition.Sizing (
openFileSoftLimit,
resolvePrivateConnections,
resolvePublicConnections,
)
import Ecluse.Config (
AppConfig (cfgRuntime),
Config (configApp),
RuntimeSettings (rtCores, rtMaxHeapBytes, rtPrivateConnectionsPerHost, rtPublicConnectionsPerHost),
loadConfig,
mountCollisionWarnings,
mountPostureLines,
renderConfigError,
resolvedKeyProvenance,
)
import Ecluse.Config.Ambient (ambientAwsFromEnv)
import Ecluse.Rts (
appliedRuntimePlan,
currentRtsPosture,
readCgroupLimits,
renderEffectivePosture,
resolveRuntimePlan,
)
import Ecluse.Runtime.Queue.Sqs (SqsConfig (sqsQueueUrl, sqsRegion))
runCheckConfig :: IO ()
runCheckConfig :: IO ()
runCheckConfig = do
rawEnvVars <- IO [(FilePath, FilePath)]
getEnvironment
envVars <- applySecretFileIndirection rawEnvVars >>= either refuseWith pure
docE <- readConfigDocument envVars
(docBlob, docPath) <- either refuseWith pure docE
TIO.putStrLn $ case docBlob of
Just ByteString
_ -> Text
"config document: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
forall a. ToText a => a -> Text
toText FilePath
docPath
Maybe ByteString
Nothing -> Text
"config document: none at " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
forall a. ToText a => a -> Text
toText FilePath
docPath Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (defaults and environment only)"
config <- either (refuseWith . renderErrs renderConfigError) pure (loadConfig envVars docBlob)
case validateComposition config of
[] -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
[BootError]
errs -> Text -> IO ()
forall a. Text -> IO a
refuseWith ((BootError -> Text) -> [BootError] -> Text
forall e. (e -> Text) -> [e] -> Text
renderErrs BootError -> Text
renderBootError [BootError]
errs)
let env = Config -> AppConfig
configApp Config
config
runtimeSettings = AppConfig -> RuntimeSettings
cfgRuntime AppConfig
env
rts <- currentRtsPosture
cgroup <- readCgroupLimits
fdLimit <- openFileSoftLimit
runtimePlan <-
either
(refuseWith . renderErrs renderBootError)
pure
(planMirrorRuntime (ambientAwsFromEnv envVars) config)
let plan = Maybe Int -> Maybe Int -> CgroupLimits -> RtsPosture -> RuntimePlan
resolveRuntimePlan (RuntimeSettings -> Maybe Int
rtCores RuntimeSettings
runtimeSettings) (RuntimeSettings -> Maybe Int
rtMaxHeapBytes RuntimeSettings
runtimeSettings) CgroupLimits
cgroup RtsPosture
rts
effective = CgroupLimits -> RuntimePlan -> RtsPosture -> EffectiveRuntimePlan
appliedRuntimePlan CgroupLimits
cgroup RuntimePlan
plan RtsPosture
rts
(_, privateLine) = resolvePrivateConnections (rtPrivateConnectionsPerHost runtimeSettings) fdLimit
(_, publicLine) = resolvePublicConnections (rtPublicConnectionsPerHost runtimeSettings) fdLimit
(memoryPlan, memoryPlanLines) = resolveMemoryPlanFor env effective runtimePlan
traverse_ TIO.putStrLn $
concat
[ resolvedKeyProvenance envVars docBlob
, renderEffectivePosture effective
, [privateLine, publicLine]
, memoryPlanLines
,
map ("warning: " <>) (mpDegradations memoryPlan)
, mirrorRuntimeLines (mpQueueMemoryMaxDepth memoryPlan) runtimePlan
, mountPostureLines config
, map ("warning: " <>) (mountCollisionWarnings config)
]
unless (null (mpOverrideViolations memoryPlan)) $
refuseWith (renderErrs renderBootError [MemoryPlanOverrideUnsafe (mpOverrideViolations memoryPlan)])
TIO.putStrLn "configuration: valid"
exitSuccess
where
renderErrs :: (e -> Text) -> [e] -> Text
renderErrs :: forall e. (e -> Text) -> [e] -> Text
renderErrs e -> Text
render = [Text] -> Text
forall t. IsText t "unlines" => [t] -> t
unlines ([Text] -> Text) -> ([e] -> [Text]) -> [e] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (e -> Text) -> [e] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map e -> Text
render
refuseWith :: Text -> IO a
refuseWith :: forall a. Text -> IO a
refuseWith Text
message = do
Handle -> Text -> IO ()
TIO.hPutStrLn Handle
stderr Text
message
Handle -> Text -> IO ()
TIO.hPutStrLn Handle
stderr Text
"configuration: refused"
ExitCode -> IO a
forall (m :: * -> *) a. MonadIO m => ExitCode -> m a
exitWith (Int -> ExitCode
ExitFailure Int
2)
mirrorRuntimeLines :: Int -> MirrorRuntimePlan -> [Text]
mirrorRuntimeLines :: Int -> MirrorRuntimePlan -> [Text]
mirrorRuntimeLines Int
memoryDepth = \case
MirrorRuntimePlan
NoMirroring -> [Text
"mirror runtime: disabled (no mount mirrors; no queue is built and no worker starts)"]
MirrorWith (SqsBackend SqsConfig
sqs) ->
[Text
"mirror queue: sqs, " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> SqsConfig -> Text
sqsQueueUrl SqsConfig
sqs Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (region " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> SqsConfig -> Text
sqsRegion SqsConfig
sqs Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"]
MirrorWith MirrorQueuePlan
MemoryBackend ->
[ Text
"mirror queue: in-memory (depth " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
memoryDepth Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
, Text
"warning: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
memoryQueueBootWarning
]