Add support for installing as a GitHub app. Part of #1
This commit is contained in:
parent
abb544feed
commit
6bc6142fad
|
@ -1192,6 +1192,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jwt-simple": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-eeoBiRth3mto4T5nwLS1vak3spQ="
|
||||||
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
"hubot-rules": "^0.1.2",
|
"hubot-rules": "^0.1.2",
|
||||||
"hubot-scripts": "^2.17.2",
|
"hubot-scripts": "^2.17.2",
|
||||||
"hubot-shipit": "^0.2.1",
|
"hubot-shipit": "^0.2.1",
|
||||||
"hubot-slack": "^4.4.0"
|
"hubot-slack": "^4.4.0",
|
||||||
|
"jwt-simple": "^0.5.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "0.10.x"
|
"node": "0.10.x"
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
# Description:
|
# Description:
|
||||||
# Script that listens to new GitHub pull requests
|
# Script that listens to new GitHub pull requests
|
||||||
# and assigns them to the REVIEW column on the "Pipeline for QA" project
|
# and assigns them to the REVIEW column on the "Pipeline for QA" project
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# github: "^13.1.0"
|
||||||
|
# hubot-github-webhook-listener: "^0.9.1"
|
||||||
|
# hubot-slack: "^4.4.0"
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# The hard-coded names for the project board and review column are just below.
|
||||||
|
# These could be read from a config file (e.g. YAML)
|
||||||
|
# TODO: Rewrite this file with ES6 to benefit from async/await
|
||||||
|
#
|
||||||
|
# Author:
|
||||||
|
# PombeirP
|
||||||
|
|
||||||
projectBoardName = "Pipeline for QA"
|
projectBoardName = "Pipeline for QA"
|
||||||
reviewColumnName = "REVIEW"
|
reviewColumnName = "REVIEW"
|
||||||
|
@ -8,32 +21,32 @@ notifyRoomName = "core"
|
||||||
|
|
||||||
module.exports = (robot) ->
|
module.exports = (robot) ->
|
||||||
|
|
||||||
context = require("./github-context.coffee")
|
context = require('./github-context.coffee')
|
||||||
|
|
||||||
robot.on "github-repo-event", (repo_event) ->
|
robot.on "github-repo-event", (repo_event) ->
|
||||||
githubPayload = repo_event.payload
|
githubPayload = repo_event.payload
|
||||||
|
|
||||||
switch(repo_event.eventType)
|
switch(repo_event.eventType)
|
||||||
when "pull_request"
|
when "pull_request"
|
||||||
|
context.initialize(robot, robot.brain.get "github-app_id")
|
||||||
# Make sure we don't listen to our own messages
|
# Make sure we don't listen to our own messages
|
||||||
return if context.equalsRobotName(robot, githubPayload.pull_request.user.login)
|
return if context.equalsRobotName(robot, githubPayload.pull_request.user.login)
|
||||||
return console.error "No Github token provided to Hubot" unless process.env.HUBOT_GITHUB_TOKEN
|
|
||||||
|
|
||||||
action = githubPayload.action
|
action = githubPayload.action
|
||||||
if action == "opened"
|
if action == "opened"
|
||||||
# A new PR was opened
|
# A new PR was opened
|
||||||
context.initialize()
|
assignPullRequestToReview context.github(), githubPayload, robot
|
||||||
|
|
||||||
assignPullRequestToReview context.github, githubPayload, robot
|
|
||||||
|
|
||||||
assignPullRequestToReview = (github, githubPayload, robot) ->
|
assignPullRequestToReview = (github, githubPayload, robot) ->
|
||||||
ownerName = githubPayload.repository.owner.login
|
ownerName = githubPayload.repository.owner.login
|
||||||
repoName = githubPayload.repository.name
|
repoName = githubPayload.repository.name
|
||||||
prNumber = githubPayload.pull_request.number
|
prNumber = githubPayload.pull_request.number
|
||||||
robot.logger.info "assignPullRequestToReview - Handling Pull Request ##{prNumber} on repo #{ownerName}/#{repoName}"
|
robot.logger.info "assignPullRequestToReview - " +
|
||||||
|
"Handling Pull Request ##{prNumber} on repo #{ownerName}/#{repoName}"
|
||||||
|
|
||||||
# Fetch repo projects
|
# Fetch repo projects
|
||||||
# TODO: The repo project and project column info should be cached in order to improve performance and reduce roundtrips
|
# TODO: The repo project and project column info should be cached
|
||||||
|
# in order to improve performance and reduce roundtrips
|
||||||
github.projects.getRepoProjects {
|
github.projects.getRepoProjects {
|
||||||
owner: ownerName,
|
owner: ownerName,
|
||||||
repo: repoName,
|
repo: repoName,
|
||||||
|
@ -86,7 +99,6 @@ assignPullRequestToReview = (github, githubPayload, robot) ->
|
||||||
"Moved PR #{githubPayload.pull_request.number} to " +
|
"Moved PR #{githubPayload.pull_request.number} to " +
|
||||||
"#{reviewColumnName} in #{projectBoardName} project"
|
"#{reviewColumnName} in #{projectBoardName} project"
|
||||||
|
|
||||||
|
|
||||||
findProject = (projects, name) ->
|
findProject = (projects, name) ->
|
||||||
for idx, project of projects
|
for idx, project of projects
|
||||||
return project if project.name == name
|
return project if project.name == name
|
||||||
|
|
|
@ -1,27 +1,72 @@
|
||||||
# Description:
|
# Description:
|
||||||
# Script that keeps GitHub-related context to be shared among scripts
|
# Script that keeps GitHub-related context to be shared among scripts
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# github: "^13.1.0"
|
||||||
|
# jwt-simple: "^0.5.1"
|
||||||
|
#
|
||||||
|
# Author:
|
||||||
|
# PombeirP
|
||||||
|
|
||||||
GitHubApi = require("github")
|
GitHubApi = require('github')
|
||||||
|
|
||||||
RegExp cachedRobotNameRegex = null
|
RegExp cachedRobotNameRegex = null
|
||||||
initialized = false
|
initialized = false
|
||||||
github = new GitHubApi { version: "3.0.0" }
|
githubAPI = new GitHubApi { version: "3.0.0" }
|
||||||
|
|
||||||
module.exports.github = github
|
module.exports = {
|
||||||
|
|
||||||
module.exports.initialize = ->
|
github: -> githubAPI
|
||||||
|
|
||||||
|
initialize: (robot, integrationID) ->
|
||||||
return if initialized
|
return if initialized
|
||||||
|
|
||||||
|
token = robot.brain.get('github-token')
|
||||||
|
if token
|
||||||
initialized = true
|
initialized = true
|
||||||
github.authenticate({
|
process.env.HUBOT_GITHUB_TOKEN = token
|
||||||
type: "token",
|
robot.logger.debug "Reused cached GitHub token"
|
||||||
token: process.env.HUBOT_GITHUB_TOKEN
|
githubAPI.authenticate({ type: 'token', token: token })
|
||||||
|
return
|
||||||
|
|
||||||
|
pemFilePath = './status-github-bot.pem'
|
||||||
|
|
||||||
|
jwt = require('jwt-simple')
|
||||||
|
|
||||||
|
# Private key contents
|
||||||
|
privateKey = ''
|
||||||
|
try
|
||||||
|
fs = require('fs')
|
||||||
|
privateKey = fs.readFileSync pemFilePath
|
||||||
|
catch err
|
||||||
|
robot.logger.error "Couldn't read #{pemFilePath} file contents: #{err}"
|
||||||
|
return
|
||||||
|
|
||||||
|
now = Math.round(Date.now() / 1000)
|
||||||
|
# Generate the JWT
|
||||||
|
payload = {
|
||||||
|
# issued at time
|
||||||
|
iat: now,
|
||||||
|
# JWT expiration time (10 minute maximum)
|
||||||
|
exp: now + (1 * 60),
|
||||||
|
# GitHub App's identifier
|
||||||
|
iss: integrationID
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt = jwt.encode(payload, privateKey, 'RS256')
|
||||||
|
githubAPI.authenticate({
|
||||||
|
type: 'integration',
|
||||||
|
token: jwt
|
||||||
})
|
})
|
||||||
|
robot.logger.debug "Configured integration authentication with JWT", jwt
|
||||||
|
|
||||||
module.exports.equalsRobotName = (robot, str) ->
|
initialized = true
|
||||||
return module.exports.getRegexForRobotName(robot).test(str)
|
|
||||||
|
|
||||||
module.exports.getRegexForRobotName = (robot) ->
|
equalsRobotName: (robot, str) ->
|
||||||
|
return getRegexForRobotName(robot).test(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegexForRobotName = (robot) ->
|
||||||
# This comes straight out of Hubot's Robot.coffee
|
# This comes straight out of Hubot's Robot.coffee
|
||||||
# - they didn't get a nice way of extracting that method though
|
# - they didn't get a nice way of extracting that method though
|
||||||
if !cachedRobotNameRegex
|
if !cachedRobotNameRegex
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Description:
|
||||||
|
# Script that handles the installation of the GitHub app
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# github: "^13.1.0"
|
||||||
|
# hubot-github-webhook-listener: "^0.9.1"
|
||||||
|
#
|
||||||
|
# Author:
|
||||||
|
# PombeirP
|
||||||
|
|
||||||
|
module.exports = (robot) ->
|
||||||
|
|
||||||
|
context = require('./github-context.coffee')
|
||||||
|
|
||||||
|
robot.on "github-repo-event", (repo_event) ->
|
||||||
|
githubPayload = repo_event.payload
|
||||||
|
|
||||||
|
switch(repo_event.eventType)
|
||||||
|
when "integration_installation"
|
||||||
|
# Make sure we don't listen to our own messages
|
||||||
|
return if context.equalsRobotName(robot, githubPayload.sender.login)
|
||||||
|
|
||||||
|
action = githubPayload.action
|
||||||
|
switch action
|
||||||
|
when "created"
|
||||||
|
# App was installed on an organization
|
||||||
|
robot.logger.info "Initializing installation for app with ID " +
|
||||||
|
"#{githubPayload.installation.app_id} and " +
|
||||||
|
"installation ID #{githubPayload.installation.id}"
|
||||||
|
|
||||||
|
robot.brain.set 'github-app_install-payload', JSON.stringify(githubPayload)
|
||||||
|
robot.brain.set 'github-app_id', githubPayload.installation.app_id
|
||||||
|
robot.brain.set 'github-app_repositories',
|
||||||
|
(x.full_name for x in githubPayload.repositories).join()
|
||||||
|
|
||||||
|
context.initialize(robot, githubPayload.installation.app_id)
|
||||||
|
|
||||||
|
perms = githubPayload.installation.permissions
|
||||||
|
robot.logger.error formatPermMessage('repository_projects', 'write') unless perms.repository_projects == 'write'
|
||||||
|
robot.logger.error formatPermMessage('metadata', 'read') unless perms.metadata == 'read'
|
||||||
|
robot.logger.error formatPermMessage('issues', 'read') unless perms.issues == 'read'
|
||||||
|
robot.logger.error formatPermMessage('pull_requests', 'write') unless perms.pull_requests == 'write'
|
||||||
|
|
||||||
|
robot.logger.error "Please enable 'pull_request' events " +
|
||||||
|
"in the app configuration on github.com" unless 'pull_request' in githubPayload.installation.events
|
||||||
|
|
||||||
|
createAccessToken robot, context.github(), githubPayload.installation.id
|
||||||
|
when "deleted"
|
||||||
|
# App was uninstalled from an organization
|
||||||
|
robot.logger.info "Removing installation for app " +
|
||||||
|
"with ID #{githubPayload.installation.app_id} and " +
|
||||||
|
"installation ID #{githubPayload.installation.id}"
|
||||||
|
|
||||||
|
robot.brain.set 'github-app_id', null
|
||||||
|
robot.brain.set 'github-app_install-payload', null
|
||||||
|
robot.brain.set 'github-app_repositories', null
|
||||||
|
robot.brain.set 'github-token', null
|
||||||
|
process.env.HUBOT_GITHUB_TOKEN = null
|
||||||
|
|
||||||
|
createAccessToken = (robot, github, id) ->
|
||||||
|
github.apps.createInstallationToken { installation_id: id }, (err, response) ->
|
||||||
|
if err
|
||||||
|
robot.logger.error "Couldn't create installation token: #{err}", id
|
||||||
|
return
|
||||||
|
|
||||||
|
console.error response.data.token
|
||||||
|
robot.brain.set 'github-token', response.data.token
|
||||||
|
# TODO: Set Redis expiration date to value from response.data.expires_at
|
||||||
|
process.env.HUBOT_GITHUB_TOKEN = response.data.token
|
||||||
|
github.authenticate({
|
||||||
|
type: 'token',
|
||||||
|
token: response.data.token
|
||||||
|
})
|
||||||
|
|
||||||
|
formatPermMessage = (permName, perm) ->
|
||||||
|
"Please enable '#{permName}' #{perm} permission in the app configuration on github.com"
|
|
@ -1,33 +1,43 @@
|
||||||
# Description:
|
# Description:
|
||||||
# Script that listens to new GitHub pull requests
|
# Script that listens to new GitHub pull requests
|
||||||
# and assigns them to the REVIEW column on the "Pipeline for QA" project
|
# and greets the user if it is their first PR on the repo
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# github: "^13.1.0"
|
||||||
|
# hubot-github-webhook-listener: "^0.9.1"
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# TODO: Rewrite this file with ES6 to benefit from async/await
|
||||||
|
#
|
||||||
|
# Author:
|
||||||
|
# PombeirP
|
||||||
|
|
||||||
module.exports = (robot) ->
|
module.exports = (robot) ->
|
||||||
|
|
||||||
context = require("./github-context.coffee")
|
context = require('./github-context.coffee')
|
||||||
|
|
||||||
robot.on "github-repo-event", (repo_event) ->
|
robot.on "github-repo-event", (repo_event) ->
|
||||||
githubPayload = repo_event.payload
|
githubPayload = repo_event.payload
|
||||||
|
|
||||||
switch(repo_event.eventType)
|
switch(repo_event.eventType)
|
||||||
when "pull_request"
|
when "pull_request"
|
||||||
|
context.initialize(robot, robot.brain.get "github-app_id")
|
||||||
# Make sure we don't listen to our own messages
|
# Make sure we don't listen to our own messages
|
||||||
return if context.equalsRobotName(robot, githubPayload.pull_request.user.login)
|
return if context.equalsRobotName(robot, githubPayload.pull_request.user.login)
|
||||||
return console.error "No Github token provided to Hubot" unless process.env.HUBOT_GITHUB_TOKEN
|
|
||||||
|
|
||||||
action = githubPayload.action
|
action = githubPayload.action
|
||||||
if action == "opened"
|
if action == "opened"
|
||||||
# A new PR was opened
|
# A new PR was opened
|
||||||
context.initialize()
|
greetNewContributor context.github(), githubPayload, robot
|
||||||
|
|
||||||
greetNewContributor context.github, githubPayload, robot
|
|
||||||
|
|
||||||
greetNewContributor = (github, githubPayload, robot) ->
|
greetNewContributor = (github, githubPayload, robot) ->
|
||||||
welcomeMessage = "Thanks for making your first PR here!" # TODO: Read the welcome message from a (per-repo?) file (e.g. status-react.welcome-msg.md)
|
# TODO: Read the welcome message from a (per-repo?) file (e.g. status-react.welcome-msg.md)
|
||||||
|
welcomeMessage = "Thanks for making your first PR here!"
|
||||||
ownerName = githubPayload.repository.owner.login
|
ownerName = githubPayload.repository.owner.login
|
||||||
repoName = githubPayload.repository.name
|
repoName = githubPayload.repository.name
|
||||||
prNumber = githubPayload.pull_request.number
|
prNumber = githubPayload.pull_request.number
|
||||||
robot.logger.info "greetNewContributor - Handling Pull Request ##{prNumber} on repo #{ownerName}/#{repoName}"
|
robot.logger.info "greetNewContributor - " +
|
||||||
|
"Handling Pull Request ##{prNumber} on repo #{ownerName}/#{repoName}"
|
||||||
|
|
||||||
github.issues.getForRepo {
|
github.issues.getForRepo {
|
||||||
owner: ownerName,
|
owner: ownerName,
|
||||||
|
@ -49,7 +59,11 @@ greetNewContributor = (github, githubPayload, robot) ->
|
||||||
body: welcomeMessage
|
body: welcomeMessage
|
||||||
}, (err, result) ->
|
}, (err, result) ->
|
||||||
if err
|
if err
|
||||||
robot.logger.error "Couldn't fetch the github projects for repo: #{err}", ownerName, repoName unless err.code == 404
|
robot.logger.error("Couldn't fetch the github projects for repo: #{err}",
|
||||||
|
ownerName, repoName) unless err.code == 404
|
||||||
|
return
|
||||||
robot.logger.info "Commented on PR with welcome message", ownerName, repoName
|
robot.logger.info "Commented on PR with welcome message", ownerName, repoName
|
||||||
else
|
else
|
||||||
robot.logger.debug "This is not the user's first PR on the repo, ignoring", ownerName, repoName, githubPayload.pull_request.user.login
|
robot.logger.debug(
|
||||||
|
"This is not the user's first PR on the repo, ignoring",
|
||||||
|
ownerName, repoName, githubPayload.pull_request.user.login)
|
Loading…
Reference in New Issue