{"action":"created","comment":{"url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/comments/61488749","id":61488749,"diff_hunk":"@@ -0,0 +1,330 @@\n+import assert from 'assert';\n+import events from 'events';\n+import slugid from 'slugid';\n+import taskcluster from 'taskcluster-client';\n+import parseRoute from './util/route_parser';\n+\n+const TASK_TREEHERDER_SCHEMA = 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/task-treeherder-config.json#';\n+\n+function stateFromRun(run) {\n+  switch (run.state) {\n+    case 'exception':\n+    case 'failed':\n+      return 'completed';\n+    default:\n+      return run.state;\n+  }\n+}\n+\n+function resultFromRun(run) {\n+  switch (run.state) {\n+    case 'completed':\n+      return 'success';\n+    case 'failed':\n+      return 'fail';\n+    case 'exception':\n+      if (run.reasonResolved === 'canceled') {\n+        return 'canceled';\n+      }\n+\n+      return 'exception';\n+    default:\n+      return 'unknown';\n+  }\n+}\n+\n+function taskIsRerun(runId, run) {\n+  // TODO: add array includes plugin to tc-rules and use here instead of indexOf\n+  return runId > 0 && ['rerun', 'retry'].indexOf(run.reasonCreated) !== -1;\n+}\n+\n+// Creates a log entry for Treeherder to retrieve and parse.  This log is\n+// displayed on the Treeherder Log Viewer once parsed.\n+function createLogReference(queue, taskId, run) {\n+  let logUrl = queue.buildUrl(\n+    queue.getArtifact, taskId, run.runId, 'public/logs/live_backing.log'\n+  );\n+\n+  return {\n+    // XXX: This is a magical name see 1147958 which enables the log viewer.\n+    name: 'builds-4h',\n+    url: logUrl\n+  };\n+}\n+\n+// Filters the task routes for the treeherder specific route.  Once found,\n+// the route is parsed into distinct parts used for constructing the\n+// Treeherder job message.\n+function parseRouteInfo(prefix, taskId, routes) {\n+  let route = routes.filter((r) => {\n+    return r.split('.')[0] === prefix;\n+  });\n+\n+  if (route.length != 1) {\n+    throw new Error(\n+      `Could not determine treeherder route.  Either there is no route, ` +\n+      `or more than one matching route exists.  Task ID: ${taskId} Routes: ${routes}`\n+    );\n+  }\n+\n+  return parseRoute(route[0]);\n+}\n+\n+\n+function validateTask(validate, taskId, task, schema) {\n+    if (!task.extra || !task.extra.treeherder) {\n+      throw new Error(`Message is missing Treeherder job configuration. Task ID: ${taskId}`);\n+    }\n+    let validationErrors = validate(task.extra.treeherder, schema);\n+    if (validationErrors) {\n+      throw new Error(`Message contains an invalid Treeherder job configuration. Task ID: ${taskId} ${validationErrors}`);\n+    }\n+}\n+\n+export class Handler extends events.EventEmitter {\n+  constructor(options) {\n+    super();\n+    this.queueEvents = options.queueEvents;\n+    this.queue = options.queue;\n+    this.scheduler = options.scheduler;\n+    this.listener = options.listener;\n+    this.prefix = options.prefix;\n+    this.publisher = options.publisher;\n+    this.validator = options.validator;\n+  }\n+\n+  // Starts up the message handler and listens for messages\n+  async start() {\n+    this.listener.on('message', async (message) => {\n+      await this.handleMessage(message).catch(err => {\n+        console.log(`Error caught when processing message. ${err.message}. ${err.stack}`);\n+      });\n+    });\n+    await this.listener.resume();\n+  }\n+\n+  // Listens for Task event messages and invokes the appropriate handler\n+  // for the type of message received.\n+  //\n+  // Only messages that contain the properly formatted routing key and contains\n+  // treeherder job information in task.extra.treeherder are accepted\n+  async handleMessage(message) {\n+    let taskId = message.payload.status.taskId;\n+    let parsedRoute = parseRouteInfo(this.prefix, taskId, message.routes);\n+\n+    let task = await this.queue.task(taskId);\n+    validateTask(this.validator, taskId, task, TASK_TREEHERDER_SCHEMA);\n+\n+    switch (message.exchange) {\n+      case this.queueEvents.taskPending().exchange:\n+        let runId = message.payload.runId;\n+        let run = message.payload.status.runs[message.payload.runId];\n+        // If the task run was created for an infrastructure rerun, then resolve\n+        // the previous run as retried.\n+        if (taskIsRerun(runId, run)) {\n+          await this.handleTaskRerun(parsedRoute, task, message.payload);\n+        }\n+\n+        return await this.handleTaskPending(parsedRoute, task, message.payload)\n+      case this.queueEvents.taskRunning().exchange:\n+        return await this.handleTaskRunning(parsedRoute, task, message.payload)\n+      case this.queueEvents.taskCompleted().exchange:\n+        return await this.handleTaskCompleted(parsedRoute, task, message.payload)\n+      case this.queueEvents.taskFailed().exchange:\n+        return await this.handleTaskFailed(parsedRoute, task, message.payload)\n+      case this.queueEvents.taskException().exchange:\n+        return await this.handleTaskException(parsedRoute, task, message.payload)\n+      default:\n+        throw new Error(`Unknown exchange: ${message.exchange}`);\n+    }\n+\n+  }\n+\n+  // Publishes the Treeherder job message to pulse.\n+  async publishJobMessage(pushInfo, job) {\n+    try {\n+      await this.publisher.jobs(job, {project:pushInfo.project, destination: pushInfo.destination});\n+    } catch(err) {\n+      throw new Error(\n+        `Could not publish job message. ${err.message}. \\n` +\n+        `Job: ${JSON.stringify(job, null, 4)}`);\n+    }\n+  }\n+\n+  // Builds the basic Treeherder job message that's universal for all\n+  // messsage types.\n+  //\n+  // Specific handlers for each message type will add/remove information necessary\n+  // for the type of task event..\n+  buildJobMessage(pushInfo, taskId, runId, task, run) {","path":"src/handler.js","position":167,"original_position":159,"commit_id":"92d5f13c27115f62f4a28e8fa072917aaa23b853","original_commit_id":"2e03072327f94ff4a8063e5e154d50923ce15138","user":{"login":"jonasfj","id":149732,"avatar_url":"https://avatars.githubusercontent.com/u/149732?v=3","gravatar_id":"","url":"https://api.github.com/users/jonasfj","html_url":"https://github.com/jonasfj","followers_url":"https://api.github.com/users/jonasfj/followers","following_url":"https://api.github.com/users/jonasfj/following{/other_user}","gists_url":"https://api.github.com/users/jonasfj/gists{/gist_id}","starred_url":"https://api.github.com/users/jonasfj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonasfj/subscriptions","organizations_url":"https://api.github.com/users/jonasfj/orgs","repos_url":"https://api.github.com/users/jonasfj/repos","events_url":"https://api.github.com/users/jonasfj/events{/privacy}","received_events_url":"https://api.github.com/users/jonasfj/received_events","type":"User","site_admin":false},"body":"why not take `status` and `runId`, then you don't need `run` and `taskId`..\r\n\r\nyou could even make life easier by saying that if `runId === undefined` then you are reporting the last runId, since you only have one exception to that case.","created_at":"2016-04-28T19:31:19Z","updated_at":"2016-04-28T19:31:19Z","html_url":"https://github.com/taskcluster/taskcluster-treeherder/pull/5#discussion_r61488749","pull_request_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5","_links":{"self":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/comments/61488749"},"html":{"href":"https://github.com/taskcluster/taskcluster-treeherder/pull/5#discussion_r61488749"},"pull_request":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5"}}},"pull_request":{"url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5","id":66569051,"html_url":"https://github.com/taskcluster/taskcluster-treeherder/pull/5","diff_url":"https://github.com/taskcluster/taskcluster-treeherder/pull/5.diff","patch_url":"https://github.com/taskcluster/taskcluster-treeherder/pull/5.patch","issue_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/5","number":5,"state":"open","locked":false,"title":"Refactor to publish pulse messages","user":{"login":"gregarndt","id":2592630,"avatar_url":"https://avatars.githubusercontent.com/u/2592630?v=3","gravatar_id":"","url":"https://api.github.com/users/gregarndt","html_url":"https://github.com/gregarndt","followers_url":"https://api.github.com/users/gregarndt/followers","following_url":"https://api.github.com/users/gregarndt/following{/other_user}","gists_url":"https://api.github.com/users/gregarndt/gists{/gist_id}","starred_url":"https://api.github.com/users/gregarndt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gregarndt/subscriptions","organizations_url":"https://api.github.com/users/gregarndt/orgs","repos_url":"https://api.github.com/users/gregarndt/repos","events_url":"https://api.github.com/users/gregarndt/events{/privacy}","received_events_url":"https://api.github.com/users/gregarndt/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-04-14T23:16:31Z","updated_at":"2016-04-28T19:31:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b1f54fcecec2f0884ef3fff4be15acad88863a3e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5/commits","review_comments_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5/comments","review_comment_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/comments{/number}","comments_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/5/comments","statuses_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/statuses/92d5f13c27115f62f4a28e8fa072917aaa23b853","head":{"label":"gregarndt:refactor","ref":"refactor","sha":"92d5f13c27115f62f4a28e8fa072917aaa23b853","user":{"login":"gregarndt","id":2592630,"avatar_url":"https://avatars.githubusercontent.com/u/2592630?v=3","gravatar_id":"","url":"https://api.github.com/users/gregarndt","html_url":"https://github.com/gregarndt","followers_url":"https://api.github.com/users/gregarndt/followers","following_url":"https://api.github.com/users/gregarndt/following{/other_user}","gists_url":"https://api.github.com/users/gregarndt/gists{/gist_id}","starred_url":"https://api.github.com/users/gregarndt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gregarndt/subscriptions","organizations_url":"https://api.github.com/users/gregarndt/orgs","repos_url":"https://api.github.com/users/gregarndt/repos","events_url":"https://api.github.com/users/gregarndt/events{/privacy}","received_events_url":"https://api.github.com/users/gregarndt/received_events","type":"User","site_admin":false},"repo":{"id":55645930,"name":"taskcluster-treeherder","full_name":"gregarndt/taskcluster-treeherder","owner":{"login":"gregarndt","id":2592630,"avatar_url":"https://avatars.githubusercontent.com/u/2592630?v=3","gravatar_id":"","url":"https://api.github.com/users/gregarndt","html_url":"https://github.com/gregarndt","followers_url":"https://api.github.com/users/gregarndt/followers","following_url":"https://api.github.com/users/gregarndt/following{/other_user}","gists_url":"https://api.github.com/users/gregarndt/gists{/gist_id}","starred_url":"https://api.github.com/users/gregarndt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gregarndt/subscriptions","organizations_url":"https://api.github.com/users/gregarndt/orgs","repos_url":"https://api.github.com/users/gregarndt/repos","events_url":"https://api.github.com/users/gregarndt/events{/privacy}","received_events_url":"https://api.github.com/users/gregarndt/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/gregarndt/taskcluster-treeherder","description":"DEPRECATED - TaskCluster Treeherder integration, worker that posts data to treeherder","fork":true,"url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder","forks_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/forks","keys_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/teams","hooks_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/hooks","issue_events_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/issues/events{/number}","events_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/events","assignees_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/assignees{/user}","branches_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/branches{/branch}","tags_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/tags","blobs_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/git/refs{/sha}","trees_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/statuses/{sha}","languages_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/languages","stargazers_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/stargazers","contributors_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/contributors","subscribers_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/subscribers","subscription_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/subscription","commits_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/commits{/sha}","git_commits_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/git/commits{/sha}","comments_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/comments{/number}","issue_comment_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/issues/comments{/number}","contents_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/contents/{+path}","compare_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/merges","archive_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/downloads","issues_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/issues{/number}","pulls_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/pulls{/number}","milestones_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/milestones{/number}","notifications_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/labels{/name}","releases_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/releases{/id}","deployments_url":"https://api.github.com/repos/gregarndt/taskcluster-treeherder/deployments","created_at":"2016-04-06T22:34:50Z","updated_at":"2016-04-06T22:34:50Z","pushed_at":"2016-04-28T19:29:43Z","git_url":"git://github.com/gregarndt/taskcluster-treeherder.git","ssh_url":"git@github.com:gregarndt/taskcluster-treeherder.git","clone_url":"https://github.com/gregarndt/taskcluster-treeherder.git","svn_url":"https://github.com/gregarndt/taskcluster-treeherder","homepage":"","size":1777,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"taskcluster:master","ref":"master","sha":"66dc9a1ca182d80beb1346ccc083ea5e2d596180","user":{"login":"taskcluster","id":6257436,"avatar_url":"https://avatars.githubusercontent.com/u/6257436?v=3","gravatar_id":"","url":"https://api.github.com/users/taskcluster","html_url":"https://github.com/taskcluster","followers_url":"https://api.github.com/users/taskcluster/followers","following_url":"https://api.github.com/users/taskcluster/following{/other_user}","gists_url":"https://api.github.com/users/taskcluster/gists{/gist_id}","starred_url":"https://api.github.com/users/taskcluster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/taskcluster/subscriptions","organizations_url":"https://api.github.com/users/taskcluster/orgs","repos_url":"https://api.github.com/users/taskcluster/repos","events_url":"https://api.github.com/users/taskcluster/events{/privacy}","received_events_url":"https://api.github.com/users/taskcluster/received_events","type":"Organization","site_admin":false},"repo":{"id":19051080,"name":"taskcluster-treeherder","full_name":"taskcluster/taskcluster-treeherder","owner":{"login":"taskcluster","id":6257436,"avatar_url":"https://avatars.githubusercontent.com/u/6257436?v=3","gravatar_id":"","url":"https://api.github.com/users/taskcluster","html_url":"https://github.com/taskcluster","followers_url":"https://api.github.com/users/taskcluster/followers","following_url":"https://api.github.com/users/taskcluster/following{/other_user}","gists_url":"https://api.github.com/users/taskcluster/gists{/gist_id}","starred_url":"https://api.github.com/users/taskcluster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/taskcluster/subscriptions","organizations_url":"https://api.github.com/users/taskcluster/orgs","repos_url":"https://api.github.com/users/taskcluster/repos","events_url":"https://api.github.com/users/taskcluster/events{/privacy}","received_events_url":"https://api.github.com/users/taskcluster/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/taskcluster/taskcluster-treeherder","description":"DEPRECATED - TaskCluster Treeherder integration, worker that posts data to treeherder","fork":false,"url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder","forks_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/forks","keys_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/keys{/key_id}","collaborators_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/teams","hooks_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/hooks","issue_events_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/events{/number}","events_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/events","assignees_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/assignees{/user}","branches_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/branches{/branch}","tags_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/tags","blobs_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/git/refs{/sha}","trees_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/git/trees{/sha}","statuses_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/statuses/{sha}","languages_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/languages","stargazers_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/stargazers","contributors_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/contributors","subscribers_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/subscribers","subscription_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/subscription","commits_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/commits{/sha}","git_commits_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/git/commits{/sha}","comments_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/comments{/number}","issue_comment_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/comments{/number}","contents_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/contents/{+path}","compare_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/compare/{base}...{head}","merges_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/merges","archive_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/downloads","issues_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues{/number}","pulls_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls{/number}","milestones_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/milestones{/number}","notifications_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/labels{/name}","releases_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/releases{/id}","deployments_url":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/deployments","created_at":"2014-04-23T00:17:26Z","updated_at":"2016-02-17T13:25:10Z","pushed_at":"2016-04-28T19:29:44Z","git_url":"git://github.com/taskcluster/taskcluster-treeherder.git","ssh_url":"git@github.com:taskcluster/taskcluster-treeherder.git","clone_url":"https://github.com/taskcluster/taskcluster-treeherder.git","svn_url":"https://github.com/taskcluster/taskcluster-treeherder","homepage":"","size":2208,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5"},"html":{"href":"https://github.com/taskcluster/taskcluster-treeherder/pull/5"},"issue":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/5"},"comments":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/issues/5/comments"},"review_comments":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5/comments"},"review_comment":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/pulls/5/commits"},"statuses":{"href":"https://api.github.com/repos/taskcluster/taskcluster-treeherder/statuses/92d5f13c27115f62f4a28e8fa072917aaa23b853"}}}}