Skip to content

Commit

Permalink
Merge pull request github#455 from nparry/gerrit-fixups
Browse files Browse the repository at this point in the history
Gerrit fixups
  • Loading branch information
tombell committed Jun 1, 2012
2 parents f9d8510 + e776362 commit 53b7fd6
Showing 1 changed file with 70 additions and 42 deletions.
112 changes: 70 additions & 42 deletions src/scripts/gerrit.coffee
Expand Up @@ -2,41 +2,50 @@
# Hubot has to be running as a user who has registered a SSH key with Gerrit.
#
# hubot gerrit search <query> - Search Gerrit for changes - the query should follow the normal Gerrit query rules.
# hubot gerrit ignore events for <project> - Don't spam about events happening for the specified project.
# hubot gerrit report events for <project> - Reset Hubot to spam about events happening for the specified project.
# hubot gerrit (ignore|report) events for (project|user|event) <thing> - Tell Hubot how to report Gerrit events.
#

cp = require "child_process"
url = require "url"

# Format JSON query result (see http://gerrit-documentation.googlecode.com/svn/Documentation/2.4/json.html#change)
formatQueryResult = (r) ->
updated = new Date(r.lastUpdated * 1000).toDateString()
"'#{r.subject}' for #{r.project}/#{r.branch} by #{r.owner.name} on #{updated}: #{r.url}"
# Required - The SSH URL for your Gerrit server.
sshUrl = process.env.HUBOT_GERRIT_SSH_URL || ""

# Format JSON event data (see http://gerrit-documentation.googlecode.com/svn/Documentation/2.4/cmd-stream-events.html)
formatEvent = (e) ->
switch e.type
when "patchset-created" then formatPatchsetEvent "New patchset", e.change, e.uploader
when "change-abandoned" then formatPatchsetEvent "Patchset abandoned", e.change, e.abandoner
when "change-restored" then formatPatchsetEvent "Patchset restored", e.change, e.restorer
when "change-merged" then formatPatchsetEvent "Patchset merged", e.change, e.submitter
when "comment-added" then formatPatchsetEvent "New comments", e.change, e.author
when "ref-updated" then "Ref updated - #{e.refUpdate.project}/#{e.refUpdate.refName} by #{e.submitter.name}"
else null
# Optional - A comma separated list of rooms to receive spam about Gerrit events.
# If not set, messages will be sent to all room of which Hubot is a member.
# To disable event stream spam, use the value "disabled"
eventStreamRooms = process.env.HUBOT_GERRIT_EVENTSTREAM_ROOMS

formatPatchsetEvent = (type, change, who) ->
"#{type} - '#{change.subject}' for #{change.project}/#{change.branch} by #{who.name}: #{change.url}"
# TODO: Make these template driven with env-var overrides possible.
# See the following for descriptions of the input JSON data:
# http://gerrit-documentation.googlecode.com/svn/Documentation/2.4/json.html
# http://gerrit-documentation.googlecode.com/svn/Documentation/2.4/cmd-stream-events.html
formatters =
queryResult: (json) -> "'#{json.change.subject}' for #{json.change.project}/#{json.change.branch} by #{extractName json.change} on #{formatDate json.change.lastUpdated}: #{json.change.url}"
events:
"patchset-created": (json) -> "#{extractName json} uploaded patchset #{json.patchSet.number} of '#{json.change.subject}' for #{json.change.project}/#{json.change.branch}: #{json.change.url}"
"change-abandoned": (json) -> "#{extractName json} abandoned '#{json.change.subject}' for #{json.change.project}/#{json.change.branch}: #{json.change.url}"
"change-restored": (json) -> "#{extractName json} restored '#{json.change.subject}' for #{json.change.project}/#{json.change.branch}: #{json.change.url}"
"change-merged": (json) -> "#{extractName json} merged patchset #{json.patchSet.number} of '#{json.change.subject}' for #{json.change.project}/#{json.change.branch}: #{json.change.url}"
"comment-added": (json) -> "#{extractName json} reviewed patchset #{json.patchSet.number} (#{extractReviews json}) of '#{json.change.subject}' for #{json.change.project}/#{json.change.branch}: #{json.change.url}"
"ref-updated": (json) -> "#{extractName json} updated reference #{json.refUpdate.project}/#{json.refUpdate.refName}"

formatDate = (seconds) -> new Date(seconds * 1000).toDateString()
extractName = (json) ->
account = json.uploader || json.abandoner || json.restorer || json.submitter || json.author || json.owner
account?.name || account?.email || "Gerrit"
extractReviews = (json) ->
("#{a.description}=#{a.value}" for a in json.approvals).join ","

module.exports = (robot) ->
gerrit = url.parse process.env.HUBOT_GERRIT_SSH_URL || ""
gerrit = url.parse sshUrl
gerrit.port = 22 unless gerrit.port
if gerrit.protocol != "ssh:" || gerrit.hostname == ""
robot.logger.error "Gerrit commands inactive because HUBOT_GERRIT_SSH_URL=#{gerrit.href} is not a valid SSH URL"
else
eventStreamMe robot, gerrit
eventStreamMe robot, gerrit unless eventStreamRooms == "disabled"
robot.respond /gerrit (?:search|query)(?: me)? (.+)/i, searchMe robot, gerrit
robot.respond /gerrit (ignore|report)(?: me)? events for (.+)/i, ignoreOrReportEventsMe robot, gerrit
robot.respond /gerrit (ignore|report)(?: me)? events for (project|user|event) (.+)/i, ignoreOrReportEventsMe robot, gerrit

searchMe = (robot, gerrit) -> (msg) ->
cp.exec "ssh #{gerrit.hostname} -p #{gerrit.port} gerrit query --format=JSON #{msg.match[1]}", (err, stdout, stderr) ->
Expand All @@ -50,17 +59,19 @@ searchMe = (robot, gerrit) -> (msg) ->
else if status.rowCount == 0
msg.send "Gerrit didn't find anything matching your query"
else
msg.send formatQueryResult r for r in results when r.id
msg.send formatters.queryResult change: r for r in results when r.id

ignoreOrReportEventsMe = (robot, gerrit) -> (msg) ->
project = msg.match[2]
ignoredProjects = (p for p in (robot.brain.data.gerrit?.eventStream?.ignoredProjects || []) when p isnt project)
ignoredProjects.push project if msg.match[1] == "ignore"
type = msg.match[2].toLowerCase()
thing = msg.match[3]
ignores = (t for t in ignoresOfType robot, type when t isnt thing)
ignores.push thing if msg.match[1] == "ignore"

robot.brain.data.gerrit ?= { }
robot.brain.data.gerrit.eventStream ?= { }
robot.brain.data.gerrit.eventStream.ignoredProjects = ignoredProjects
msg.send "Got it, the updated list of Gerrit projects to ignore is #{ignoredProjects.join(', ') || 'empty'}"
robot.brain.data.gerrit.eventStream.ignores ?= { }
robot.brain.data.gerrit.eventStream.ignores[type] = ignores
msg.send "Got it, the updated list of Gerrit #{type}s to ignore is #{ignores.join(', ') || 'empty'}"

eventStreamMe = (robot, gerrit) ->
robot.logger.info "Gerrit stream-events: Starting connection"
Expand All @@ -76,25 +87,42 @@ eventStreamMe = (robot, gerrit) ->
robot.logger.info "Gerrit stream-events: Connection lost (rc=#{code})"
reconnect = setTimeout (-> eventStreamMe robot, gerrit), 10 * 1000 unless done

projectFor = (json) -> (json.change || json.refUpdate).project
isWanted = (project) -> (p for p in (robot.brain.data.gerrit?.eventStream?.ignoredProjects || []) when p is project).length == 0
isIgnored = (type, thing) -> (t for t in ignoresOfType robot, type when t is thing).length != 0
isWanted = (event) -> !(
isIgnored("project", (event.change || event.refUpdate).project) ||
isIgnored("user", extractName event) ||
isIgnored("event", event.type))

streamEvents.stderr.on "data", (data) ->
robot.logger.info "Gerrit stream-events: #{data}"
streamEvents.stdout.on "data", (data) ->
json = JSON.parse data
msg = formatEvent json
if msg == null
robot.logger.debug "Gerrit stream-events: #{data}"
json = try
JSON.parse data
catch error
robot.logger.error "Gerrit stream-events: Error parsing Gerrit JSON. Error=#{error}, Event=#{data}"
null
return unless json
formatter = formatters.events[json.type]
msg = try
formatter json if formatter
catch error
robot.logger.error "Gerrit stream-events: Error formatting event. Error=#{error}, Event=#{data}"
null
if formatter == null
robot.logger.info "Gerrit stream-events: Unrecognized event #{data}"
else if msg && isWanted projectFor json
robot.messageRoom room, "Gerrit: #{msg}" for room in robotRooms robot
else if msg && isWanted json
# Bug in messageRoom? Doesn't work with multiple rooms
#robot.messageRoom room, "Gerrit: #{msg}" for room in robotRooms robot
robot.send room: room, "Gerrit: #{msg}" for room in robotRooms robot

ignoresOfType = (robot, type) -> robot.brain.data.gerrit?.eventStream?.ignores?[type] || []

# So this is kind of terrible - not sure of a better way to do this for now
robotRooms = (robot) ->
roomlistish = /^HUBOT_.+_ROOMS/i
roomlists = (v for k,v of process.env when roomlistish.exec(k) isnt null)
if roomlists.length != 0
roomlists[0].split(",")
else
robot.logger.error "Gerrit stream-events: Unable to determine the list of rooms"
[ "dummy" ]
roomlists =
if eventStreamRooms
[ eventStreamRooms ]
else
v for k,v of process.env when /^HUBOT_.+_ROOMS/i.exec(k) isnt null
robot.logger.error "Gerrit stream-events: Unable to determine the list of rooms" if roomlists.length == 0
r for r in (roomlists[0] || "").split "," when r isnt ""

0 comments on commit 53b7fd6

Please sign in to comment.