on master help sign in
Added docker timeout
commiteb15496a6b60b71eecdc0cc3bdcd39c3ff0adadf authoriko <ilyakooo0@gmail.com> dateMon 24 Feb 2020, 00:23 (7 years ago) parentf23e84c
3 files changed+95−58
src/Data/Tasks/Build.hs+64−29 src/Server.hs+26−21 src/Server/Html.hs+5−8
src/Data/Tasks/Build.hs+64−29
@@ -8,6 +8,7 @@ module Data.Tasks.Build
88where
99
1010import Colog
11+import Control.Concurrent
1112import Control.GithubCloner
1213import Control.Monad.Except
1314import Control.Monad.IO.Unlift
@@ -30,6 +31,7 @@ import System.Directory
3031import System.Exit
3132import System.Process.Typed
3233import System.Random
34+import UnliftIO.Async
3335import UnliftIO.Exception
3436
3537data Build
@@ -38,7 +40,8 @@ data Build
3840 dockerFile :: String,
3941 owner :: Name Owner,
4042 repoName :: Name Repo,
41− sha :: Name Commit
43+ sha :: Name Commit,
44+ timeoutMinutes :: Int
4245 }
4346 deriving (Eq, Show, Generic, FromJSON, ToJSON)
4447
@@ -46,9 +49,7 @@ instance Task Build "build-repo" where
4649 type TaskMonad Build m = (MonadUnliftIO m, GithubCloner m, StaticPQ m, HasDockerInfo m)
4750
4851 performTask Build {..} =
49− bracket createDir (liftIO . removePathForcibly) $ \dir -> do
50− let fullRepoName = T.unpack $ untagName owner <> "/" <> untagName repoName
51− sha' = T.unpack $ untagName sha
52+ bracket createDir (liftIO . removePathForcibly) $ \dir -> flip catchAny (const $ return Failure) $ do
5253 cloneRepo fullRepoName sha' dir
5354 (shellCode, _, shellErr) <- readProcess . setWorkingDir dir $ shell preProcessShell
5455 case shellCode of
@@ -67,8 +68,16 @@ instance Task Build "build-repo" where
6768 h <- liftIO D.defaultHttpHandler
6869 dUrl <- getDockerUrl
6970 let opts = D.defaultCreateOpts imageName
70− runDocker :: forall a m. MonadIO m => D.DockerT IO a -> m a
71− runDocker = liftIO . D.runDockerT (D.defaultClientOpts {D.baseUrl = dUrl}, h)
71+ runDocker ::
72+ ( MonadIO m,
73+ Exception err,
74+ WithLog env Message m,
75+ StaticPQ m,
76+ MonadUnliftIO m
77+ ) =>
78+ D.DockerT IO (Either err a) ->
79+ m a
80+ runDocker = runEither <=< liftIO . D.runDockerT (D.defaultClientOpts {D.baseUrl = dUrl}, h)
7281 imageName = T.pack dir
7382 liftIO $ writeFile (dir <> "/Dockerfile") dockerFile
7483 absDir <- liftIO $ makeAbsolute dir
@@ -76,25 +85,37 @@ instance Task Build "build-repo" where
7685 D.waitContainer cid >>= \case
7786 Left D.GenericDockerError {} -> waitDockerForever cid
7887 a -> return a
79− dockerRes <- runDocker . runExceptT $ do
88+ cid <- runDocker . runExceptT $ do
8089 ExceptT $ D.buildImageFromDockerfile (D.defaultBuildOpts imageName) absDir
81− cid <- ExceptT $ D.createContainer opts Nothing
82− ExceptT $ D.startContainer D.defaultStartOpts cid
83− buildCode <- ExceptT $ waitDockerForever cid
84− buildOut <-
85− ExceptT $
86− D.getContainerLogs
87− D.LogOpts
88− { stdout = True,
89− stderr = False,
90− since = Nothing,
91− timestamps = False,
92− tail = D.All
93− }
94− cid
95− return (buildCode, buildOut)
90+ ExceptT $ D.createContainer opts Nothing
91+ dockerRes <- fmap (either id id)
92+ . race (liftIO (threadDelay (timeoutMinutes * 60000000)) >> return (ExitFailure 1, "Time out"))
93+ $ flip
94+ finally
95+ ( runDocker $
96+ D.deleteContainer
97+ D.ContainerDeleteOpts {deleteVolumes = True, force = True}
98+ cid
99+ )
100+ . runDocker
101+ . runExceptT
102+ $ do
103+ ExceptT $ D.startContainer D.defaultStartOpts cid
104+ buildCode <- ExceptT $ waitDockerForever cid
105+ buildOut <-
106+ ExceptT $
107+ D.getContainerLogs
108+ D.LogOpts
109+ { stdout = True,
110+ stderr = False,
111+ since = Nothing,
112+ timestamps = False,
113+ tail = D.All
114+ }
115+ cid
116+ return (buildCode, buildOut)
96117 case dockerRes of
97− Right (ExitFailure n, buildOut) -> do
118+ (ExitFailure n, buildOut) -> do
98119 let err = BS.unpack buildOut
99120 logError . T.pack $
100121 "test command for repo " <> show fullRepoName <> " at sha "
@@ -105,12 +126,7 @@ instance Task Build "build-repo" where
105126 <> err
106127 scheduleTask erroredTask
107128 updateSubmissionStatus fullRepoName sha' (SubmissionFailed err)
108− Left err' -> do
109− let err = show err'
110− logError . T.pack $ err
111− scheduleTask erroredTask
112− updateSubmissionStatus fullRepoName sha' (SubmissionFailed err)
113− Right (ExitSuccess, buildOut) ->
129+ (ExitSuccess, buildOut) ->
114130 case eitherDecode' (BS.dropWhile (/= '{') buildOut) of
115131 Left err' -> do
116132 let err = err' <> " " <> BS.unpack buildOut
@@ -148,6 +164,23 @@ instance Task Build "build-repo" where
148164 newStatusDescription = Just "Build failed",
149165 newStatusContext = Nothing
150166 }
167+ fullRepoName = T.unpack $ untagName owner <> "/" <> untagName repoName
168+ sha' = T.unpack $ untagName sha
169+ runEither ::
170+ ( Exception err,
171+ WithLog env Message m,
172+ StaticPQ m,
173+ MonadUnliftIO m
174+ ) =>
175+ Either err b ->
176+ m b
177+ runEither (Left err) = do
178+ let err' = show err
179+ logError $ T.pack err'
180+ scheduleTask erroredTask
181+ updateSubmissionStatus fullRepoName sha' (SubmissionFailed err')
182+ throwIO err
183+ runEither (Right a) = return a
151184
152185createDir :: MonadIO m => m FilePath
153186createDir = do
@@ -172,3 +205,5 @@ getTmpDirName = liftIO $ do
172205
173206class HasDockerInfo m where
174207 getDockerUrl :: m T.Text
208+
209+instance Exception D.DockerError
src/Server.hs+26−21
@@ -44,7 +44,7 @@ import Server.Schema
4444import Squeal.PostgreSQL
4545import System.IO
4646import Text.Blaze.Html
47−import UnliftIO.Exception (try)
47+import UnliftIO.Exception (catch, throwIO, try)
4848
4949type API =
5050 "github-webhook"
@@ -52,8 +52,8 @@ type API =
5252 :> GitHubSignedReqBody '[JSON] InstallationEvent
5353 :> Post '[JSON] ()
5454 :<|> GitHubEvent '[ 'WebhookPushEvent]
55− :> GitHubSignedReqBody '[JSON] PushEvent
56− :> Post '[JSON] ()
55+ :> GitHubSignedReqBody '[JSON] PushEvent
56+ :> Post '[JSON] ()
5757 )
5858 :<|> "submission"
5959 :> Capture "user name" String
@@ -78,7 +78,7 @@ pendingStatus :: NewStatus
7878pendingStatus = NewStatus StatusPending Nothing Nothing Nothing
7979
8080webhookPushEvent ::
81− (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m) =>
81+ (StaticPQ m, WithLog env Message m, MonadUnliftIO m, HasTasks m, MonadError ServerError m) =>
8282 RepoWebhookEvent ->
8383 ((), PushEvent) ->
8484 m ()
@@ -86,8 +86,10 @@ webhookPushEvent _ ((), ev) = do
8686 let repo = evPushRepository ev
8787 user = T.unpack . whUserLogin . evPushSender $ ev
8888 fullRepoName = whRepoFullName repo
89− [owner, repoName] = T.splitOn "/" fullRepoName
90− Just sha = evPushHeadSha ev
89+ (owner, repoName) <- case T.splitOn "/" fullRepoName of
90+ [a, b] -> return (a, b)
91+ _ -> throwError err422
92+ sha <- maybe (throwError err422) return $ evPushHeadSha ev
9193 tasks <- getTasks
9294 let task =
9395 M.lookupMax $
@@ -102,7 +104,7 @@ webhookPushEvent _ ((), ev) = do
102104 (T.unpack name)
103105 (T.unpack sha)
104106 (Jsonb BuildScheduled)
105− scheduleTask $ Build prebuild dockerfile (N owner) (N repoName) (N sha)
107+ scheduleTask $ Build prebuild dockerfile (N owner) (N repoName) (N sha) timeoutMinutes
106108 liftIO $ do
107109 print ev
108110 hFlush stdout
@@ -130,7 +132,7 @@ retestSubmission owner repoName sha = do
130132 Just (name, TaskConfig {..}) | T.take (T.length name) repoName' == name -> do
131133 scheduleTask $ StatusUpdate (N owner') (N repoName') (N sha') pendingStatus
132134 updateSubmissionStatus fullRepoName sha BuildScheduled
133− scheduleTask $ Build prebuild dockerfile (N owner') (N repoName') (N sha')
135+ scheduleTask $ Build prebuild dockerfile (N owner') (N repoName') (N sha') timeoutMinutes
134136 _ -> return ()
135137 redirectToSubmission owner repoName sha
136138
@@ -148,6 +150,10 @@ newtype ServerM (schema :: SchemasType) a
148150 MonadUnliftIO
149151 )
150152
153+instance MonadError ServerError (ServerM schema) where
154+ throwError = throwIO
155+ catchError = catch
156+
151157server :: ServerT API (ServerM Schema)
152158server = (webhookInstallation :<|> webhookPushEvent) :<|> getSubmissionR :<|> retestSubmission :<|> getResults
153159
@@ -164,15 +170,16 @@ runServer = do
164170 let context =
165171 gitPolyHubKey (return webhookSecret)
166172 :. EmptyContext
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− }
173+ serverData =
174+ ServerData
175+ { githubAppAuth = auth,
176+ githubUserName = GithubUserName githubUsername,
177+ githubAccessToken = GithubAccessToken personalAccessToken,
178+ tasks = ts,
179+ logger = cfilter ((logSeverity <=) . msgSeverity) richMessageAction,
180+ baseUrl = baseSiteUrl,
181+ dockerUrl = T.pack cfgDockerUrl
182+ }
176183 repeatIfNotEmpty n f = f >>= \m -> do
177184 when (m == 0) $ liftIO $ threadDelay n
178185 repeatIfNotEmpty n f
@@ -221,7 +228,6 @@ instance HasContextEntry '[PolyGitHubKey] (GitHubKey result) where
221228 getContextEntry (PolyGitHubKey x :. _) = x
222229
223230instance MonadReader (ServerData n) m => GithubCloner m where
224−
225231 getGithubAccessToken = asks githubAccessToken
226232
227233 getGithubUserName = asks githubUserName
@@ -245,8 +251,8 @@ type TasksConfig = Map T.Text TaskConfig
245251data TaskConfig
246252 = TaskConfig
247253 { prebuild :: String,
248− dockerfile :: String
249− -- build :: T.Text
254+ dockerfile :: String,
255+ timeoutMinutes :: Int
250256 }
251257 deriving stock (Show, Eq, Generic)
252258 deriving anyclass (FromJSON, ToJSON)
@@ -255,7 +261,6 @@ class HasTasks m where
255261 getTasks :: m TasksConfig
256262
257263instance HasLog (ServerData m) Message m where
258−
259264 getLogAction :: ServerData m -> LogAction m Message
260265 getLogAction = logger
261266 {-# INLINE getLogAction #-}
src/Server/Html.hs+5−8
@@ -1,3 +1,5 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
13module Server.Html
24 ( getSubmissionR,
35 MonadHasBaseUrl (..),
@@ -8,17 +10,12 @@ where
810
911import Clay as C
1012import Colog
11−import Control.Monad.IO.Class
1213import Control.Monad.IO.Unlift
13−import Data.Aeson hiding (Success)
14−import qualified Data.ByteString.Lazy.Char8 as BS
1514import Data.Foldable
1615import qualified Data.Map as M
1716import Data.String
1817import Data.Submission
1918import Data.Submission.Query
20−import qualified Data.Text as T
21−import GHC.Generics
2219import Server.Schema
2320import Squeal.PostgreSQL (Jsonb (..))
2421import Text.Blaze
@@ -32,12 +29,12 @@ getSubmissionR ::
3229 String ->
3330 String ->
3431 m Markup
35−getSubmissionR user repo sha = do
36− restartUrl <- (<> "/restart") <$> getUrl (user <> "/" <> repo) sha
32+getSubmissionR user repo sha' = do
33+ restartUrl <- (<> "/restart") <$> getUrl (user <> "/" <> repo) sha'
3734 let restartBuild = do
3835 H.form H.! A.action (fromString restartUrl) H.! A.method "post" $
3936 H.input H.! A.type_ "submit" H.! A.value "Restart test"
40− (titleText, inner) <- getSubmission (user <> "/" <> repo) sha >>= \case
37+ (titleText, inner) <- getSubmission (user <> "/" <> repo) sha' >>= \case
4138 Nothing ->
4239 return . ("Not found",) $
4340 H.h1 "404. not found. go away."