{"action":"created","comment":{"url":"https://api.github.com/repos/RackHD/on-tasks/pulls/comments/57680575","id":57680575,"diff_hunk":"@@ -0,0 +1,207 @@\n+// Copyright 2016, EMC, Inc.\n+\n+'use strict';\n+\n+var di = require('di');\n+module.exports = sshJobFactory;\n+\n+//var Client = require('ssh2').Client;\n+\n+di.annotate(sshJobFactory, new di.Provide('Job.Ssh'));\n+di.annotate(sshJobFactory, new di.Inject(\n+    'Job.Base',\n+    'JobUtils.CommandParser',\n+    'Util',\n+    'Logger',\n+    'Assert',\n+    'Promise',\n+    'Services.Waterline',\n+    'Services.Encryption',\n+    'ssh',\n+    '_'\n+));\n+\n+function sshJobFactory(\n+    BaseJob,\n+    parser,\n+    util,\n+    Logger,\n+    assert,\n+    Promise,\n+    waterline,\n+    cryptService,\n+    ssh,\n+    _\n+) {\n+    var logger = Logger.initialize(sshJobFactory);\n+    var Client = ssh.Client;\n+    function SshJob(options, context, taskId) {\n+        SshJob.super_.call(this, logger, options, context, taskId);\n+        assert.string(this.context.target);\n+        this.nodeId = this.context.target;\n+        this.acceptedCodes = (options.acceptedCodes || []).concat(0);\n+        this.commands = this.buildCommands(options.commands);\n+        assert.arrayOfObject(this.commands);\n+    }\n+    util.inherits(SshJob, BaseJob);\n+\n+    SshJob.prototype._run = function run() {\n+        var self = this;\n+        return waterline.nodes.needByIdentifier(self.nodeId)\n+        .then(function(node) {\n+            return Promise.reduce(self.commands, function(results, commandData) {\n+                return self.sshExec(commandData.command, node.sshSettings, new Client())\n+                .then(function(result) {\n+                    result.catalogOptions = commandData.catalogOptions;\n+                    result.cmd = commandData.command; //for parser matching\n+                    return results.concat([result]);\n+                });\n+            }, []);\n+        })\n+        .then(self.handleResponse.bind(self))\n+        .then(function() {\n+            self._done();\n+        })\n+        .catch(self._done.bind(self));\n+    };\n+\n+    SshJob.prototype.handleResponse = function(results) {\n+        var self = this;\n+\n+        logger.debug(\"Received remote command output from node.\", {\n+            id: self.nodeId,\n+        });\n+\n+        var catalogTasks = _.filter(results, function(result) {\n+            logger.debug('task result', {data:result});\n+            return  _.has(result, 'catalogOptions') && result.catalogOptions;\n+        });\n+\n+        return self.catalogUserTasks(catalogTasks)\n+        .catch(function(err) {\n+            logger.error(\"Job error processing catalog output.\", {\n+                error: err,\n+                id: self.nodeId,\n+                taskContext: self.context\n+            });\n+            throw err;\n+        });\n+    };\n+\n+    SshJob.prototype.catalogUserTasks = function(tasks) {\n+        var self = this;\n+\n+        return parser.parseUnknownTasks(tasks)\n+        .spread(function() {\n+            return Promise.map(Array.prototype.slice.call(arguments), function(result) {\n+                if (result.error) {\n+                    logger.error(\"Failed to parse data for \" +\n+                        result.source + ', ' + result.error,\n+                        { error: result });\n+                } else if (result.store) {\n+                    return waterline.catalogs.create({\n+                        node: self.nodeId,\n+                        source: result.source || 'unknown',\n+                        data: result.data\n+                    });\n+                } else {\n+                    logger.info(\"Catalog result for \" + result.source +\n+                        \" has not been marked as significant. Not storing.\");\n+                }\n+            });\n+        });\n+    };\n+\n+    SshJob.prototype.sshExec = function(command, sshSettings, sshClient) {\n+        var self = this;\n+        return new Promise(function(resolve, reject) {\n+\n+            var ssh = sshClient;\n+            var sshData = {};\n+            ssh.on('ready', function() {\n+                ssh.exec(command, function(err, stream) {\n+                    if (err) { reject(err); }\n+                    stream.on('close', function(code) {\n+                        sshData.exitCode = code;\n+                        ssh.end();\n+                    }).on('data', function(data) {\n+                        sshData.stdout = ( sshData.stdout || '' ) + data.toString();\n+                    }).stderr.on('data', function(data) {\n+                        sshData.stderr = ( sshData.stderr || '' ) + data.toString();\n+                    });\n+                });\n+            }).on('close', function() {","path":"lib/jobs/ssh-job.js","position":133,"original_position":133,"commit_id":"a7471a2975caba05e669b02dd53deb7cb998ee6e","original_commit_id":"a7471a2975caba05e669b02dd53deb7cb998ee6e","user":{"login":"yyscamper","id":511378,"avatar_url":"https://avatars.githubusercontent.com/u/511378?v=3","gravatar_id":"","url":"https://api.github.com/users/yyscamper","html_url":"https://github.com/yyscamper","followers_url":"https://api.github.com/users/yyscamper/followers","following_url":"https://api.github.com/users/yyscamper/following{/other_user}","gists_url":"https://api.github.com/users/yyscamper/gists{/gist_id}","starred_url":"https://api.github.com/users/yyscamper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yyscamper/subscriptions","organizations_url":"https://api.github.com/users/yyscamper/orgs","repos_url":"https://api.github.com/users/yyscamper/repos","events_url":"https://api.github.com/users/yyscamper/events{/privacy}","received_events_url":"https://api.github.com/users/yyscamper/received_events","type":"User","site_admin":false},"body":"From the user guide from [ssh2](https://www.npmjs.com/package/ssh2), the `close` event will pass a argument `hadError` to the callback, so here needs to reject the promise if `hadError` is positive.\r\n\r\nMeanwhile, there is another event 'error`, this event need be better to be caught to reject promise and print the error info to assist debugging.","created_at":"2016-03-29T07:13:09Z","updated_at":"2016-03-29T07:13:09Z","html_url":"https://github.com/RackHD/on-tasks/pull/150#discussion_r57680575","pull_request_url":"https://api.github.com/repos/RackHD/on-tasks/pulls/150","_links":{"self":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/comments/57680575"},"html":{"href":"https://github.com/RackHD/on-tasks/pull/150#discussion_r57680575"},"pull_request":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/150"}}},"pull_request":{"url":"https://api.github.com/repos/RackHD/on-tasks/pulls/150","id":64395046,"html_url":"https://github.com/RackHD/on-tasks/pull/150","diff_url":"https://github.com/RackHD/on-tasks/pull/150.diff","patch_url":"https://github.com/RackHD/on-tasks/pull/150.patch","issue_url":"https://api.github.com/repos/RackHD/on-tasks/issues/150","number":150,"state":"open","locked":false,"title":"Added a job to enable arbitrary ssh commands","user":{"login":"VulpesArtificem","id":5447423,"avatar_url":"https://avatars.githubusercontent.com/u/5447423?v=3","gravatar_id":"","url":"https://api.github.com/users/VulpesArtificem","html_url":"https://github.com/VulpesArtificem","followers_url":"https://api.github.com/users/VulpesArtificem/followers","following_url":"https://api.github.com/users/VulpesArtificem/following{/other_user}","gists_url":"https://api.github.com/users/VulpesArtificem/gists{/gist_id}","starred_url":"https://api.github.com/users/VulpesArtificem/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/VulpesArtificem/subscriptions","organizations_url":"https://api.github.com/users/VulpesArtificem/orgs","repos_url":"https://api.github.com/users/VulpesArtificem/repos","events_url":"https://api.github.com/users/VulpesArtificem/events{/privacy}","received_events_url":"https://api.github.com/users/VulpesArtificem/received_events","type":"User","site_admin":false},"body":"@RackHD/corecommitters  @heckj ","created_at":"2016-03-29T00:01:22Z","updated_at":"2016-03-29T07:13:10Z","closed_at":null,"merged_at":null,"merge_commit_sha":"fbcd3171eef830d7b4cae6aa17032c8753dbd976","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/RackHD/on-tasks/pulls/150/commits","review_comments_url":"https://api.github.com/repos/RackHD/on-tasks/pulls/150/comments","review_comment_url":"https://api.github.com/repos/RackHD/on-tasks/pulls/comments{/number}","comments_url":"https://api.github.com/repos/RackHD/on-tasks/issues/150/comments","statuses_url":"https://api.github.com/repos/RackHD/on-tasks/statuses/a7471a2975caba05e669b02dd53deb7cb998ee6e","head":{"label":"VulpesArtificem:ssh-job","ref":"ssh-job","sha":"a7471a2975caba05e669b02dd53deb7cb998ee6e","user":{"login":"VulpesArtificem","id":5447423,"avatar_url":"https://avatars.githubusercontent.com/u/5447423?v=3","gravatar_id":"","url":"https://api.github.com/users/VulpesArtificem","html_url":"https://github.com/VulpesArtificem","followers_url":"https://api.github.com/users/VulpesArtificem/followers","following_url":"https://api.github.com/users/VulpesArtificem/following{/other_user}","gists_url":"https://api.github.com/users/VulpesArtificem/gists{/gist_id}","starred_url":"https://api.github.com/users/VulpesArtificem/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/VulpesArtificem/subscriptions","organizations_url":"https://api.github.com/users/VulpesArtificem/orgs","repos_url":"https://api.github.com/users/VulpesArtificem/repos","events_url":"https://api.github.com/users/VulpesArtificem/events{/privacy}","received_events_url":"https://api.github.com/users/VulpesArtificem/received_events","type":"User","site_admin":false},"repo":{"id":45429601,"name":"on-tasks","full_name":"VulpesArtificem/on-tasks","owner":{"login":"VulpesArtificem","id":5447423,"avatar_url":"https://avatars.githubusercontent.com/u/5447423?v=3","gravatar_id":"","url":"https://api.github.com/users/VulpesArtificem","html_url":"https://github.com/VulpesArtificem","followers_url":"https://api.github.com/users/VulpesArtificem/followers","following_url":"https://api.github.com/users/VulpesArtificem/following{/other_user}","gists_url":"https://api.github.com/users/VulpesArtificem/gists{/gist_id}","starred_url":"https://api.github.com/users/VulpesArtificem/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/VulpesArtificem/subscriptions","organizations_url":"https://api.github.com/users/VulpesArtificem/orgs","repos_url":"https://api.github.com/users/VulpesArtificem/repos","events_url":"https://api.github.com/users/VulpesArtificem/events{/privacy}","received_events_url":"https://api.github.com/users/VulpesArtificem/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/VulpesArtificem/on-tasks","description":"","fork":true,"url":"https://api.github.com/repos/VulpesArtificem/on-tasks","forks_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/forks","keys_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/keys{/key_id}","collaborators_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/teams","hooks_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/hooks","issue_events_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/issues/events{/number}","events_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/events","assignees_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/assignees{/user}","branches_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/branches{/branch}","tags_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/tags","blobs_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/git/refs{/sha}","trees_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/git/trees{/sha}","statuses_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/statuses/{sha}","languages_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/languages","stargazers_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/stargazers","contributors_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/contributors","subscribers_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/subscribers","subscription_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/subscription","commits_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/commits{/sha}","git_commits_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/git/commits{/sha}","comments_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/comments{/number}","issue_comment_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/issues/comments{/number}","contents_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/contents/{+path}","compare_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/compare/{base}...{head}","merges_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/merges","archive_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/downloads","issues_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/issues{/number}","pulls_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/pulls{/number}","milestones_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/milestones{/number}","notifications_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/labels{/name}","releases_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/releases{/id}","deployments_url":"https://api.github.com/repos/VulpesArtificem/on-tasks/deployments","created_at":"2015-11-02T23:49:14Z","updated_at":"2015-11-02T23:49:16Z","pushed_at":"2016-03-29T00:01:07Z","git_url":"git://github.com/VulpesArtificem/on-tasks.git","ssh_url":"git@github.com:VulpesArtificem/on-tasks.git","clone_url":"https://github.com/VulpesArtificem/on-tasks.git","svn_url":"https://github.com/VulpesArtificem/on-tasks","homepage":null,"size":1198,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"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":"RackHD:master","ref":"master","sha":"0bdeda8103ba32912ed83a3b1e36fad4eac19586","user":{"login":"RackHD","id":13982662,"avatar_url":"https://avatars.githubusercontent.com/u/13982662?v=3","gravatar_id":"","url":"https://api.github.com/users/RackHD","html_url":"https://github.com/RackHD","followers_url":"https://api.github.com/users/RackHD/followers","following_url":"https://api.github.com/users/RackHD/following{/other_user}","gists_url":"https://api.github.com/users/RackHD/gists{/gist_id}","starred_url":"https://api.github.com/users/RackHD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RackHD/subscriptions","organizations_url":"https://api.github.com/users/RackHD/orgs","repos_url":"https://api.github.com/users/RackHD/repos","events_url":"https://api.github.com/users/RackHD/events{/privacy}","received_events_url":"https://api.github.com/users/RackHD/received_events","type":"Organization","site_admin":false},"repo":{"id":41690099,"name":"on-tasks","full_name":"RackHD/on-tasks","owner":{"login":"RackHD","id":13982662,"avatar_url":"https://avatars.githubusercontent.com/u/13982662?v=3","gravatar_id":"","url":"https://api.github.com/users/RackHD","html_url":"https://github.com/RackHD","followers_url":"https://api.github.com/users/RackHD/followers","following_url":"https://api.github.com/users/RackHD/following{/other_user}","gists_url":"https://api.github.com/users/RackHD/gists{/gist_id}","starred_url":"https://api.github.com/users/RackHD/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RackHD/subscriptions","organizations_url":"https://api.github.com/users/RackHD/orgs","repos_url":"https://api.github.com/users/RackHD/repos","events_url":"https://api.github.com/users/RackHD/events{/privacy}","received_events_url":"https://api.github.com/users/RackHD/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/RackHD/on-tasks","description":"","fork":false,"url":"https://api.github.com/repos/RackHD/on-tasks","forks_url":"https://api.github.com/repos/RackHD/on-tasks/forks","keys_url":"https://api.github.com/repos/RackHD/on-tasks/keys{/key_id}","collaborators_url":"https://api.github.com/repos/RackHD/on-tasks/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/RackHD/on-tasks/teams","hooks_url":"https://api.github.com/repos/RackHD/on-tasks/hooks","issue_events_url":"https://api.github.com/repos/RackHD/on-tasks/issues/events{/number}","events_url":"https://api.github.com/repos/RackHD/on-tasks/events","assignees_url":"https://api.github.com/repos/RackHD/on-tasks/assignees{/user}","branches_url":"https://api.github.com/repos/RackHD/on-tasks/branches{/branch}","tags_url":"https://api.github.com/repos/RackHD/on-tasks/tags","blobs_url":"https://api.github.com/repos/RackHD/on-tasks/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/RackHD/on-tasks/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/RackHD/on-tasks/git/refs{/sha}","trees_url":"https://api.github.com/repos/RackHD/on-tasks/git/trees{/sha}","statuses_url":"https://api.github.com/repos/RackHD/on-tasks/statuses/{sha}","languages_url":"https://api.github.com/repos/RackHD/on-tasks/languages","stargazers_url":"https://api.github.com/repos/RackHD/on-tasks/stargazers","contributors_url":"https://api.github.com/repos/RackHD/on-tasks/contributors","subscribers_url":"https://api.github.com/repos/RackHD/on-tasks/subscribers","subscription_url":"https://api.github.com/repos/RackHD/on-tasks/subscription","commits_url":"https://api.github.com/repos/RackHD/on-tasks/commits{/sha}","git_commits_url":"https://api.github.com/repos/RackHD/on-tasks/git/commits{/sha}","comments_url":"https://api.github.com/repos/RackHD/on-tasks/comments{/number}","issue_comment_url":"https://api.github.com/repos/RackHD/on-tasks/issues/comments{/number}","contents_url":"https://api.github.com/repos/RackHD/on-tasks/contents/{+path}","compare_url":"https://api.github.com/repos/RackHD/on-tasks/compare/{base}...{head}","merges_url":"https://api.github.com/repos/RackHD/on-tasks/merges","archive_url":"https://api.github.com/repos/RackHD/on-tasks/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/RackHD/on-tasks/downloads","issues_url":"https://api.github.com/repos/RackHD/on-tasks/issues{/number}","pulls_url":"https://api.github.com/repos/RackHD/on-tasks/pulls{/number}","milestones_url":"https://api.github.com/repos/RackHD/on-tasks/milestones{/number}","notifications_url":"https://api.github.com/repos/RackHD/on-tasks/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/RackHD/on-tasks/labels{/name}","releases_url":"https://api.github.com/repos/RackHD/on-tasks/releases{/id}","deployments_url":"https://api.github.com/repos/RackHD/on-tasks/deployments","created_at":"2015-08-31T17:29:32Z","updated_at":"2016-01-31T09:07:09Z","pushed_at":"2016-03-29T03:12:27Z","git_url":"git://github.com/RackHD/on-tasks.git","ssh_url":"git@github.com:RackHD/on-tasks.git","clone_url":"https://github.com/RackHD/on-tasks.git","svn_url":"https://github.com/RackHD/on-tasks","homepage":null,"size":1238,"stargazers_count":6,"watchers_count":6,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":34,"mirror_url":null,"open_issues_count":6,"forks":34,"open_issues":6,"watchers":6,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/150"},"html":{"href":"https://github.com/RackHD/on-tasks/pull/150"},"issue":{"href":"https://api.github.com/repos/RackHD/on-tasks/issues/150"},"comments":{"href":"https://api.github.com/repos/RackHD/on-tasks/issues/150/comments"},"review_comments":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/150/comments"},"review_comment":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/RackHD/on-tasks/pulls/150/commits"},"statuses":{"href":"https://api.github.com/repos/RackHD/on-tasks/statuses/a7471a2975caba05e669b02dd53deb7cb998ee6e"}}}}