@@ -2,6 +2,8 @@ module Octopod.Server (runOctopodServer) where
22
33import Common.Validation
44import Control.Applicative
5+import Control.CacheMap (CacheMap)
6+import qualified Control.CacheMap as CM
57import Control.Concurrent (threadDelay)
68import Control.Concurrent.Async (race_)
79import qualified Control.Concurrent.Lifted as L
@@ -70,6 +72,12 @@ import Prelude hiding (lines, log, unlines, unwords)
7072
7173type AppM = ReaderT AppState Handler
7274
75+type AppMConstraints m = (MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m)
76+
77+-- | This is a polymorphic monad wrapper with the main constraints from 'AppM'.
78+-- Currently used only for CacheMap values.
79+newtype AppM' a = AppM' {runAppM' :: forall m. AppMConstraints m => m a}
80+
7381-- | Octopod Server state definition.
7482data AppState = AppState
7583 { -- | postgres pool
@@ -118,6 +126,10 @@ data AppState = AppState
118126 , -- | Deployments currently being processed which has not yet been
119127 -- recorded in the database.
120128 lockedDeployments :: LockedDeployments
129+ , depOverridesCache :: CacheMap ServerError AppM' () (DefaultConfig 'DeploymentLevel)
130+ , depOverrideKeysCache :: CacheMap ServerError AppM' () [Text]
131+ , appOverridesCache :: CacheMap ServerError AppM' (Config 'DeploymentLevel) (DefaultConfig 'ApplicationLevel)
132+ , appOverrideKeysCache :: CacheMap ServerError AppM' (Config 'DeploymentLevel) [Text]
121133 }
122134 deriving stock (Generic)
123135
@@ -174,11 +186,36 @@ runOctopodServer = do
174186 aKeysCmd <- Command . pack <$> getEnvOrDie "APPLICATION_KEYS_COMMAND"
175187 unarchiveCmd <- Command . pack <$> getEnvOrDie "UNARCHIVE_COMMAND"
176188 powerAuthorizationHeader <- AuthHeader . BSC.pack <$> getEnvOrDie "POWER_AUTHORIZATION_HEADER"
189+ invalidationTime <- fromInteger . maybe (60 * 60 * 24) read <$> lookupEnv "CACHE_INVALIDATION_TIME"
190+ updateTime <- fromInteger . maybe (60 * 10) read <$> lookupEnv "CACHE_UPDATE_TIME"
191+ let cacheMap :: forall a x. (forall m. AppMConstraints m => x -> m a) -> IO (CacheMap ServerError AppM' x a)
192+ cacheMap f = CM.initCacheMap invalidationTime updateTime $ \x -> AppM' $ f x
193+ decodeCSVDefaultConfig :: BSL.ByteString -> Either String (DefaultConfig l)
194+ decodeCSVDefaultConfig bs = do
195+ x <- C.decode C.NoHeader bs
196+ pure $ DefaultConfig . OM.fromList . V.toList $ x
197+ either500S :: (MonadError ServerError m, MonadBase IO m) => Either String x -> m x
198+ either500S (Right x) = pure x
199+ either500S (Left err) = do
200+ liftBase . logWarning logger' . T.pack $ err
201+ throwError err500 {errBody = BSLC.pack err}
177202 notificationCmd <-
178203 (fmap . fmap) (Command . pack) $
179204 lookupEnv "NOTIFICATION_COMMAND" <&> \case
180205 Just "" -> Nothing
181206 x -> x
207+ depOverrideKeysCache' <- cacheMap $ \() -> do
208+ (_, Stdout out, _) <- deploymentOverrideKeys >>= runCommandArgs deploymentOverrideKeysCommand
209+ pure $ T.lines out
210+ depOverridesCache' <- cacheMap $ \() -> do
211+ (_, Stdout out, _) <- defaultDeploymentOverridesArgs >>= runCommandArgs deploymentOverridesCommand
212+ either500S $ decodeCSVDefaultConfig . BSL.fromStrict . T.encodeUtf8 $ out
213+ appOverrideKeysCache' <- cacheMap $ \cfg -> do
214+ (_, Stdout out, _) <- applicationOverrideKeys cfg >>= runCommandArgs applicationOverrideKeysCommand
215+ pure $ T.lines out
216+ appOverridesCache' <- cacheMap $ \cfg -> do
217+ (_, Stdout out, _) <- defaultApplicationOverridesArgs cfg >>= runCommandArgs applicationOverridesCommand
218+ either500S $ decodeCSVDefaultConfig . BSL.fromStrict . T.encodeUtf8 $ out
182219 dbPool' <-
183220 createPool
184221 (fmap (either (error . show) id) . acquire $ unDBConnectionString $ octopodDB opts)
@@ -190,32 +227,38 @@ runOctopodServer = do
190227 lockedDs <- initLockedDeployments
191228 let appSt =
192229 AppState
193− dbPool'
194− logger'
195− channel
196− bgWorkersC
197− gracefulShutdownAct
198− shutdownS
199− projName
200− domain
201− ns
202− archRetention
203− stUpdateTimeout
204− creationCmd
205− updateCmd
206− archiveCmd
207− checkingCmd
208− cleanupCmd
209− archiveCheckingCmd
210− tagCheckingCmd
211− infoCmd
212− notificationCmd
213− dOverridesCmd
214− dKeysCmd
215− aOverridesCmd
216− aKeysCmd
217− unarchiveCmd
218− lockedDs
230+ { dbPool = dbPool'
231+ , logger = logger'
232+ , eventSink = channel
233+ , bgWorkersCounter = bgWorkersC
234+ , gracefulShutdownActivated = gracefulShutdownAct
235+ , shutdownSem = shutdownS
236+ , projectName = projName
237+ , baseDomain = domain
238+ , namespace = ns
239+ , archiveRetention = archRetention
240+ , statusUpdateTimeout = stUpdateTimeout
241+ , creationCommand = creationCmd
242+ , updateCommand = updateCmd
243+ , archiveCommand = archiveCmd
244+ , checkingCommand = checkingCmd
245+ , cleanupCommand = cleanupCmd
246+ , archiveCheckingCommand = archiveCheckingCmd
247+ , tagCheckingCommand = tagCheckingCmd
248+ , infoCommand = infoCmd
249+ , notificationCommand = notificationCmd
250+ , deploymentOverridesCommand = dOverridesCmd
251+ , deploymentOverrideKeysCommand = dKeysCmd
252+ , applicationOverridesCommand = aOverridesCmd
253+ , applicationOverrideKeysCommand = aKeysCmd
254+ , unarchiveCommand = unarchiveCmd
255+ , lockedDeployments = lockedDs
256+ , depOverridesCache = depOverridesCache'
257+ , depOverrideKeysCache = depOverrideKeysCache'
258+ , appOverridesCache = appOverridesCache'
259+ , appOverrideKeysCache = appOverrideKeysCache'
260+ }
261+
219262 app' = app appSt
220263 wsApp' = wsApp channel
221264 serverPort = octopodPort opts
@@ -283,34 +326,22 @@ server =
283326 :<|> pingH
284327 :<|> projectNameH
285328
286−decodeCSVDefaultConfig :: BSL.ByteString -> Either String (DefaultConfig l)
287−decodeCSVDefaultConfig bs = do
288− x <- C.decode C.NoHeader bs
289− pure $ DefaultConfig . OM.fromList . V.toList $ x
329+lookupCache :: (Ord x, AppMConstraints m) => (AppState -> CacheMap ServerError AppM' x y) -> x -> m y
330+lookupCache f x = do
331+ cm <- asks f
332+ CM.lookupBlocking (CM.ntCacheMap runAppM' cm) x
290333
291−either500S :: MonadError ServerError m => Either String x -> m x
292−either500S (Right x) = pure x
293−either500S (Left err) = throwError err500 {errBody = BSLC.pack err}
334+defaultDeploymentKeysH :: AppMConstraints m => m [Text]
335+defaultDeploymentKeysH = lookupCache depOverrideKeysCache ()
294336
295−defaultDeploymentKeysH :: AppM [Text]
296−defaultDeploymentKeysH = do
297− (_, Stdout out, _) <- deploymentOverrideKeys >>= runCommandArgs deploymentOverrideKeysCommand
298− pure $ T.lines out
337+defaultDeploymentOverridesH :: AppMConstraints m => m (DefaultConfig 'DeploymentLevel)
338+defaultDeploymentOverridesH = lookupCache depOverridesCache ()
299339
300−defaultDeploymentOverridesH :: AppM (DefaultConfig 'DeploymentLevel)
301−defaultDeploymentOverridesH = do
302− (_, Stdout out, _) <- defaultDeploymentOverridesArgs >>= runCommandArgs deploymentOverridesCommand
303− either500S $ decodeCSVDefaultConfig . BSL.fromStrict . T.encodeUtf8 $ out
340+defaultApplicationKeysH :: AppMConstraints m => Config 'DeploymentLevel -> m [Text]
341+defaultApplicationKeysH = lookupCache appOverrideKeysCache
304342
305−defaultApplicationKeysH :: Config 'DeploymentLevel -> AppM [Text]
306−defaultApplicationKeysH cfg = do
307− (_, Stdout out, _) <- applicationOverrideKeys cfg >>= runCommandArgs applicationOverrideKeysCommand
308− pure $ T.lines out
309−
310−defaultApplicationOverridesH :: Config 'DeploymentLevel -> AppM (DefaultConfig 'ApplicationLevel)
311−defaultApplicationOverridesH cfg = do
312− (_, Stdout out, _) <- defaultApplicationOverridesArgs cfg >>= runCommandArgs applicationOverridesCommand
313− either500S $ decodeCSVDefaultConfig . BSL.fromStrict . T.encodeUtf8 $ out
343+defaultApplicationOverridesH :: AppMConstraints m => Config 'DeploymentLevel -> m (DefaultConfig 'ApplicationLevel)
344+defaultApplicationOverridesH = lookupCache appOverridesCache
314345
315346-- | Application with the octo CLI API.
316347powerApp :: AuthHeader -> AppState -> IO Application
@@ -490,8 +521,8 @@ updateDeploymentInfo ::
490521updateDeploymentInfo dName = do
491522 log <- asks (logWarning . logger)
492523 DeploymentFullInfo {deployment = dep} <- getDeploymentS dName
493− dc <- getDefaultConfig dName
494− (ec, out, err) <- runCommandArgs infoCommand =<< infoCommandArgs dc dep
524+ cfg <- getDeploymentConfig dep
525+ (ec, out, err) <- runCommandArgs infoCommand =<< infoCommandArgs cfg dep
495526 case ec of
496527 ExitSuccess -> case parseDeploymentMetadata (unStdout out) of
497528 Right dMeta -> upsertDeploymentMetadatum dName dMeta
@@ -505,22 +536,24 @@ updateDeploymentInfo dName = do
505536 <> "stderr: "
506537 <> coerce err
507538
508−getDefaultConfig :: Monad m => DeploymentName -> m FullDefaultConfig
509−getDefaultConfig _ =
539+getDeploymentConfig :: AppMConstraints m => Deployment -> m FullConfig
540+getDeploymentConfig dep = do
541+ depCfg <- applyOverrides (dep ^. #deploymentOverrides) <$> defaultDeploymentOverridesH
542+ appCfg <- applyOverrides (dep ^. #appOverrides) <$> defaultApplicationOverridesH depCfg
510543 pure
511− FullDefaultConfig
512− { appDefaultConfig = DefaultConfig OM.empty
513− , depDefaultConfig = DefaultConfig OM.empty
544+ FullConfig
545+ { appConfig = appCfg
546+ , depConfig = depCfg
514547 }
515548
516549-- | Helper to create a new deployment.
517550createDeployment ::
518− (MonadBaseControl IO m, MonadReader AppState m) =>
551+ (MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
519552 Deployment ->
520553 m (ExitCode, Stdout, Stderr)
521554createDeployment dep = do
522555 st <- ask
523− dCfg <- getDefaultConfig $ dep ^. #name
556+ cfg <- getDeploymentConfig dep
524557 let log :: Text -> IO ()
525558 log = logInfo (logger st)
526559 args =
@@ -536,7 +569,7 @@ createDeployment dep = do
536569 , "--tag"
537570 , T.unpack . coerce $ dep ^. #tag
538571 ]
539− <> fullConfigArgs dCfg dep
572+ <> fullConfigArgs cfg
540573 (ec, out, err) <- runCommandArgs creationCommand args
541574 liftBase . log $ "deployment created, deployment: " <> (pack . show $ dep)
542575 pure (ec, out, err)
@@ -807,7 +840,7 @@ updateH dName dUpdate = do
807840
808841 updateDeploymentInfo dName
809842 liftBase $ sendReloadEvent st
810− dCfg <- getDefaultConfig dName
843+ cfg <- getDeploymentConfig dep
811844 let args =
812845 ControlScriptArgs
813846 [ "--project-name"
@@ -821,7 +854,7 @@ updateH dName dUpdate = do
821854 , "--tag"
822855 , T.unpack . coerce $ dep ^. #tag
823856 ]
824− <> fullConfigArgs dCfg dep
857+ <> fullConfigArgs cfg
825858 (ec, out, err) <- runCommandArgs updateCommand args
826859 liftBase . log $
827860 "deployment updated, name: "
@@ -884,9 +917,9 @@ projectNameH = asks projectName
884917-- | Handles the 'status' request.
885918statusH :: DeploymentName -> AppM CurrentDeploymentStatus
886919statusH dName = do
887− dep <- getDeploymentS dName
888− dCfg <- getDefaultConfig dName
889− (ec, _, _) <- runCommandArgs checkingCommand =<< checkCommandArgs dCfg (dep ^. #deployment)
920+ (view #deployment -> dep) <- getDeploymentS dName
921+ cfg <- getDeploymentConfig dep
922+ (ec, _, _) <- runCommandArgs checkingCommand =<< checkCommandArgs cfg dep
890923 pure . CurrentDeploymentStatus $
891924 case ec of
892925 ExitSuccess -> Ok
@@ -985,10 +1018,10 @@ restoreH dName = do
9851018 failIfImageNotFound $ dep ^. #deployment
9861019 failIfGracefulShutdownActivated
9871020 runDeploymentBgWorker (Just CreatePending) dName (pure ()) $ \() -> do
988− dep' <- getDeploymentS dName
1021+ (view #deployment -> dep') <- getDeploymentS dName
9891022 updateDeploymentInfo dName
990− dCfg <- getDefaultConfig dName
991− (ec, out, err) <- runCommandArgs unarchiveCommand =<< unarchiveCommandArgs dCfg (dep' ^. #deployment)
1023+ cfg <- getDeploymentConfig dep'
1024+ (ec, out, err) <- runCommandArgs unarchiveCommand =<< unarchiveCommandArgs cfg dep'
9921025 t2 <- liftBase getCurrentTime
9931026 let elTime = elapsedTime t2 t1
9941027 transitionToStatusS dName $
@@ -1089,8 +1122,8 @@ upsertDeploymentMetadatum dName dMetadata =
10891122-- Returns 404 'Tag not found' response if the deployment tag doesn't exist.
10901123failIfImageNotFound :: Deployment -> AppM ()
10911124failIfImageNotFound dep = do
1092− dCfg <- getDefaultConfig $ dep ^. #name
1093− (ec, _, _) <- runCommandArgs tagCheckingCommand =<< tagCheckCommandArgs dCfg dep
1125+ cfg <- getDeploymentConfig dep
1126+ (ec, _, _) <- runCommandArgs tagCheckingCommand =<< tagCheckCommandArgs cfg dep
10941127 case ec of
10951128 ExitSuccess -> pure ()
10961129 ExitFailure _ ->
@@ -1112,7 +1145,7 @@ sendReloadEvent state =
11121145
11131146-- | Returns time delta between 2 timestamps.
11141147elapsedTime :: UTCTime -> UTCTime -> Duration
1115−elapsedTime t1 t2 = Duration . calendarTimeTime $ t2 `diffUTCTime` t1
1148+elapsedTime t1 t2 = Duration . calendarTimeTime $ Prelude.abs $ t2 `diffUTCTime` t1
11161149
11171150-- | Runs the status updater.
11181151runStatusUpdater :: AppState -> IO ()
@@ -1124,7 +1157,10 @@ runStatusUpdater state = do
11241157 forever $ do
11251158 currentTime <- liftBase getCurrentTime
11261159 let cutoff = addUTCTime (negate interval) currentTime
1127− flip runReaderT state $ do
1160+ logLeft :: Either ServerError () -> IO ()
1161+ logLeft (Left err) = logErr . T.pack $ displayException err
1162+ logLeft (Right ()) = pure ()
1163+ (>>= logLeft) . runExceptT . flip runReaderT state $ do
11281164 rows' <- runStatement . select $ do
11291165 ds <- each deploymentSchema
11301166 where_ $ ds ^. #checkedAt <. litExpr cutoff
@@ -1139,9 +1175,9 @@ runStatusUpdater state = do
11391175 _ -> do
11401176 getSingleFullInfo dName >>= \case
11411177 Nothing -> liftBase (logErr $ "Couldn't find deployment: " <> coerce dName) $> Nothing
1142− Just dep -> do
1143− dCfg <- getDefaultConfig dName
1144− (ec, _, _) <- runCommandArgs checkingCommand =<< checkCommandArgs dCfg (dep ^. #deployment)
1178+ Just (view #deployment -> dep) -> do
1179+ cfg <- getDeploymentConfig dep
1180+ (ec, _, _) <- runCommandArgs checkingCommand =<< checkCommandArgs cfg dep
11451181 pure $ Just ec
11461182 pure $ mEc <&> \ec -> (dName, statusTransition ec dStatus ts timeout, dStatus)
11471183 updated <-
@@ -1287,7 +1323,6 @@ extractDeploymentFullInfo ::
12871323extractDeploymentFullInfo d = do
12881324 let dName = d ^. #name
12891325 locked <- isDeploymentLocked dName
1290− dCfg <- getDefaultConfig dName
12911326 pure
12921327 DeploymentFullInfo
12931328 { deployment = extractDeployment d
@@ -1298,5 +1333,4 @@ extractDeploymentFullInfo d = do
12981333 , metadata = d ^. #metadata
12991334 , createdAt = d ^. #createdAt
13001335 , updatedAt = d ^. #updatedAt
1301− , deploymentDefaultConfig = dCfg
13021336 }