lita-jls 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,321 @@
1
+ require "cabin"
2
+ require "tmpdir"
3
+ require "tempfile"
4
+ require "fileutils"
5
+ require "insist"
6
+ require "uri"
7
+ require "lita-jls/bot_builder"
8
+ require "lita-jls/repository"
9
+ require "lita-jls/github_url_parser"
10
+ require "lita-jls/util"
11
+
12
+ # TODO(sissel): This code needs some suuuper serious refactoring and testing improvements.
13
+ # TODO(sissel): Remove any usage of Rugged. This library requires compile-time
14
+ # settings of libgit2 and that's an annoying battle.
15
+
16
+ module Lita
17
+ module Handlers
18
+ class Jls < Handler
19
+ include LitaJLS::Util
20
+
21
+ route /^merge(?<dry>\?)? (?<pr_url>[^ ]+) (?<branchspec>.*)$/, :merge,
22
+ :command => true,
23
+ :help => { "merge https://github.com/ORG/PROJECT/pull/NUMBER branch1 [branch2 ...]" => "merges a PR into one or more branches. To see if a merge is successful, use 'merge? project#pr branch1 [branch2 ...]" }
24
+
25
+ route /^cla(?<dry>\?)? (?<pr_url>[^ ]+)$/, :cla,
26
+ :command => true,
27
+ :help => { "cla https://github.com/ORG/PROJECT/pull/NUMBER" => "CLA check for a giaven PR" }
28
+
29
+ route /^\(tableflip\)$/, :tableflip,
30
+ :command => true,
31
+ :help => { "(tableflip)" => "Fix whatever just broke. Probably git is going funky, so I will purge my local git junk" }
32
+
33
+ route /^ping/, :ping, :command => true
34
+
35
+ route /^publish\s(?<git_url>[^ ]+)$/, :publish,
36
+ :command => true,
37
+ :restrict_to => :logstash,
38
+ :help => { 'publish https://github.com/ORG/project' => 'Install dependencies, Run test, build gem, publish and compare version on rubygems' }
39
+
40
+ route /^(why computer(s?) so bad\?|explain)/i, :pop_exception,
41
+ :command => true,
42
+ :help => { 'explain or why computers so bad?' => 'return the last exception from redis' }
43
+
44
+ route /^migrate_pr (?<source_url>[^ ]+) (?<destination_url>[^ ]+)$/, :migrate_pr,
45
+ :command => true,
46
+ :help => { 'migrate_pr https://github.com/elasticsearch/logstash/pull/1452 "\
47
+ + "https://github.com/logstash-plugins/logstash-codec-line' => 'migrate pr from one repo to another' }
48
+
49
+ REMOTE = "origin"
50
+ URLBASE = "https://github.com/"
51
+ LIMIT_EXCEPTIONS_HISTORY = 20
52
+
53
+ RUBY_VERSION = "jruby-1.7.16"
54
+
55
+ on :loaded, :setup
56
+
57
+ def self.default_config(config)
58
+ config.default_organization = nil
59
+ end
60
+
61
+ def pop_exception(msg)
62
+ public_response = ['Commencing automated assembly. Estimated completion time is five hours.',
63
+ 'That is the only way, sir.',
64
+ 'Sir, please may I request just a few hours to calibrate.' ]
65
+
66
+ e = @redis.lpop(:exception)
67
+
68
+ msg.reply(public_response.sample)
69
+
70
+ logger.debug("pop exception", :exception => e)
71
+
72
+ if e
73
+ e = JSON.parse(e)
74
+
75
+ msg.reply_privately("exception: #{e.delete('exception')}")
76
+ msg.reply_privately("message: #{e.delete('message')}")
77
+ msg.reply_privately("backtrace: #{e.delete('backtrace')}")
78
+
79
+ # Print the remaining context
80
+ e.each do |key, value|
81
+ msg.reply_privately("#{key}: #{value}")
82
+ end
83
+ else
84
+ msg.reply_privately("No exception saved.")
85
+ end
86
+ end
87
+
88
+ def push_exception(e, context = {})
89
+ error = {
90
+ "exception" => e.exception,
91
+ "message" => e.message,
92
+ "backtrace" => e.backtrace,
93
+ }.merge(context)
94
+
95
+ @redis.lpush(:exception, error.to_json)
96
+ @redis.ltrim(:exception, 0, LIMIT_EXCEPTIONS_HISTORY)
97
+ end
98
+
99
+ def publish(msg)
100
+ git_url = msg.match_data["git_url"]
101
+
102
+ logger.info('publish', :url => git_url)
103
+
104
+ github_parser = LitaJLS::GithubUrlParser.parse(git_url, :link => :repository)
105
+ github_parser.validate!
106
+
107
+ repository = LitaJLS::Repository.new(github_parser)
108
+ repository.clone
109
+ repository.switch_branch('master')
110
+
111
+ builder = LitaJLS::BotBuilder.new(repository.git_path, { :ruby_version => RUBY_VERSION })
112
+
113
+ msg.reply("publishing (allthethings) for project: #{builder.project_name} branch: master")
114
+
115
+ reporter = LitaJLS::Reporter::HipChat.new(builder.build)
116
+ reporter.format(msg)
117
+ rescue => e
118
+ push_exception(e, :project => "#{github_parser.user}/#{github_parser.project}")
119
+ msg.reply("(stare) Error: #{e.inspect}")
120
+ raise
121
+ end # def publish
122
+
123
+ def ping(msg)
124
+ msg.reply("(chompy)")
125
+ end
126
+
127
+ def setup(*args)
128
+ ENV["PAGER"] = "cat"
129
+ @@logger_subscription ||= logger.subscribe(STDOUT)
130
+
131
+ @redis ||= Redis::Namespace.new("opsbot", redis: Lita.redis)
132
+ end
133
+
134
+ def cla(msg)
135
+ @cla_uri = config.cla_uri
136
+ pull = msg.match_data["pr_url"]
137
+ pull_path = URI.parse(pull).path
138
+ _, user, project, _, pr = pull_path.split("/")
139
+ cla?("#{user}/#{project}", pr)
140
+ msg.reply("#{user}/#{project}##{pr} CLA OK (freddie)")
141
+ rescue => e
142
+ msg.reply("cla check error: #{e}")
143
+ push_exception(e, :project => "#{user}/#{project}", :pr => pr)
144
+ end
145
+
146
+ def migrate_pr(msg)
147
+ source_url = msg.match_data["source_url"]
148
+ destination_url = msg.match_data["destination_url"]
149
+ if source_url.nil? || destination_url.nil?
150
+ raise "Invalid paramaters provided #{msg}"
151
+ end
152
+
153
+ destination_github_parser = parse_github_url(destination_url)
154
+ source_github_parser = parse_github_url(source_url)
155
+
156
+ pr_num = source_github_parser.pr
157
+ source_github_pr = github_get_pr("#{source_github_parser.user}/#{source_github_parser.project}", pr_num)
158
+
159
+ # Clone destination dir, patch and then push branch
160
+ repository = LitaJLS::Repository.new(destination_github_parser)
161
+ repository.clone if Dir["#{repository.git_path}/*"].empty?
162
+ repository.switch_branch("master")
163
+
164
+ # create a branch like pr/1234
165
+ pr_branch = "bot-migrated-pr/#{pr_num}"
166
+ repository.delete_local_branch(pr_branch, true)
167
+ repository.switch_branch(pr_branch, true)
168
+
169
+ patch_file = download_patch(source_github_pr[:patch_url], pr_num)
170
+
171
+ # Apply patch on repo
172
+ begin
173
+ repository.git_patch(patch_file.path)
174
+ repository.git_push_branch(pr_branch)
175
+ rescue => e
176
+ msg.reply("Error while migrating pr: #{e}")
177
+ push_exception(e, :source_url => source_url,
178
+ :destination_url => destination_url)
179
+ ensure
180
+ patch_file.unlink
181
+ end
182
+
183
+ # create the migrated PR in the destination repo
184
+ github_create_pr("#{destination_github_parser.user}/#{destination_github_parser.project}",
185
+ pr_branch, source_github_pr[:title],
186
+ source_github_pr[:body] + "\nMoved from #{source_url}")
187
+ end
188
+
189
+ @private
190
+ # downloads the patch file in mail format and saves it to a file
191
+ def download_patch(pr_url, pr_num)
192
+ http = Faraday.new("https://github.com")
193
+ response = http.get(URI.parse(pr_url).path)
194
+ if response.status != 200
195
+ raise "Unable to fetch pull request #{pr_url}"
196
+ end
197
+
198
+ patch_file = Tempfile.new("#{pr_num}.patch")
199
+
200
+ begin
201
+ #TODO: Use chunked writes
202
+ patch_file.write(response.body)
203
+ patch_file.close
204
+ rescue => e
205
+ raise "Error while downloading pr: #{pr_url}, exception #{e}"
206
+ end
207
+
208
+ return patch_file
209
+ end
210
+
211
+ @private
212
+ def parse_github_url(url)
213
+ github_parser = LitaJLS::GithubUrlParser.parse(url, :link => :repository)
214
+ github_parser.validate!
215
+ return github_parser
216
+ end
217
+
218
+ def merge(msg)
219
+ @cla_uri = config.cla_uri
220
+ FileUtils.mkdir_p(workdir) unless File.directory?(workdir)
221
+ pull = msg.match_data["pr_url"]
222
+ pull_path = URI.parse(pull).path
223
+ _, user, project, _, pr = pull_path.split("/")
224
+
225
+ if user.nil? || project.nil? || pr.nil? || pull !~ /^https:\/\/github.com\//
226
+ raise "Invalid URL. Expected something like: https://github.com/elasticsearch/snacktime/pull/12345"
227
+ end
228
+
229
+ branchspec = msg.match_data["branchspec"]
230
+ dry_run = msg.match_data["dry"]
231
+
232
+ begin
233
+ cla?("#{user}/#{project}", pr)
234
+ rescue => e
235
+ msg.reply("(firstworldproblems) cla check failed for #{user}/#{project}##{pr}.\n #{e}")
236
+ push_exception(e, :project => "#{user}/#{project}", :pr => pr)
237
+ return
238
+ end
239
+
240
+ url = File.join(URLBASE, user, project)
241
+ #git_url = "git@github.com:/#{user}/#{project}.git"
242
+ git_url = url
243
+ pr_url = File.join(url, "pull", "#{pr}.patch")
244
+ gitpath = gitdir(project)
245
+ branches = branchspec.split(/\s+/)
246
+
247
+ logger.info("Cloning git repo", :url => git_url, :gitpath => gitpath)
248
+ repo = clone_at(git_url, gitpath)
249
+
250
+ git(gitpath, "am", "--abort") if File.directory?(".git/rebase-apply")
251
+
252
+ # TODO(sissel): Fetch the PR patch
253
+ logger.info("Fetching PR patch", :url => pr_url)
254
+ http = Faraday.new("https://github.com")
255
+ response = http.get(URI.parse(pr_url).path)
256
+ if !response.success?
257
+ logger.warn("Failed fetching patch", :url => pr_url, :status => response.status, :headers => response.headers)
258
+ msg.reply("(grumpycat) Failed fetching patch. Cannot continue!")
259
+ return
260
+ end
261
+
262
+ patch = response.body
263
+
264
+ # For each branch, try to merge
265
+ repo = gitpath
266
+ branches.each do |branch|
267
+ begin
268
+ logger.info("Switching branches", :branch => branch, :repo => gitpath)
269
+ git(gitpath, "checkout", branch)
270
+ git(gitpath, "reset", "--hard", "#{REMOTE}/#{branch}")
271
+ git(gitpath, "pull", "--ff-only")
272
+ apply_patch(repo, patch) do |commit|
273
+ # Append the PR number to commit message
274
+
275
+ # Use "Fixes #XYZ" to make the PR get closed upon commit.
276
+ # https://help.github.com/articles/closing-issues-via-commit-messages
277
+ commit[:message] += "\nFixes ##{pr}"
278
+ end
279
+ rescue => e
280
+ push_exception(e, :project => "#{user}/#{project}", :pr => pr, :branch => branch)
281
+ msg.reply("(jackie) Failed attempting to merge #{user}/#{project}##{pr} into #{branch}: #{e}")
282
+ raise
283
+ end
284
+ end
285
+
286
+ # At this point, all branches merged successfully. Time to push!
287
+ if dry_run
288
+ msg.reply("(success) Merging was successful #{user}/#{project}##{pr} into: #{branchspec}.\n(but I did not push it)")
289
+ else
290
+ msg.reply("(success) #{user}/#{project}##{pr} merged into: #{branchspec}")
291
+ git(gitpath, "push", REMOTE, *branches)
292
+
293
+ labels = branches.reject { |b| b == "master" }
294
+ github_issue_label("#{user}/#{project}", pr.to_i, labels)
295
+ end
296
+ github_issue_comment("#{user}/#{project}", pr.to_i, "Merged sucessfully into #{branchspec}!")
297
+ rescue => e
298
+ push_exception(e, :project => "#{user}/#{project}", :pr => pr, :branch => branches)
299
+ msg.reply("(stare) Error: #{e.inspect}")
300
+ raise
301
+ end # def merge
302
+
303
+ def tableflip(msg)
304
+ logger.debug("(fliptable), remove the git directory")
305
+
306
+ begin
307
+ dir = workdir("gitbase")
308
+ insist { dir } =~ /\/lita-jls/ # Just in case, before we go purging things...
309
+ FileUtils.rm_r(dir) if File.directory?(dir)
310
+ msg.reply("Git: (tableflip) (success)")
311
+ rescue => e
312
+ push_exception(e)
313
+ msg.reply("Git: (tableflip) (huh): #{e}")
314
+ raise e
315
+ end
316
+ end
317
+ end # class Jls
318
+
319
+ Lita.register_handler(Jls)
320
+ end
321
+ end
@@ -0,0 +1,41 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "lita-jls"
3
+ spec.version = "0.0.11"
4
+ spec.authors = ["Jordan Sissel"]
5
+ spec.email = ["jls@semicomplete.com"]
6
+ spec.description = %q{Some stuff for the lita.io bot}
7
+ spec.summary = spec.description
8
+ spec.homepage = "http://example.com/"
9
+ spec.license = "MIT"
10
+ spec.metadata = { "lita_plugin_type" => "handler" }
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ["lib"]
16
+
17
+ spec.add_runtime_dependency "lita", ">= 3.3"
18
+ spec.add_runtime_dependency "cabin", ">= 0"
19
+ spec.add_runtime_dependency "faraday", ">= 0"
20
+
21
+ # For access to Github's api
22
+ spec.add_runtime_dependency "octokit", ">= 0"
23
+ # For netrc support in octokit
24
+ spec.add_runtime_dependency "netrc"
25
+
26
+ # For parsing github's .patch files (mbox format)
27
+ spec.add_runtime_dependency "mbox", ">= 0"
28
+ spec.add_runtime_dependency "insist"
29
+ spec.add_runtime_dependency 'gems', '~> 0.8.3'
30
+ spec.add_runtime_dependency 'semverly', '~> 1.0.0'
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.3"
33
+ spec.add_development_dependency "rake"
34
+ spec.add_development_dependency "stud"
35
+ spec.add_development_dependency "rspec", ">= 3.0.0"
36
+ spec.add_development_dependency "simplecov"
37
+ spec.add_development_dependency "pry"
38
+ spec.add_development_dependency "coveralls"
39
+ spec.add_development_dependency "vcr"
40
+ spec.add_development_dependency "webmock"
41
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ lita:
3
+ handlers:
4
+ jls:
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gem 'this-gem-doesnt-exist'
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-codec-edn'
4
+ s.version = '0.1.3'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Codec to process EDN data"
7
+ s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'info@elasticsearch.com'
10
+ s.homepage = "http://www.elasticsearch.org/guide/en/logstash/current/index.html"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "codec" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency 'edn'
26
+
27
+ s.add_development_dependency 'logstash-devutils'
28
+ end
29
+
@@ -0,0 +1,3 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "logstash"
3
+ end
@@ -0,0 +1,3 @@
1
+ module Testmore
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,9 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'testmore/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dummy-gem-dont-publish"
8
+ spec.version = Testmore::VERSION
9
+ end
@@ -0,0 +1,96 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://rubygems.org/api/v1/versions/logstash-output-s3.yaml
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - "*/*"
14
+ User-Agent:
15
+ - Gems 0.8.3
16
+ - Ruby
17
+ Connection:
18
+ - keep-alive
19
+ Keep-Alive:
20
+ - '30'
21
+ Content-Type:
22
+ - application/x-www-form-urlencoded
23
+ response:
24
+ status:
25
+ code: 200
26
+ message: OK
27
+ headers:
28
+ Server:
29
+ - nginx
30
+ Date:
31
+ - Tue, 16 Dec 2014 16:12:50 GMT
32
+ Content-Type:
33
+ - text/yaml
34
+ Transfer-Encoding:
35
+ - chunked
36
+ Connection:
37
+ - keep-alive
38
+ Status:
39
+ - 200 OK
40
+ Etag:
41
+ - '"7341898c9b8c18bb4a2f72cafa13f98e"'
42
+ Last-Modified:
43
+ - Thu, 06 Nov 2014 09:33:38 GMT
44
+ Content-Disposition:
45
+ - attachment
46
+ Content-Transfer-Encoding:
47
+ - binary
48
+ Cache-Control:
49
+ - private
50
+ X-Content-Type-Options:
51
+ - nosniff
52
+ X-Ua-Compatible:
53
+ - IE=Edge,chrome=1
54
+ - IE=Edge,chrome=1
55
+ X-Request-Id:
56
+ - e726d0892877a0b310402be784f88add
57
+ X-Runtime:
58
+ - '0.012082'
59
+ X-Rack-Cache:
60
+ - miss
61
+ body:
62
+ encoding: UTF-8
63
+ string: |
64
+ ---
65
+ - authors: Elasticsearch
66
+ built_at: '2014-11-19T00:00:00Z'
67
+ description: This gem is a logstash plugin required to be installed on top of the
68
+ Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is
69
+ not a stand-alone program
70
+ downloads_count: 479
71
+ number: 0.1.1
72
+ summary: This plugin was created for store the logstash's events into Amazon Simple
73
+ Storage Service (Amazon S3)
74
+ platform: ruby
75
+ ruby_version: '>= 0'
76
+ prerelease: false
77
+ licenses:
78
+ - Apache License (2.0)
79
+ requirements: []
80
+ - authors: Elasticsearch
81
+ built_at: '2014-11-06T00:00:00Z'
82
+ description: This plugin was created for store the logstash's events into Amazon
83
+ Simple Storage Service (Amazon S3)
84
+ downloads_count: 195
85
+ number: 0.1.0
86
+ summary: This plugin was created for store the logstash's events into Amazon Simple
87
+ Storage Service (Amazon S3)
88
+ platform: ruby
89
+ ruby_version: '>= 0'
90
+ prerelease: false
91
+ licenses:
92
+ - Apache License (2.0)
93
+ requirements: []
94
+ http_version:
95
+ recorded_at: Tue, 16 Dec 2014 16:12:51 GMT
96
+ recorded_with: VCR 2.9.3