@@ -10,17 +10,12 @@ import Colog
1010import Control.AppRequestable
1111import Control.Concurrent
1212import Control.GithubCloner
13−import Control.HasGithubStatus
1413import Control.Monad.Except
1514import Control.Monad.IO.Unlift
1615import Control.Monad.Reader
1716import Control.Task.Scheduler
18−import Crypto.PubKey.RSA.Read
1917import Data.Aeson
2018import Data.ByteString
21−import qualified Data.ByteString as BS
22−import qualified Data.ByteString.Char8 as BSC
23−import qualified Data.ByteString.Lazy.Char8 as BSCL
2419import qualified Data.Csv as Csv
2520import Data.IORef
2621import Data.Map (Map)
@@ -30,17 +25,14 @@ import Data.Submission.Query
3025import Data.Tasks.Build
3126import Data.Tasks.StatusUpdate
3227import qualified Data.Text as T
33−import Data.Time.Clock
3428import Data.Yaml
3529import GHC.Generics
3630import GitHub hiding (Accept)
3731import GitHub.App.Auth
3832import GitHub.App.Request
39−import GitHub.Auth (Auth (OAuth))
4033import GitHub.Data.Name
4134import GitHub.Data.Webhooks.Events
42−import GitHub.Data.Webhooks.Payload hiding (URL (..))
43−import Network.HTTP.Client.TLS (newTlsManager)
35+import GitHub.Data.Webhooks.Payload
4436import Network.HTTP.Media.MediaType
4537import Network.Wai.Handler.Warp
4638import Servant
@@ -50,8 +42,9 @@ import Server.Config
5042import Server.Html
5143import Server.Schema
5244import Squeal.PostgreSQL
45+import System.IO
5346import Text.Blaze.Html
54−import UnliftIO.Exception (catch, displayException, handleAny, throwIO, try)
47+import UnliftIO.Exception (try)
5548
5649type API =
5750 "github-webhook"
@@ -59,8 +52,8 @@ type API =
5952 :> GitHubSignedReqBody '[JSON] InstallationEvent
6053 :> Post '[JSON] ()
6154 :<|> GitHubEvent '[ 'WebhookPushEvent]
62− :> GitHubSignedReqBody '[JSON] PushEvent
63− :> Post '[JSON] ()
55+ :> GitHubSignedReqBody '[JSON] PushEvent
56+ :> Post '[JSON] ()
6457 )
6558 :<|> "submission"
6659 :> Capture "user name" String
@@ -75,18 +68,17 @@ type API =
7568 :> Post '[HTML] Markup
7669 :<|> "results" :> Capture "task name" String :> Get '[CSV, HTML] [Score]
7770
78−webhookInstallation :: WithLog env Message m => RepoWebhookEvent -> ((), InstallationEvent) -> m ()
71+webhookInstallation :: MonadIO m => RepoWebhookEvent -> ((), InstallationEvent) -> m ()
7972webhookInstallation _ ((), ev) =
80− logInfo . T.pack . show $ ev
73+ liftIO $ do
74+ print ev
75+ hFlush stdout
76+
77+pendingStatus :: NewStatus
78+pendingStatus = NewStatus StatusPending Nothing Nothing Nothing
8179
8280webhookPushEvent ::
83− ( StaticPQ m,
84− MonadReader (ServerData m) m,
85− MonadUnliftIO m,
86− HasTasks m,
87− MonadError ServerError m,
88− WithLog env Message m
89− ) =>
81+ (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m) =>
9082 RepoWebhookEvent ->
9183 ((), PushEvent) ->
9284 m ()
@@ -94,42 +86,30 @@ webhookPushEvent _ ((), ev) = do
9486 let repo = evPushRepository ev
9587 user = T.unpack . whUserLogin . evPushSender $ ev
9688 fullRepoName = whRepoFullName repo
97− (owner, repoName) <- case T.splitOn "/" fullRepoName of
98− [a, b] -> return (a, b)
99− _ -> throwError err422
100− sha <- maybe (throwError err422) return $ evPushHeadSha ev
89+ [owner, repoName] = T.splitOn "/" fullRepoName
90+ Just sha = evPushHeadSha ev
10191 tasks <- getTasks
10292 let task =
10393 M.lookupMax $
10494 M.takeWhileAntitone (repoName >) tasks
10595 case task of
10696 Just (name, TaskConfig {..}) | T.take (T.length name) repoName == name -> do
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
97+ scheduleTask $ StatusUpdate (N owner) (N repoName) (N sha) pendingStatus
98+ createSubmission $
99+ Submission
100+ user
101+ (T.unpack fullRepoName)
102+ (T.unpack name)
103+ (T.unpack sha)
104+ (Jsonb BuildScheduled)
105+ scheduleTask $ Build prebuild dockerfile (N owner) (N repoName) (N sha)
106+ liftIO $ do
107+ print ev
108+ hFlush stdout
129109 _ -> return ()
130110
131111retestSubmission ::
132− (StaticPQ m, MonadReader (ServerData m) m, MonadUnliftIO m, HasTasks m, MonadHasBaseUrl m) =>
112+ (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m, MonadHasBaseUrl m) =>
133113 String ->
134114 String ->
135115 String ->
@@ -148,9 +128,9 @@ retestSubmission owner repoName sha = do
148128 M.takeWhileAntitone (repoName' >) tasks
149129 case task of
150130 Just (name, TaskConfig {..}) | T.take (T.length name) repoName' == name -> do
151− schedulePendingStatus (N owner') (N repoName') (N sha')
131+ scheduleTask $ StatusUpdate (N owner') (N repoName') (N sha') pendingStatus
152132 updateSubmissionStatus fullRepoName sha BuildScheduled
153− scheduleTask $ Build prebuild dockerfile (N owner') (N repoName') (N sha') timeoutMinutes
133+ scheduleTask $ Build prebuild dockerfile (N owner') (N repoName') (N sha')
154134 _ -> return ()
155135 redirectToSubmission owner repoName sha
156136
@@ -168,21 +148,13 @@ newtype ServerM (schema :: SchemasType) a
168148 MonadUnliftIO
169149 )
170150
171−instance MonadError ServerError (ServerM schema) where
172− throwError = throwIO
173− catchError = catch
174−
175151server :: ServerT API (ServerM Schema)
176152server = (webhookInstallation :<|> webhookPushEvent) :<|> getSubmissionR :<|> retestSubmission :<|> getResults
177153
178−runServer :: FilePath -> IO ()
179−runServer config = do
180− c@Config {..} <- getConfig config
154+runServer :: IO ()
155+runServer = do
156+ c@Config {..} <- getConfig
181157 print c
182− pem <- BS.readFile appPkPemPath
183− appPkPem <- case readRsaPem pem of
184− Right k -> return k
185− Left e -> error (show e)
186158 auth <- mkInstallationAuth (AppAuth appId appPkPem) installationId
187159 ts <- newIORef mempty
188160 pool <- createConnectionPool databseUrl 1 0.5 10
@@ -192,38 +164,29 @@ runServer config = do
192164 let context =
193165 gitPolyHubKey (return webhookSecret)
194166 :. EmptyContext
195− serverData =
196− ServerData
197− { githubAppAuth = auth,
198− tasks = ts,
199− logger = cfilter ((logSeverity <=) . msgSeverity) richMessageAction,
200− baseUrl = baseSiteUrl,
201− dockerUrl = T.pack cfgDockerUrl,
202− context = githubContext
203− }
167+ serverData = ServerData
168+ { githubAppAuth = auth,
169+ githubUserName = GithubUserName githubUsername,
170+ githubAccessToken = GithubAccessToken personalAccessToken,
171+ tasks = ts,
172+ logger = cfilter ((logSeverity <=) . msgSeverity) richMessageAction,
173+ baseUrl = baseSiteUrl,
174+ dockerUrl = T.pack cfgDockerUrl
175+ }
204176 repeatIfNotEmpty n f = f >>= \m -> do
205177 when (m == 0) $ liftIO $ threadDelay n
206178 repeatIfNotEmpty n f
207− printingErrors =
208− handleAny (logError . T.pack . displayException >=> const (return 0))
209− usingConnectionPool pool
210− . (`runReaderT` serverData)
211− . unApp
212− . handleAny (error . displayException)
213− $ makeAllRunnable
214− replicateM_ builderCount
215− $ forkIO . repeatIfNotEmpty (10 ^ (6 :: Int))
179+ _ <-
180+ forkIO . repeatIfNotEmpty (10 ^ (6 :: Int))
216181 . usingConnectionPool pool
217182 . (`runReaderT` serverData)
218183 . unApp
219− . printingErrors
220− $ runTasks @'[Build] 1
184+ $ runTasks @'[Build] 1
221185 _ <-
222186 forkIO . repeatIfNotEmpty (10 ^ (6 :: Int))
223187 . usingConnectionPool pool
224188 . (`runReaderT` serverData)
225189 . unApp
226− . printingErrors
227190 $ runTasks @'[StatusUpdate] 5
228191 run port $ serveWithContext (Proxy @API) context $
229192 hoistServerWithContext
@@ -241,11 +204,12 @@ updateTasks ref path =
241204data ServerData m
242205 = ServerData
243206 { githubAppAuth :: !InstallationAuth,
207+ githubUserName :: !GithubUserName,
208+ githubAccessToken :: !GithubAccessToken,
244209 tasks :: IORef TasksConfig,
245210 logger :: LogAction m Message,
246− baseUrl :: T.Text,
247− dockerUrl :: T.Text,
248− context :: T.Text
211+ baseUrl :: String,
212+ dockerUrl :: T.Text
249213 }
250214
251215newtype PolyGitHubKey = PolyGitHubKey (forall result. GitHubKey result)
@@ -256,18 +220,11 @@ gitPolyHubKey k = PolyGitHubKey (Servant.GitHub.Webhook.gitHubKey k)
256220instance HasContextEntry '[PolyGitHubKey] (GitHubKey result) where
257221 getContextEntry (PolyGitHubKey x :. _) = x
258222
259−instance (MonadReader (ServerData n) m, MonadIO m, MonadError ServerError m) => GithubCloner m where
260− getGithubInstallationToken =
261− either
262− (\e -> throwError err500 {errBody = BSCL.pack . displayException $ e})
263− return
264− <=< runExceptT
265− $ do
266− iAuth <- asks githubAppAuth
267− manager <- newTlsManager
268− liftIO (obtainAccessToken manager iAuth) >>= either throwError return >>= \case
269− OAuth token -> return $ InstallationToken (BSC.unpack token)
270− _ -> throwError $ UserError "Not OAuth token. This should never happen."
223+instance MonadReader (ServerData n) m => GithubCloner m where
224+
225+ getGithubAccessToken = asks githubAccessToken
226+
227+ getGithubUserName = asks githubUserName
271228
272229instance (MonadIO m, MonadReader (ServerData n) m) => AppRequestable m where
273230 appRequest req = do
@@ -280,71 +237,6 @@ instance MonadReader (ServerData n) m => MonadHasBaseUrl m where
280237instance (MonadIO m, MonadReader (ServerData n) m) => HasTasks m where
281238 getTasks = asks tasks >>= liftIO . readIORef
282239
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−
348240instance (MonadReader (ServerData n) m) => HasDockerInfo m where
349241 getDockerUrl = asks dockerUrl
350242
@@ -353,9 +245,8 @@ type TasksConfig = Map T.Text TaskConfig
353245data TaskConfig
354246 = TaskConfig
355247 { prebuild :: String,
356− dockerfile :: String,
357− timeoutMinutes :: Int,
358− deadline :: UTCTime
248+ dockerfile :: String
249+ -- build :: T.Text
359250 }
360251 deriving stock (Show, Eq, Generic)
361252 deriving anyclass (FromJSON, ToJSON)
@@ -364,6 +255,7 @@ class HasTasks m where
364255 getTasks :: m TasksConfig
365256
366257instance HasLog (ServerData m) Message m where
258+
367259 getLogAction :: ServerData m -> LogAction m Message
368260 getLogAction = logger
369261 {-# INLINE getLogAction #-}