codebot 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +32 -0
  3. data/.github/ISSUE_TEMPLATE/formatter_issue.md +20 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  5. data/.gitignore +10 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +11 -0
  8. data/.travis.yml +26 -0
  9. data/CODE_OF_CONDUCT.md +46 -0
  10. data/CONTRIBUTING.md +15 -0
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +75 -0
  13. data/LICENSE +21 -0
  14. data/README.md +230 -0
  15. data/Rakefile +29 -0
  16. data/bin/console +8 -0
  17. data/codebot.gemspec +49 -0
  18. data/exe/codebot +7 -0
  19. data/lib/codebot.rb +8 -0
  20. data/lib/codebot/channel.rb +134 -0
  21. data/lib/codebot/command_error.rb +17 -0
  22. data/lib/codebot/config.rb +125 -0
  23. data/lib/codebot/configuration_error.rb +17 -0
  24. data/lib/codebot/core.rb +76 -0
  25. data/lib/codebot/cryptography.rb +38 -0
  26. data/lib/codebot/event.rb +62 -0
  27. data/lib/codebot/ext/cinch/ssl_extensions.rb +37 -0
  28. data/lib/codebot/formatter.rb +242 -0
  29. data/lib/codebot/formatters.rb +109 -0
  30. data/lib/codebot/formatters/.rubocop.yml +2 -0
  31. data/lib/codebot/formatters/commit_comment.rb +43 -0
  32. data/lib/codebot/formatters/fork.rb +40 -0
  33. data/lib/codebot/formatters/gitlab_issue_hook.rb +56 -0
  34. data/lib/codebot/formatters/gitlab_job_hook.rb +77 -0
  35. data/lib/codebot/formatters/gitlab_merge_request_hook.rb +57 -0
  36. data/lib/codebot/formatters/gitlab_note_hook.rb +119 -0
  37. data/lib/codebot/formatters/gitlab_pipeline_hook.rb +51 -0
  38. data/lib/codebot/formatters/gitlab_push_hook.rb +83 -0
  39. data/lib/codebot/formatters/gitlab_wiki_page_hook.rb +56 -0
  40. data/lib/codebot/formatters/gollum.rb +67 -0
  41. data/lib/codebot/formatters/issue_comment.rb +41 -0
  42. data/lib/codebot/formatters/issues.rb +41 -0
  43. data/lib/codebot/formatters/ping.rb +79 -0
  44. data/lib/codebot/formatters/public.rb +30 -0
  45. data/lib/codebot/formatters/pull_request.rb +71 -0
  46. data/lib/codebot/formatters/pull_request_review_comment.rb +49 -0
  47. data/lib/codebot/formatters/push.rb +172 -0
  48. data/lib/codebot/formatters/watch.rb +38 -0
  49. data/lib/codebot/integration.rb +195 -0
  50. data/lib/codebot/integration_manager.rb +225 -0
  51. data/lib/codebot/ipc_client.rb +83 -0
  52. data/lib/codebot/ipc_server.rb +79 -0
  53. data/lib/codebot/irc_client.rb +102 -0
  54. data/lib/codebot/irc_connection.rb +156 -0
  55. data/lib/codebot/message.rb +37 -0
  56. data/lib/codebot/metadata.rb +15 -0
  57. data/lib/codebot/network.rb +240 -0
  58. data/lib/codebot/network_manager.rb +181 -0
  59. data/lib/codebot/options.rb +49 -0
  60. data/lib/codebot/options/base.rb +55 -0
  61. data/lib/codebot/options/core.rb +126 -0
  62. data/lib/codebot/options/integration.rb +101 -0
  63. data/lib/codebot/options/network.rb +109 -0
  64. data/lib/codebot/payload.rb +32 -0
  65. data/lib/codebot/request.rb +51 -0
  66. data/lib/codebot/sanitizers.rb +130 -0
  67. data/lib/codebot/serializable.rb +101 -0
  68. data/lib/codebot/shortener.rb +43 -0
  69. data/lib/codebot/thread_controller.rb +70 -0
  70. data/lib/codebot/user_error.rb +13 -0
  71. data/lib/codebot/validation_error.rb +17 -0
  72. data/lib/codebot/web_listener.rb +107 -0
  73. data/lib/codebot/web_server.rb +58 -0
  74. data/webhook.png +0 -0
  75. metadata +249 -0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codebot
4
+ # This class provides a consistent interface for subclasses that manage a
5
+ # thread.
6
+ class ThreadController
7
+ # Creates a new thread controller.
8
+ def initialize
9
+ @thread = nil
10
+ end
11
+
12
+ # Suspends execution of the calling thread until the managed thread exits.
13
+ #
14
+ # @return [Thread, nil] the dead thead, or +nil+ if no thread was active
15
+ def join
16
+ @thread.join if running?
17
+ end
18
+
19
+ # Checks whether the managed thread is currently running.
20
+ #
21
+ # @return [Boolean] +true+ if the managed thread is alive,
22
+ # +false+ otherwise
23
+ def running?
24
+ !@thread.nil? && @thread.alive?
25
+ end
26
+
27
+ # Starts a new managed thread if no thread is currently running.
28
+ # The thread invokes the +run+ method of the class that manages it.
29
+ #
30
+ # @param arg the argument to pass to the +#run+ method
31
+ # @return [Thread, nil] the newly created thread, or +nil+ if
32
+ # there was already a running thread
33
+ def start(arg = nil)
34
+ return if running?
35
+
36
+ @thread = Thread.new do
37
+ Thread.current.abort_on_exception = true
38
+ run(arg)
39
+ end
40
+ end
41
+
42
+ # Starts a new managed thread. The thread invokes the +run+ method of the
43
+ # class that manages it.
44
+ #
45
+ # @param arg the argument to pass to the +run+ method
46
+ # @return [Thread] the newly created thread
47
+ # @raise [RuntimeError] if there was already a running thread
48
+ def start!(arg = nil)
49
+ raise "#{self.class.name} is already running" unless start(arg)
50
+ end
51
+
52
+ # Stops the managed thread if a thread is currently running.
53
+ #
54
+ # @return [Thread, nil] the stopped thread, or +nil+ if
55
+ # no thread was running
56
+ def stop
57
+ return unless running?
58
+
59
+ @thread.exit
60
+ end
61
+
62
+ # Stops the managed thread.
63
+ #
64
+ # @return [Thread] the stopped thread
65
+ # @raise [RuntimeError] if there was no running thread
66
+ def stop!
67
+ raise "#{self.class.name} is already stopped" unless stop
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codebot
4
+ # This class serves as a parent class for errors caused by user actions.
5
+ class UserError < RuntimeError
6
+ # Constructs a new user error.
7
+ #
8
+ # @param message [String] the error message
9
+ def initialize(message)
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/user_error'
4
+
5
+ module Codebot
6
+ # This exception stores information about an error that occurred due to a
7
+ # failed validation, for example when a network with an invalid name is
8
+ # created.
9
+ class ValidationError < UserError
10
+ # Constructs a new validation error.
11
+ #
12
+ # @param message [String] the error message
13
+ def initialize(message)
14
+ super
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/cryptography'
4
+ require 'codebot/event'
5
+ require 'codebot/integration_manager'
6
+ require 'codebot/request'
7
+
8
+ module Codebot
9
+ # This module provides methods for processing incoming webhooks and
10
+ # dispatching them to the IRC client.
11
+ module WebListener
12
+ # Handles a POST request.
13
+ #
14
+ # @param core [Core] the bot to dispatch requests to
15
+ # @param request [Sinatra::Request] the request to handle
16
+ # @param params [Hash] the request parameters
17
+ def handle_post(core, request, params)
18
+ payload = params['payload'] || request.body.read
19
+ dispatch(core, request, *params['splat'], payload)
20
+ rescue JSON::ParserError
21
+ [400, 'Invalid JSON payload']
22
+ end
23
+
24
+ # Finds the integration associated with an endpoint.
25
+ #
26
+ # @param config [Config] the configuration containing the integrations
27
+ # @param endpoint [String] the endpoint
28
+ def integration_for(config, endpoint)
29
+ IntegrationManager.new(config).find_integration_by_endpoint(endpoint)
30
+ end
31
+
32
+ # Dispatches a received payload to the IRC client.
33
+ #
34
+ # @param core [Core] the bot to dispatch this request to
35
+ # @param request [Sinatra::Request] the request received by the web server
36
+ # @param endpoint [String] the endpoint at which the request was received
37
+ # @param payload [String] the payload that was sent to the endpoint
38
+ # @return [Array<Integer, String>] HTTP status code and response
39
+ def dispatch(core, request, endpoint, payload)
40
+ intg = integration_for(core.config, endpoint)
41
+ return [404, 'Endpoint not registered'] if intg.nil?
42
+ return [403, 'Invalid signature'] unless valid?(request, intg, payload)
43
+
44
+ req = create_request(intg, request, payload)
45
+ return req if req.is_a? Array
46
+
47
+ core.irc_client.dispatch(req)
48
+ [202, 'Accepted']
49
+ end
50
+
51
+ def create_gitlab_event(request)
52
+ event = request.env['HTTP_X_GITLAB_EVENT']
53
+ return [400, 'Missing event header'] if event.nil? || event.empty?
54
+
55
+ Event.symbolize("Gitlab #{event}".tr(' ', '_'))
56
+ end
57
+
58
+ def create_github_event(request)
59
+ event = request.env['HTTP_X_GITHUB_EVENT']
60
+ return [400, 'Missing event header'] if event.nil? || event.empty?
61
+
62
+ Event.symbolize(event)
63
+ end
64
+
65
+ # Creates a new request for the webhook.
66
+ #
67
+ # @param integration [Integration] the integration for which the request
68
+ # was made
69
+ # @param request [Sinatra::Request] the request received by the web server
70
+ # @param payload [String] the payload that was sent to the endpoint
71
+ # @return [Request, Array<Integer, String>] the created request, or an
72
+ # array containing HTTP status
73
+ # code and response if the
74
+ # request was invalid
75
+ def create_request(integration, request, payload)
76
+ event = if integration.gitlab
77
+ create_gitlab_event(request)
78
+ else
79
+ create_github_event(request)
80
+ end
81
+ return event if event.is_a? Array
82
+
83
+ return [501, 'Event not yet supported'] if event.nil?
84
+
85
+ Request.new(integration, event, payload)
86
+ end
87
+
88
+ # Verifies a webhook signature.
89
+ #
90
+ # @param request [Sinatra::Request] the request received by the web server
91
+ # @param integration [Integration] the integration for which the request
92
+ # was made
93
+ # @param payload [String] the payload that was sent to the endpoint
94
+ # @return [Boolean] whether the signature is valid
95
+ def valid?(request, integration, payload)
96
+ return true unless integration.verify_payloads?
97
+
98
+ secret = integration.secret
99
+ if integration.gitlab
100
+ secret == request.env['HTTP_X_GITLAB_TOKEN']
101
+ else
102
+ request_signature = request.env['HTTP_X_HUB_SIGNATURE']
103
+ Cryptography.valid_signature?(payload, secret, request_signature)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/thread_controller'
4
+ require 'codebot/web_listener'
5
+ require 'codebot/user_error'
6
+
7
+ require 'sinatra'
8
+
9
+ module Codebot
10
+ # This class manages a {WebListener} that runs in a separate thread.
11
+ class WebServer < ThreadController
12
+ extend Sanitizers
13
+
14
+ # Creates a new web server.
15
+ #
16
+ # @param core [Core] the bot this server belongs to
17
+ def initialize(core)
18
+ @core = core
19
+ end
20
+
21
+ # Starts this web server.
22
+ def run(*)
23
+ create_server.run!
24
+ end
25
+
26
+ # Creates a +Proc+ for configuring this web server.
27
+ #
28
+ # @return [Proc] the proc
29
+ def self.configuration
30
+ server = self
31
+ proc do
32
+ disable :traps
33
+ set :bind, ENV['CODEBOT_BIND'] unless ENV['CODEBOT_BIND'].to_s.empty?
34
+ port = ENV['CODEBOT_PORT']
35
+ if port.is_a?(String) && !port.empty?
36
+ set :port, (server.valid! port, server.valid_port(port), nil,
37
+ invalid_error: 'invalid port %s')
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Creates a new Sinatra server for handling incoming requests.
45
+ #
46
+ # @return [Class] the created server
47
+ def create_server
48
+ core = @core
49
+ Sinatra.new do
50
+ include WebListener
51
+
52
+ configure { instance_eval(&WebServer.configuration) }
53
+ post('/*') { handle_post(core, request, params) }
54
+ error(Sinatra::NotFound) { [405, 'Method not allowed'] }
55
+ end
56
+ end
57
+ end
58
+ end
Binary file
metadata ADDED
@@ -0,0 +1,249 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codebot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Janik Rabe
8
+ - Ola Bini
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2019-03-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '12.3'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '12.3'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.8'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.8'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubocop
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.65.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.65.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: cinch
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.3'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: cinch-identify
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.7'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.7'
98
+ - !ruby/object:Gem::Dependency
99
+ name: irb
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '1.0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '1.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: sinatra
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '2.0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '2.0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: thor
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: 0.20.0
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 0.20.0
140
+ description: Codebot is an IRC bot that receives GitHub webhooks and forwards them
141
+ to IRC channels. It is designed to send messages in a format similar to that of
142
+ the official GitHub IRC Service. Codebot is able to stay connected after sending
143
+ messages. This eliminates the delays and visual clutter caused by reconnecting each
144
+ time a new message has to be delivered.
145
+ email:
146
+ - codebot@olabini.se
147
+ executables:
148
+ - codebot
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - ".github/ISSUE_TEMPLATE.md"
153
+ - ".github/ISSUE_TEMPLATE/formatter_issue.md"
154
+ - ".github/PULL_REQUEST_TEMPLATE.md"
155
+ - ".gitignore"
156
+ - ".rspec"
157
+ - ".rubocop.yml"
158
+ - ".travis.yml"
159
+ - CODE_OF_CONDUCT.md
160
+ - CONTRIBUTING.md
161
+ - Gemfile
162
+ - Gemfile.lock
163
+ - LICENSE
164
+ - README.md
165
+ - Rakefile
166
+ - bin/console
167
+ - codebot.gemspec
168
+ - exe/codebot
169
+ - lib/codebot.rb
170
+ - lib/codebot/channel.rb
171
+ - lib/codebot/command_error.rb
172
+ - lib/codebot/config.rb
173
+ - lib/codebot/configuration_error.rb
174
+ - lib/codebot/core.rb
175
+ - lib/codebot/cryptography.rb
176
+ - lib/codebot/event.rb
177
+ - lib/codebot/ext/cinch/ssl_extensions.rb
178
+ - lib/codebot/formatter.rb
179
+ - lib/codebot/formatters.rb
180
+ - lib/codebot/formatters/.rubocop.yml
181
+ - lib/codebot/formatters/commit_comment.rb
182
+ - lib/codebot/formatters/fork.rb
183
+ - lib/codebot/formatters/gitlab_issue_hook.rb
184
+ - lib/codebot/formatters/gitlab_job_hook.rb
185
+ - lib/codebot/formatters/gitlab_merge_request_hook.rb
186
+ - lib/codebot/formatters/gitlab_note_hook.rb
187
+ - lib/codebot/formatters/gitlab_pipeline_hook.rb
188
+ - lib/codebot/formatters/gitlab_push_hook.rb
189
+ - lib/codebot/formatters/gitlab_wiki_page_hook.rb
190
+ - lib/codebot/formatters/gollum.rb
191
+ - lib/codebot/formatters/issue_comment.rb
192
+ - lib/codebot/formatters/issues.rb
193
+ - lib/codebot/formatters/ping.rb
194
+ - lib/codebot/formatters/public.rb
195
+ - lib/codebot/formatters/pull_request.rb
196
+ - lib/codebot/formatters/pull_request_review_comment.rb
197
+ - lib/codebot/formatters/push.rb
198
+ - lib/codebot/formatters/watch.rb
199
+ - lib/codebot/integration.rb
200
+ - lib/codebot/integration_manager.rb
201
+ - lib/codebot/ipc_client.rb
202
+ - lib/codebot/ipc_server.rb
203
+ - lib/codebot/irc_client.rb
204
+ - lib/codebot/irc_connection.rb
205
+ - lib/codebot/message.rb
206
+ - lib/codebot/metadata.rb
207
+ - lib/codebot/network.rb
208
+ - lib/codebot/network_manager.rb
209
+ - lib/codebot/options.rb
210
+ - lib/codebot/options/base.rb
211
+ - lib/codebot/options/core.rb
212
+ - lib/codebot/options/integration.rb
213
+ - lib/codebot/options/network.rb
214
+ - lib/codebot/payload.rb
215
+ - lib/codebot/request.rb
216
+ - lib/codebot/sanitizers.rb
217
+ - lib/codebot/serializable.rb
218
+ - lib/codebot/shortener.rb
219
+ - lib/codebot/thread_controller.rb
220
+ - lib/codebot/user_error.rb
221
+ - lib/codebot/validation_error.rb
222
+ - lib/codebot/web_listener.rb
223
+ - lib/codebot/web_server.rb
224
+ - webhook.png
225
+ homepage: https://github.com/olabini/codebot
226
+ licenses:
227
+ - MIT
228
+ metadata: {}
229
+ post_install_message:
230
+ rdoc_options: []
231
+ require_paths:
232
+ - lib
233
+ required_ruby_version: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: 2.2.0
238
+ required_rubygems_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0'
243
+ requirements: []
244
+ rubyforge_project:
245
+ rubygems_version: 2.7.8
246
+ signing_key:
247
+ specification_version: 4
248
+ summary: Forward GitHub webhooks to IRC channels
249
+ test_files: []