@@ -11,12 +11,12 @@ import qualified Data.Text as T
1111import qualified Data.Text.Encoding as TE
1212import qualified Network.HTTP.Client as HTTP
1313import Network.HTTP.Client.TLS (tlsManagerSettings )
14- import System.Environment (getEnv )
14+ import System.Environment (getEnv , lookupEnv , setEnv )
1515import Network.HTTP.Types.Status (Status (.. ))
1616import Data.Aeson.Decoding (eitherDecode )
1717import qualified Data.Text as Text
1818import Utils (getCurrentCommit , logError , logDebug )
19- import Types (AppState )
19+ import Types (AppState ( .. ), GithubClient ( .. ) )
2020
2121-- Define the data types for the status update
2222data StatusRequest = StatusRequest
@@ -33,70 +33,105 @@ newtype InstallationTokenResponse = InstallationTokenResponse
3333 } deriving (Show , Generic )
3434 deriving anyclass (FromJSON )
3535
36- updateCommitStatus :: MonadIO m => AppState -> StatusRequest -> m ()
37- updateCommitStatus appState statusRequest = liftIO do
36+ getClient :: AppState -> IO GithubClient
37+ getClient appState = do
38+ mClient <- readIORef appState. githubClient
39+ case mClient of
40+ Just client -> pure client
41+ Nothing -> do
42+ client <- initClient appState
43+ writeIORef appState. githubClient $ Just client
44+ pure client
45+
46+ initClient :: AppState -> IO GithubClient
47+ initClient appState = do
3848 -- Load environment variables
49+ apiUrl <- fromMaybe " https://api.github.com" <$> lookupEnv " GITHUB_API_URL"
3950 appId <- getEnv " GITHUB_APP_ID"
4051 installationId <- getEnv " GITHUB_INSTALLATION_ID"
4152 privateKeyStr <- getEnv " GITHUB_APP_PRIVATE_KEY"
4253 owner <- getEnv " GITHUB_REPOSITORY_OWNER"
4354 repo <- getEnv " GITHUB_REPOSITORY"
44-
45- sha <- getCurrentCommit appState
46-
47- let privateKeyBytes = encodeUtf8 $ Text. replace " |" " \n " $ toText privateKeyStr
48- let privateKey = fromMaybe (error " Invalid github key" ) $ readRsaSecret privateKeyBytes
49-
50- -- Create the JWT token
51- now <- getPOSIXTime
52- let claims = mempty { iss = stringOrURI $ T. pack appId
53- , iat = numericDate now
54- , exp = numericDate (now + 5 * 60 )
55- }
56- let jwt = encodeSigned (EncodeRSAPrivateKey privateKey) (mempty { alg = Just RS256 }) claims
57-
5855 -- Prepare the HTTP manager
5956 manager <- HTTP. newManager tlsManagerSettings
6057
61- -- Get the installation access token
62- let installUrl = " https://api.github.com/app/installations/" ++ installationId ++ " /access_tokens"
63- initRequest <- HTTP. parseRequest installUrl
64- let request = initRequest
65- { HTTP. method = " POST"
66- , HTTP. requestHeaders =
67- [ (" Authorization" , " Bearer " <> TE. encodeUtf8 jwt)
68- , (" Accept" , " application/vnd.github.v3+json" )
69- , (" User-Agent" , " restaumatic-bot" )
70- ]
71- }
72- response <- HTTP. httpLbs request manager
73- let mTokenResponse = eitherDecode @ InstallationTokenResponse (HTTP. responseBody response)
74- case mTokenResponse of
75- Left err -> do
76- logError appState $ " CommitStatus: Failed to parse installation token response: " <> show err
77- logError appState $ " CommitStatus: Response: " <> decodeUtf8 response. responseBody
78- exitFailure
79- Right tokenResponse -> do
80- let accessToken = tokenResponse. token
58+ let createToken = do
59+ let privateKeyBytes = encodeUtf8 $ Text. replace " |" " \n " $ toText privateKeyStr
60+ let privateKey = fromMaybe (error " Invalid github key" ) $ readRsaSecret privateKeyBytes
61+
62+ -- Create the JWT token
63+ now <- getPOSIXTime
64+ let claims = mempty { iss = stringOrURI $ T. pack appId
65+ , iat = numericDate now
66+ , exp = numericDate (now + 5 * 60 )
67+ }
68+ let jwt = encodeSigned (EncodeRSAPrivateKey privateKey) (mempty { alg = Just RS256 }) claims
8169
82- -- Prepare the status update request
83- let statusUrl = " https://api.github.com/repos/ " ++ owner ++ " /" ++ repo ++ " /statuses/ " ++ toString sha
84- initStatusRequest <- HTTP. parseRequest statusUrl
85- let statusReq = initStatusRequest
70+ -- Get the installation access token
71+ let installUrl = apiUrl <> " /app/installations/ " ++ installationId ++ " /access_tokens "
72+ initRequest <- HTTP. parseRequest installUrl
73+ let request = initRequest
8674 { HTTP. method = " POST"
8775 , HTTP. requestHeaders =
88- [ (" Authorization" , " Bearer " <> TE. encodeUtf8 accessToken )
76+ [ (" Authorization" , " Bearer " <> TE. encodeUtf8 jwt )
8977 , (" Accept" , " application/vnd.github.v3+json" )
90- , (" Content-Type" , " application/json" )
9178 , (" User-Agent" , " restaumatic-bot" )
9279 ]
93- , HTTP. requestBody = HTTP. RequestBodyLBS $ encode statusRequest
9480 }
95- statusResponse <- HTTP. httpLbs statusReq manager
96- if statusResponse. responseStatus. statusCode == 201
97- then
98- logDebug appState " Commit status updated successfully"
99- else do
100- logError appState $ " CommitStatus: Failed to update commit status: " <> show statusResponse
101- logError appState $ " CommitStatus: Response: " <> decodeUtf8 response. responseBody
102- exitFailure
81+ response <- HTTP. httpLbs request manager
82+ let mTokenResponse = eitherDecode @ InstallationTokenResponse (HTTP. responseBody response)
83+ case mTokenResponse of
84+ Left err -> do
85+ logError appState $ " CommitStatus: Failed to parse installation token response: " <> show err
86+ logError appState $ " CommitStatus: Response: " <> decodeUtf8 response. responseBody
87+
88+ -- FIXME: handle the error better
89+ exitFailure
90+ Right tokenResponse ->
91+ pure tokenResponse. token
92+
93+ -- Try to read token from environment variable
94+ -- Otherwise generate a new one, and set env for future uses (also in child processes)
95+ accessToken <- lookupEnv " _taskrunner_github_access_token" >>= \ case
96+ Just token -> pure $ T. pack token
97+ Nothing -> do
98+ token <- createToken
99+ setEnv " _taskrunner_github_access_token" $ T. unpack token
100+ pure token
101+
102+ pure $ GithubClient { apiUrl = T. pack apiUrl
103+ , appId = T. pack appId
104+ , installationId = T. pack installationId
105+ , privateKey = T. pack privateKeyStr
106+ , owner = T. pack owner
107+ , repo = T. pack repo
108+ , manager = manager
109+ , accessToken = accessToken
110+ }
111+
112+ updateCommitStatus :: MonadIO m => AppState -> StatusRequest -> m ()
113+ updateCommitStatus appState statusRequest = liftIO do
114+ client <- getClient appState
115+ sha <- getCurrentCommit appState
116+
117+ -- Prepare the status update request
118+ let statusUrl = toString client. apiUrl <> " /repos/" ++ toString client. owner ++ " /" ++ toString client. repo ++ " /statuses/" ++ toString sha
119+ initStatusRequest <- HTTP. parseRequest statusUrl
120+ let statusReq = initStatusRequest
121+ { HTTP. method = " POST"
122+ , HTTP. requestHeaders =
123+ [ (" Authorization" , " Bearer " <> TE. encodeUtf8 client. accessToken)
124+ , (" Accept" , " application/vnd.github.v3+json" )
125+ , (" Content-Type" , " application/json" )
126+ , (" User-Agent" , " restaumatic-bot" )
127+ ]
128+ , HTTP. requestBody = HTTP. RequestBodyLBS $ encode statusRequest
129+ }
130+ statusResponse <- HTTP. httpLbs statusReq client. manager
131+ if statusResponse. responseStatus. statusCode == 201
132+ then
133+ logDebug appState " Commit status updated successfully"
134+ else do
135+ logError appState $ " CommitStatus: Failed to update commit status: " <> show statusResponse
136+ logError appState $ " CommitStatus: Response: " <> decodeUtf8 statusResponse. responseBody
137+ exitFailure
0 commit comments