@@ -10,6 +10,7 @@ import Colog
1010import Control.AppRequestable
1111import Control.Concurrent
1212import Control.GithubCloner
13+import Control.HasGithubStatus
1314import Control.Monad.Except
1415import Control.Monad.IO.Unlift
1516import Control.Monad.Reader
@@ -29,6 +30,7 @@ import Data.Submission.Query
2930import Data.Tasks.Build
3031import Data.Tasks.StatusUpdate
3132import qualified Data.Text as T
33+import Data.Time.Clock
3234import Data.Yaml
3335import GHC.Generics
3436import GitHub hiding (Accept)
@@ -37,7 +39,7 @@ import GitHub.App.Request
3739import GitHub.Auth (Auth (OAuth))
3840import GitHub.Data.Name
3941import GitHub.Data.Webhooks.Events
40−import GitHub.Data.Webhooks.Payload
42+import GitHub.Data.Webhooks.Payload hiding (URL (..))
4143import Network.HTTP.Client.TLS (newTlsManager)
4244import Network.HTTP.Media.MediaType
4345import Network.Wai.Handler.Warp
@@ -48,7 +50,6 @@ import Server.Config
4850import Server.Html
4951import Server.Schema
5052import Squeal.PostgreSQL
51−import System.IO
5253import Text.Blaze.Html
5354import UnliftIO.Exception (catch, displayException, handleAny, throwIO, try)
5455
@@ -74,17 +75,18 @@ type API =
7475 :> Post '[HTML] Markup
7576 :<|> "results" :> Capture "task name" String :> Get '[CSV, HTML] [Score]
7677
77−webhookInstallation :: MonadIO m => RepoWebhookEvent -> ((), InstallationEvent) -> m ()
78+webhookInstallation :: WithLog env Message m => RepoWebhookEvent -> ((), InstallationEvent) -> m ()
7879webhookInstallation _ ((), ev) =
79− liftIO $ do
80− print ev
81− hFlush stdout
82−
83−pendingStatus :: NewStatus
84−pendingStatus = NewStatus StatusPending Nothing Nothing Nothing
80+ logInfo . T.pack . show $ ev
8581
8682webhookPushEvent ::
87− (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m, MonadError ServerError m) =>
83+ ( StaticPQ m,
84+ MonadReader (ServerData m) m,
85+ MonadUnliftIO m,
86+ HasTasks m,
87+ MonadError ServerError m,
88+ WithLog env Message m
89+ ) =>
8890 RepoWebhookEvent ->
8991 ((), PushEvent) ->
9092 m ()
@@ -102,22 +104,32 @@ webhookPushEvent _ ((), ev) = do
102104 M.takeWhileAntitone (repoName >) tasks
103105 case task of
104106 Just (name, TaskConfig {..}) | T.take (T.length name) repoName == name -> do
105− scheduleTask $ StatusUpdate (N owner) (N repoName) (N sha) pendingStatus
106− createSubmission $
107− Submission
108− user
109− (T.unpack fullRepoName)
110− (T.unpack name)
111− (T.unpack sha)
112− (Jsonb BuildScheduled)
113− scheduleTask $ Build prebuild dockerfile (N owner) (N repoName) (N sha) timeoutMinutes
114− liftIO $ do
115− print ev
116− hFlush stdout
107+ nowTime <- liftIO getCurrentTime
108+ if nowTime > deadline
109+ then do
110+ deadlineHasPassedStatus (N owner) (N repoName) (N sha)
111+ createSubmission $
112+ Submission
113+ user
114+ (T.unpack fullRepoName)
115+ (T.unpack name)
116+ (T.unpack sha)
117+ (Jsonb PastDeadline)
118+ else do
119+ schedulePendingStatus (N owner) (N repoName) (N sha)
120+ createSubmission $
121+ Submission
122+ user
123+ (T.unpack fullRepoName)
124+ (T.unpack name)
125+ (T.unpack sha)
126+ (Jsonb BuildScheduled)
127+ scheduleTask $ Build prebuild dockerfile (N owner) (N repoName) (N sha) timeoutMinutes
128+ logInfo . T.pack . show $ ev
117129 _ -> return ()
118130
119131retestSubmission ::
120− (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m, MonadHasBaseUrl m) =>
132+ (StaticPQ m, MonadReader (ServerData m) m, MonadUnliftIO m, HasTasks m, MonadHasBaseUrl m) =>
121133 String ->
122134 String ->
123135 String ->
@@ -136,7 +148,7 @@ retestSubmission owner repoName sha = do
136148 M.takeWhileAntitone (repoName' >) tasks
137149 case task of
138150 Just (name, TaskConfig {..}) | T.take (T.length name) repoName' == name -> do
139− scheduleTask $ StatusUpdate (N owner') (N repoName') (N sha') pendingStatus
151+ schedulePendingStatus (N owner') (N repoName') (N sha')
140152 updateSubmissionStatus fullRepoName sha BuildScheduled
141153 scheduleTask $ Build prebuild dockerfile (N owner') (N repoName') (N sha') timeoutMinutes
142154 _ -> return ()
@@ -163,9 +175,9 @@ instance MonadError ServerError (ServerM schema) where
163175server :: ServerT API (ServerM Schema)
164176server = (webhookInstallation :<|> webhookPushEvent) :<|> getSubmissionR :<|> retestSubmission :<|> getResults
165177
166−runServer :: IO ()
167−runServer = do
168− c@Config {..} <- getConfig
178+runServer :: FilePath -> IO ()
179+runServer config = do
180+ c@Config {..} <- getConfig config
169181 print c
170182 pem <- BS.readFile appPkPemPath
171183 appPkPem <- case readRsaPem pem of
@@ -186,13 +198,19 @@ runServer = do
186198 tasks = ts,
187199 logger = cfilter ((logSeverity <=) . msgSeverity) richMessageAction,
188200 baseUrl = baseSiteUrl,
189− dockerUrl = T.pack cfgDockerUrl
201+ dockerUrl = T.pack cfgDockerUrl,
202+ context = githubContext
190203 }
191204 repeatIfNotEmpty n f = f >>= \m -> do
192205 when (m == 0) $ liftIO $ threadDelay n
193206 repeatIfNotEmpty n f
194207 printingErrors =
195208 handleAny (logError . T.pack . displayException >=> const (return 0))
209+ usingConnectionPool pool
210+ . (`runReaderT` serverData)
211+ . unApp
212+ . handleAny (error . displayException)
213+ $ makeAllRunnable
196214 replicateM_ builderCount
197215 $ forkIO . repeatIfNotEmpty (10 ^ (6 :: Int))
198216 . usingConnectionPool pool
@@ -225,8 +243,9 @@ data ServerData m
225243 { githubAppAuth :: !InstallationAuth,
226244 tasks :: IORef TasksConfig,
227245 logger :: LogAction m Message,
228− baseUrl :: String,
229− dockerUrl :: T.Text
246+ baseUrl :: T.Text,
247+ dockerUrl :: T.Text,
248+ context :: T.Text
230249 }
231250
232251newtype PolyGitHubKey = PolyGitHubKey (forall result. GitHubKey result)
@@ -261,6 +280,71 @@ instance MonadReader (ServerData n) m => MonadHasBaseUrl m where
261280instance (MonadIO m, MonadReader (ServerData n) m) => HasTasks m where
262281 getTasks = asks tasks >>= liftIO . readIORef
263282
283+instance (MonadReader (ServerData m) m, StaticPQ m, MonadUnliftIO m) => HasGithubStatus m where
284+ schedulePendingStatus nOwner@(N owner) nRepo@(N repo) nCommit@(N sha) = do
285+ h <- asks baseUrl
286+ c <- asks context
287+ scheduleTask $
288+ StatusUpdate
289+ nOwner
290+ nRepo
291+ nCommit
292+ ( NewStatus
293+ StatusPending
294+ (Just . URL $ h <> "/submission/" <> owner <> "/" <> repo <> "/" <> sha)
295+ (Just "⌛")
296+ (Just c)
297+ )
298+ scheduleTestedStatus TestResult {..} nOwner@(N owner) nRepo@(N repo) nCommit@(N sha) = do
299+ h <- asks baseUrl
300+ c <- asks context
301+ let total = M.size tests
302+ passed = M.size . M.filter id $ tests
303+ description = T.pack $ show passed <> "/" <> show total <> if passed == total then " ✅" else " ❌"
304+ status =
305+ StatusUpdate
306+ nOwner
307+ nRepo
308+ nCommit
309+ NewStatus
310+ { newStatusState =
311+ if total == passed then StatusSuccess else StatusFailure,
312+ newStatusTargetUrl = Just . URL $ h <> "/submission/" <> owner <> "/" <> repo <> "/" <> sha,
313+ newStatusDescription = Just description,
314+ newStatusContext = Just c
315+ }
316+ scheduleTask status
317+ scheduleFailedStatus _ nOwner@(N owner) nRepo@(N repo) nCommit@(N sha) = do
318+ h <- asks baseUrl
319+ c <- asks context
320+ let status =
321+ StatusUpdate
322+ nOwner
323+ nRepo
324+ nCommit
325+ NewStatus
326+ { newStatusState = StatusError,
327+ newStatusTargetUrl = Just . URL $ h <> "/submission/" <> owner <> "/" <> repo <> "/" <> sha,
328+ newStatusDescription = Just "Build failed ☠️",
329+ newStatusContext = Just c
330+ }
331+ scheduleTask status
332+ deadlineHasPassedStatus nOwner@(N owner) nRepo@(N repo) nCommit@(N sha) = do
333+ h <- asks baseUrl
334+ c <- asks context
335+ let status =
336+ StatusUpdate
337+ nOwner
338+ nRepo
339+ nCommit
340+ NewStatus
341+ { newStatusState = StatusError,
342+ newStatusTargetUrl = Just . URL $ h <> "/submission/" <> owner <> "/" <> repo <> "/" <> sha,
343+ newStatusDescription = Just "The deadline has passed ⏰",
344+ newStatusContext = Just c
345+ }
346+ scheduleTask status
347+
264348instance (MonadReader (ServerData n) m) => HasDockerInfo m where
265349 getDockerUrl = asks dockerUrl
266350
@@ -270,7 +354,8 @@ data TaskConfig
270354 = TaskConfig
271355 { prebuild :: String,
272356 dockerfile :: String,
273− timeoutMinutes :: Int
357+ timeoutMinutes :: Int,
358+ deadline :: UTCTime
274359 }
275360 deriving stock (Show, Eq, Generic)
276361 deriving anyclass (FromJSON, ToJSON)