module Ecluse.Pilot (
runPilot,
pilotApplication,
PilotCompileOptions (..),
runPilotCompile,
PilotUploadUnconfigured (..),
) where
import Conduit (MonadResource, runResourceT)
import Control.Monad.Catch (MonadMask)
import Katip (KatipContext, LogEnv, Severity (InfoS), logFM, ls)
import Katip.Monadic (runKatipContextT)
import Network.Wai (Application)
import UnliftIO (MonadUnliftIO)
import UnliftIO.Concurrent (threadDelay)
import UnliftIO.Exception (throwIO)
import Ecluse.Boot (BootEnv (..))
import Ecluse.Config (
AdvisoriesSettings (advBucket, advCompileInterval, advDataDir, advOsvExportBaseUrl),
AppConfig (cfgAdvisories, cfgServer),
Config (configApp),
ServerSettings (srvPort),
)
import Ecluse.Config.Ambient (AmbientAws (ambientAwsEndpointUrl), parseEndpointUrl)
import Ecluse.Core.Osv.Advisory (osvExportUrl)
import Ecluse.Core.Osv.Compile (compileOsvToSqlite)
import Ecluse.Core.Supervision (
BackoffSchedule (BackoffSchedule, bsBaseMicros, bsCapMicros),
FaultDisposition (Transient),
SupervisionPolicy (SupervisionPolicy, spBackoff, spClassify, spLabel),
superviseLoop,
)
import Ecluse.Runtime.Log (moduleField)
import Ecluse.Runtime.Pilot.Export (exportToS3)
import Ecluse.Runtime.Server (ServerConfig (scCheckReady, scDrain, scPort), mkServerConfig, probeApplication, raceServerAgainstLoop, runWarp, serverMiddleware)
import Ecluse.Runtime.Telemetry (Telemetry, telemetryTracerProvider)
pilotApplication :: ServerConfig -> IO Application
pilotApplication :: ServerConfig -> IO Application
pilotApplication ServerConfig
cfg = Application -> IO Application
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ServerConfig -> Middleware
serverMiddleware ServerConfig
cfg (DrainSignal -> IO Bool -> IO Bool -> Application
probeApplication (ServerConfig -> DrainSignal
scDrain ServerConfig
cfg) (ServerConfig -> IO Bool
scCheckReady ServerConfig
cfg) (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True)))
runPilot :: BootEnv -> IO ()
runPilot :: BootEnv -> IO ()
runPilot BootEnv
bootEnv = do
let logEnv :: LogEnv
logEnv = BootEnv -> LogEnv
beLogEnv BootEnv
bootEnv
port :: Int
port = ServerSettings -> Int
srvPort (AppConfig -> ServerSettings
cfgServer (BootEnv -> AppConfig
beConfig BootEnv
bootEnv))
cfg :: ServerConfig
cfg = ([MountBinding] -> ServerConfig
mkServerConfig []){scPort = port}
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.Pilot") Namespace
forall a. Monoid a => a
mempty (KatipContextT IO () -> IO ()) -> KatipContextT IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Severity -> LogStr -> KatipContextT IO ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (String -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (String
"Pilot mode starting up on port " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall b a. (Show a, IsString b) => a -> b
show Int
port :: String))
KatipContextT IO () -> KatipContextT IO () -> KatipContextT IO ()
forall (m :: * -> *). MonadUnliftIO m => m () -> m () -> m ()
raceServerAgainstLoop
(IO () -> KatipContextT IO ()
forall a. IO a -> KatipContextT IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> KatipContextT IO ()) -> IO () -> KatipContextT IO ()
forall a b. (a -> b) -> a -> b
$ ServerConfig -> (ServerConfig -> IO Application) -> IO ()
runWarp ServerConfig
cfg ServerConfig -> IO Application
pilotApplication)
(Telemetry -> AmbientAws -> Config -> KatipContextT IO ()
forall (m :: * -> *).
(MonadMask m, MonadUnliftIO m, KatipContext m) =>
Telemetry -> AmbientAws -> Config -> m ()
runExportLoop (BootEnv -> Telemetry
beTelemetry BootEnv
bootEnv) (BootEnv -> AmbientAws
beAmbient BootEnv
bootEnv) (BootEnv -> Config
beConfigFull BootEnv
bootEnv))
runExportLoop :: (MonadMask m, MonadUnliftIO m, KatipContext m) => Telemetry -> AmbientAws -> Config -> m ()
runExportLoop :: forall (m :: * -> *).
(MonadMask m, MonadUnliftIO m, KatipContext m) =>
Telemetry -> AmbientAws -> Config -> m ()
runExportLoop Telemetry
telemetry AmbientAws
ambient Config
config = do
let appCfg :: AppConfig
appCfg = Config -> AppConfig
configApp Config
config
intervalMicros :: Int
intervalMicros = (NominalDiffTime -> Int
forall b. Integral b => NominalDiffTime -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (AdvisoriesSettings -> NominalDiffTime
advCompileInterval (AppConfig -> AdvisoriesSettings
cfgAdvisories AppConfig
appCfg)) :: Int) Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000
case AdvisoriesSettings -> Maybe Text
advBucket (AppConfig -> AdvisoriesSettings
cfgAdvisories AppConfig
appCfg) of
Maybe Text
Nothing -> do
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS LogStr
"No S3 bucket configured for OSV database export; export loop disabled."
m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Int -> m ()
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay (Int
24 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
60 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
60 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000)
Just Text
bucketName -> do
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"S3 export loop starting up. Target bucket: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
bucketName))
m Void -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void
(m Void -> m ()) -> m Void -> m ()
forall a b. (a -> b) -> a -> b
$ SupervisionPolicy -> m () -> m Void
forall (m :: * -> *).
(MonadUnliftIO m, KatipContext m) =>
SupervisionPolicy -> m () -> m Void
superviseLoop
SupervisionPolicy
{ spLabel :: Text
spLabel = Text
"pilot-export"
, spClassify :: SomeException -> FaultDisposition
spClassify = FaultDisposition -> SomeException -> FaultDisposition
forall a b. a -> b -> a
const FaultDisposition
Transient
, spBackoff :: BackoffSchedule
spBackoff = BackoffSchedule{bsBaseMicros :: Int
bsBaseMicros = Int
intervalMicros, bsCapMicros :: Int
bsCapMicros = Int
intervalMicros}
}
(m () -> m Void) -> m () -> m Void
forall a b. (a -> b) -> a -> b
$ do
ResourceT m () -> m ()
forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT (Telemetry -> AmbientAws -> AppConfig -> Text -> ResourceT m ()
forall (m :: * -> *).
(MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) =>
Telemetry -> AmbientAws -> AppConfig -> Text -> m ()
exportNpm Telemetry
telemetry AmbientAws
ambient AppConfig
appCfg Text
bucketName)
Int -> m ()
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay Int
intervalMicros
exportNpm :: (MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) => Telemetry -> AmbientAws -> AppConfig -> Text -> m ()
exportNpm :: forall (m :: * -> *).
(MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) =>
Telemetry -> AmbientAws -> AppConfig -> Text -> m ()
exportNpm Telemetry
telemetry AmbientAws
ambient AppConfig
appCfg Text
bucketName = do
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS LogStr
"Starting npm OSV database compilation"
dbPath <- Maybe TracerProvider -> String -> Text -> String -> m String
forall (m :: * -> *).
(MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) =>
Maybe TracerProvider -> String -> Text -> String -> m String
compileOsvToSqlite (Telemetry -> Maybe TracerProvider
telemetryTracerProvider Telemetry
telemetry) (AdvisoriesSettings -> String
advDataDir (AppConfig -> AdvisoriesSettings
cfgAdvisories AppConfig
appCfg)) Text
"npm" (Text -> Text -> String
osvExportUrl (AdvisoriesSettings -> Text
advOsvExportBaseUrl (AppConfig -> AdvisoriesSettings
cfgAdvisories AppConfig
appCfg)) Text
"npm")
exportToS3 (telemetryTracerProvider telemetry) (ambientAwsEndpointUrl ambient >>= parseEndpointUrl) bucketName dbPath
data PilotCompileOptions = PilotCompileOptions
{ PilotCompileOptions -> Text
pcoEcosystem :: Text
, PilotCompileOptions -> Maybe String
pcoSource :: Maybe String
, PilotCompileOptions -> String
pcoOutDir :: FilePath
, PilotCompileOptions -> Bool
pcoUpload :: Bool
}
deriving stock (PilotCompileOptions -> PilotCompileOptions -> Bool
(PilotCompileOptions -> PilotCompileOptions -> Bool)
-> (PilotCompileOptions -> PilotCompileOptions -> Bool)
-> Eq PilotCompileOptions
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PilotCompileOptions -> PilotCompileOptions -> Bool
== :: PilotCompileOptions -> PilotCompileOptions -> Bool
$c/= :: PilotCompileOptions -> PilotCompileOptions -> Bool
/= :: PilotCompileOptions -> PilotCompileOptions -> Bool
Eq, Int -> PilotCompileOptions -> String -> String
[PilotCompileOptions] -> String -> String
PilotCompileOptions -> String
(Int -> PilotCompileOptions -> String -> String)
-> (PilotCompileOptions -> String)
-> ([PilotCompileOptions] -> String -> String)
-> Show PilotCompileOptions
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> PilotCompileOptions -> String -> String
showsPrec :: Int -> PilotCompileOptions -> String -> String
$cshow :: PilotCompileOptions -> String
show :: PilotCompileOptions -> String
$cshowList :: [PilotCompileOptions] -> String -> String
showList :: [PilotCompileOptions] -> String -> String
Show)
data PilotUploadUnconfigured = PilotUploadUnconfigured
deriving stock (PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool
(PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool)
-> (PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool)
-> Eq PilotUploadUnconfigured
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool
== :: PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool
$c/= :: PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool
/= :: PilotUploadUnconfigured -> PilotUploadUnconfigured -> Bool
Eq, Int -> PilotUploadUnconfigured -> String -> String
[PilotUploadUnconfigured] -> String -> String
PilotUploadUnconfigured -> String
(Int -> PilotUploadUnconfigured -> String -> String)
-> (PilotUploadUnconfigured -> String)
-> ([PilotUploadUnconfigured] -> String -> String)
-> Show PilotUploadUnconfigured
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> PilotUploadUnconfigured -> String -> String
showsPrec :: Int -> PilotUploadUnconfigured -> String -> String
$cshow :: PilotUploadUnconfigured -> String
show :: PilotUploadUnconfigured -> String
$cshowList :: [PilotUploadUnconfigured] -> String -> String
showList :: [PilotUploadUnconfigured] -> String -> String
Show)
instance Exception PilotUploadUnconfigured
runPilotCompile :: LogEnv -> Telemetry -> AmbientAws -> AppConfig -> PilotCompileOptions -> IO FilePath
runPilotCompile :: LogEnv
-> Telemetry
-> AmbientAws
-> AppConfig
-> PilotCompileOptions
-> IO String
runPilotCompile LogEnv
logEnv Telemetry
telemetry AmbientAws
ambient AppConfig
appCfg PilotCompileOptions
opts = do
let url :: String
url = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe (Text -> Text -> String
osvExportUrl (AdvisoriesSettings -> Text
advOsvExportBaseUrl (AppConfig -> AdvisoriesSettings
cfgAdvisories AppConfig
appCfg)) (PilotCompileOptions -> Text
pcoEcosystem PilotCompileOptions
opts)) (PilotCompileOptions -> Maybe String
pcoSource PilotCompileOptions
opts)
LogEnv
-> SimpleLogPayload
-> Namespace
-> KatipContextT IO String
-> IO String
forall c (m :: * -> *) a.
LogItem c =>
LogEnv -> c -> Namespace -> KatipContextT m a -> m a
runKatipContextT LogEnv
logEnv (Text -> SimpleLogPayload
moduleField Text
"Ecluse.Pilot") Namespace
forall a. Monoid a => a
mempty (KatipContextT IO String -> IO String)
-> KatipContextT IO String -> IO String
forall a b. (a -> b) -> a -> b
$
ResourceT (KatipContextT IO) String -> KatipContextT IO String
forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT (ResourceT (KatipContextT IO) String -> KatipContextT IO String)
-> ResourceT (KatipContextT IO) String -> KatipContextT IO String
forall a b. (a -> b) -> a -> b
$ do
dbFile <- Maybe TracerProvider
-> String -> Text -> String -> ResourceT (KatipContextT IO) String
forall (m :: * -> *).
(MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) =>
Maybe TracerProvider -> String -> Text -> String -> m String
compileOsvToSqlite (Telemetry -> Maybe TracerProvider
telemetryTracerProvider Telemetry
telemetry) (PilotCompileOptions -> String
pcoOutDir PilotCompileOptions
opts) (PilotCompileOptions -> Text
pcoEcosystem PilotCompileOptions
opts) String
url
when (pcoUpload opts) $
case advBucket (cfgAdvisories appCfg) of
Maybe Text
Nothing -> PilotUploadUnconfigured -> ResourceT (KatipContextT IO) ()
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO PilotUploadUnconfigured
PilotUploadUnconfigured
Just Text
bucket -> Maybe TracerProvider
-> Maybe (Bool, Text, Int)
-> Text
-> String
-> ResourceT (KatipContextT IO) ()
forall (m :: * -> *).
(MonadResource m, MonadUnliftIO m, MonadThrow m, KatipContext m) =>
Maybe TracerProvider
-> Maybe (Bool, Text, Int) -> Text -> String -> m ()
exportToS3 (Telemetry -> Maybe TracerProvider
telemetryTracerProvider Telemetry
telemetry) (AmbientAws -> Maybe Text
ambientAwsEndpointUrl AmbientAws
ambient Maybe Text
-> (Text -> Maybe (Bool, Text, Int)) -> Maybe (Bool, Text, Int)
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Maybe (Bool, Text, Int)
parseEndpointUrl) Text
bucket String
dbFile
pure dbFile