packages feed

mudbath-0.0.2: Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Snap.Core
import           Snap.Http.Server

import           Crypto.Hash

import           Data.Monoid
import           Data.Aeson hiding (Success, Error)
import           Data.Aeson.Types (parseMaybe)
import           Data.Text.Encoding
import           Data.Maybe
import qualified Data.ByteString.Lazy  as LBS
import qualified Data.ByteString.Char8 as BC8

import           Control.Applicative
import           Control.Concurrent (forkIO)
import           Control.Concurrent.STM
import           Control.Monad
import           Control.Monad.IO.Class
import           Control.Exception

import           System.Environment

import           GitHub.Types
import           Executor
import           Notifications



main :: IO ()
main = do

    hookPath <- fromMaybe "webhook" <$> lookupEnv "HOOKPATH"
    mbSecretKey <- lookupEnv "SECRET_TOKEN"
    queue <- newTQueueIO

    -- Fork the background build thead, Ignore all exceptions, because IO is
    -- dirty and can throw exceptions at any time. And we don't actually care
    -- about those, all we want is to keep the thread running. At any cost.

    void $ forkIO $ forever $
        backgroundBuildThread queue `catch` ignoreException

    quickHttpServe $
        path (BC8.pack hookPath) (method POST $ hook queue mbSecretKey) <|> writeText "ok\n"

  where

    backgroundBuildThread queue = do
        de <- atomically $ readTQueue queue
        processEvent de


    ignoreException :: SomeException -> IO ()
    ignoreException e = do
        putStrLn $ "ignoreException: " ++ show e



-- | The handler of the webhook. It parses the request body as an GitHub event
-- and processes it.
--
-- Some events are processed immediately, others are put into the queuer to
-- process them in the background thread.
hook :: TQueue Event -> Maybe String -> Snap ()
hook queue mbSecretKey = do
    hdrs <- headers <$> getRequest

    body <- do
        body <- readRequestBody (100 * 1000)
        case (mbSecretKey, getHeader "X-Hub-Signature" hdrs) of

            -- No secret key and no signature. Pass along the body unverified.
            (Nothing, Nothing) -> return body

            -- Signature is available but no secret key to verify it. Log this
            -- and accept the body.
            (Nothing, Just _) -> do
                liftIO $ putStrLn $
                    "Ignoring signature because the secret token was not provided"
                return body

            -- Secret token is available but the request is not signed. Reject
            -- the request.
            (Just _, Nothing) -> do
                writeText "Signature missing"
                res <- getResponse
                finishWith $ setResponseCode 400 res

            -- Both the signature and secret token are available. Verify the
            -- signature and reject the request if that fails.
            (Just sc, Just sig) -> do
                let mac = hmac (BC8.pack sc) (LBS.toStrict body) :: HMAC SHA1
                if sig == ("sha1=" <> digestToHexByteString (hmacGetDigest mac))
                    then return body
                    else do
                        liftIO $ do
                            putStrLn "Signature does not match:"
                            print $ show sig
                            print $ digestToHexByteString $ hmacGetDigest mac

                        writeText "Signature does not match"
                        res <- getResponse
                        finishWith $ setResponseCode 400 res


    let mbEvent = do
        eventName <- getHeader "X-GitHub-Event" hdrs
        value     <- decode body
        parseMaybe (eventParser $ decodeUtf8 eventName) value

    case mbEvent of
        Nothing -> return ()
        Just ev -> void $ liftIO $ case ev of
            (DeploymentEventType _) -> atomically $ writeTQueue queue ev
            _                       -> processEvent ev



processEvent :: Event -> IO ()
processEvent (DeploymentEventType de) =
    executeDeploymentScript de

processEvent (DeploymentStatusEventType dse) =
    notifyDeploymentStatusChange dse

processEvent _ =
    return ()