github-web-hooks-receiver 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de99b9c01f899326fd8b4cf3422244ab1afbe6bc
4
+ data.tar.gz: 0638ab1bc10af285fce9e117074df11959e02975
5
+ SHA512:
6
+ metadata.gz: 9e1ef74e2f4d9d411c7d69e4e9cb87b56c5396db505534b7cd3f51b85e31778255deaea5a6a6dadd90d0d4437b84c6214980b6557371067ffa7b5cea8938d94b
7
+ data.tar.gz: fd4b1e4779938c54b116d4d7a1ccddbf77699c0f4ec97bf61747578dced26904a7622394fa12be39d2373010e8dddb3d27f23451409d362c0d52b9bfc9f08147
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /vendor/
4
+ /test/.test-result/
5
+
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 2.0.0
3
+ - 2.1
4
+ - 2.2
5
+ notifications:
6
+ email:
7
+ recipients:
8
+ - commit@clear-code.com
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ [![Build Status](https://travis-ci.org/clear-code/github-web-hooks-receiver.svg?branch=master)](https://travis-ci.org/clear-code/github-web-hooks-receiver)
2
+
3
+ # GitHub Web hooks receiver
4
+
5
+ This is a Rack based web application that can process POST request from GitHub, GitLab and GHE.
6
+
7
+ ## Set up
8
+
9
+ Prepare following files.
10
+
11
+ /home/github-web-hooks-receiver/github-web-hooks-receiver/Gemfile:
12
+ ```
13
+ source "https://rubygems.org"
14
+ gem "github-web-hooks-receiver"
15
+ # gem "unicorn" # Enable this line if you use Unicorn.
16
+ # gem "passenger" # Enable this line if you use latest Passenger.
17
+ ```
18
+
19
+ /home/github-web-hooks-receiver/github-web-hooks-receiver/config.ru:
20
+ ```ruby
21
+ require "yaml"
22
+ require "pathname"
23
+ require "github-web-hooks-receiver"
24
+
25
+ use Rack::CommonLogger
26
+ use Rack::Runtime
27
+ use Rack::ContentLength
28
+
29
+ base_dir = Pathname(__FILE__).dirname
30
+ config_file = base_dir + "config.yaml"
31
+
32
+ options = YAML.load_file(config_file.to_s)
33
+
34
+ map "/post-receiver/" do
35
+ run GitHubWebHooksReceiver::App.new(options)
36
+ end
37
+ ```
38
+
39
+ /home/github-web-hooks-receiver/github-web-hooks-receiver/config.yaml:
40
+ ```
41
+ to: receiver@example.com
42
+ sender: sender@example.com
43
+ add_html: true
44
+ owners:
45
+ groonga:
46
+ to: groonga-commit@lists.sourceforge.jp
47
+ ```
48
+
49
+ ### Apache + Passenger
50
+
51
+ On Debian GNU/Linux wheezy.
52
+
53
+ See also [Phusion Passenger users guide, Apache version](https://www.phusionpassenger.com/documentation/Users%20guide%20Apache.html).
54
+
55
+ Install Passenger or write `gem "passenger"` in your Gemfile.
56
+
57
+ ```
58
+ $ sudo apt-get install -y ruby-passenger
59
+ ```
60
+
61
+ Install gems.
62
+
63
+ ```
64
+ $ sudo -u github-web-hooks-receiver -H bundle install --path vendor/bundle
65
+ ```
66
+
67
+ Prepare following files.
68
+
69
+ /etc/apache2/mods-available.conf:
70
+ ```
71
+ PassengerRoot /path/to/passenger-x.x.x
72
+ PassengerRuby /path/to/ruby
73
+
74
+ PassengerMaxRequests 100
75
+ ```
76
+
77
+ /etc/apache2/mods-available.load:
78
+ ```
79
+ LoadModule passenger_module /path/to/mod_passenger.so
80
+ ```
81
+
82
+ /etc/apache2/sites-available/github-web-hooks-receiver:
83
+ ```
84
+ <VirtualHost *:80>
85
+ ServerName github-web-hooks-receiver.example.com
86
+ DocumentRoot /home/github-web-hooks-receiver/github-web-hooks-receiver/public
87
+ <Directory /home/github-web-hooks-receiver/github-web-hooks-receiver/public>
88
+ AllowOverride all
89
+ Options -MultiViews
90
+ </Directory>
91
+
92
+ ErrorLog ${APACHE_LOG_DIR}/github-web-hooks-receiver_error.log
93
+ CustomLog ${APACHE_LOG_DIR}/github-web-hooks-receiver_access.log combined
94
+
95
+ AllowEncodedSlashes On
96
+ AcceptPathInfo On
97
+ </VirtualHost>
98
+ ```
99
+
100
+ Enable the module.
101
+
102
+ ```
103
+ $ sudo a2enmod passenger
104
+ ```
105
+
106
+ Enable the virtual host.
107
+
108
+ ```
109
+ $ sudo a2ensite github-web-hooks-receiver
110
+ ```
111
+
112
+ Restart web server.
113
+
114
+ ```
115
+ $ sudo service apache2 restart
116
+ ```
117
+
118
+ ### Nginx + Unicorn
119
+
120
+ Prepare following files.
121
+
122
+ /etc/nginx/sites-enabled/github-web-hooks-receiver:
123
+ ```
124
+ upstream github-web-hooks-receiver {
125
+ server unix:/tmp/unicorn-github-web-hooks-receiver.sock;
126
+ }
127
+
128
+ server {
129
+ listen 80;
130
+ server_name github-web-hooks-receiver.example.com;
131
+ access_log /var/log/nginx/github-web-hooks-receiver.example.com-access.log combined;
132
+
133
+ root /srv/www/github-web-hooks-receiver;
134
+ index index.html;
135
+
136
+ proxy_set_header X-Real-IP $remote_addr;
137
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
138
+ proxy_set_header Host $http_host;
139
+ #proxy_redirect off;
140
+
141
+ location / {
142
+ root /home/github-web-hooks-receiver/github-web-hooks-receiver/public;
143
+ include maintenance;
144
+ if (-f $request_filename){
145
+ break;
146
+ }
147
+ if (!-f $request_filename){
148
+ proxy_pass http://github-web-hooks-receiver;
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ /home/github-web-hooks-receiver/github-web-hooks-receiver/unicorn.conf:
156
+ ```
157
+ # -*- ruby -*-
158
+ worker_processes 2
159
+ working_directory "/home/github-web-hooks-receiver/github-web-hooks-receiver"
160
+ listen '/tmp/unicorn-github-post-receiver.sock', :backlog => 1
161
+ timeout 120
162
+ pid 'tmp/pids/unicorn.pid'
163
+ preload_app true
164
+ stderr_path 'log/unicorn.log'
165
+ stdout_path "log/stdout.log"
166
+ user "github-web-hooks-receiver", "github-web-hooks-receiver"
167
+ ```
168
+
169
+ /home/github-web-hooks-receiver/bin/github-web-hooks-receiver:
170
+ ```
171
+ #! /bin/zsh
172
+ BASE_DIR=/home/github-web-hooks-receiver/github-web-hooks-receiver
173
+ export RACK_ENV=production
174
+ cd $BASE_DIR
175
+ rbenv version
176
+
177
+ command=$1
178
+
179
+ function start() {
180
+ mkdir -p $BASE_DIR/tmp/pids
181
+ mkdir -p $BASE_DIR/log
182
+ bundle exec unicorn -D -c unicorn.conf config.ru
183
+ }
184
+
185
+ function stop() {
186
+ kill $(cat $BASE_DIR/tmp/pids/unicorn.pid)
187
+ }
188
+
189
+ function restart() {
190
+ kill -USR2 $(cat $BASE_DIR/tmp/pids/unicorn.pid)
191
+ }
192
+
193
+ $command
194
+ ```
195
+
196
+ Install gems.
197
+
198
+ ```
199
+ $ sudo -u github-web-hooks-receiver -H bundle install --path vendor/bundle
200
+ ```
201
+
202
+ Run the application.
203
+
204
+ ```
205
+ $ sudo -u github-web-hooks-receiver -H ~github-web-hooks-receiver/bin/github-web-hooks-receiver start
206
+ ```
207
+
208
+ ## Configuration
209
+
210
+ You need to edit config.yaml to configure this web application.
211
+ See config.yaml.example and test codes.
212
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require "bundler/gem_tasks"
17
+
18
+ task :default => :test
19
+
20
+ desc "Run test"
21
+ task :test do
22
+ ruby("test/run-test.rb")
23
+ end
data/config.ru ADDED
@@ -0,0 +1,52 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require "yaml"
19
+
20
+ require "pathname"
21
+
22
+ base_dir = Pathname(__FILE__).dirname
23
+ lib_dir = base_dir + "lib"
24
+
25
+ racknga_base_dir = base_dir.parent.parent + "racknga"
26
+ racknga_lib_dir = racknga_base_dir + "lib"
27
+
28
+ $LOAD_PATH.unshift(racknga_lib_dir.to_s)
29
+ $LOAD_PATH.unshift(lib_dir.to_s)
30
+
31
+ require "github-web-hooks-receiver"
32
+
33
+ require "racknga/middleware/exception_notifier"
34
+
35
+ use Rack::CommonLogger
36
+ use Rack::Runtime
37
+ use Rack::ContentLength
38
+
39
+ config_file = base_dir + "config.yaml"
40
+ options = YAML.load_file(config_file.to_s)
41
+ notifier_options = options.dup
42
+ if options[:error_to]
43
+ notifier_options[:to] = options[:error_to]
44
+ end
45
+ notifier_options.merge!(options["exception_notifier"] || {})
46
+ notifiers = [Racknga::ExceptionMailNotifier.new(notifier_options)]
47
+ use Racknga::Middleware::ExceptionNotifier, :notifiers => notifiers
48
+
49
+ map "/post-receiver/" do
50
+ run GitHubWebHooksReceiver::App.new(options)
51
+ end
52
+
@@ -0,0 +1,20 @@
1
+ private_token: VERYSECRETTOKEN
2
+ gitlab_api_end_point: https://gitlab.example.com/api/v3
3
+ web_hooks:
4
+ - http://gh.example.com/post-receiver1
5
+ - http://gh.example.com/post-receiver2
6
+ - http://gh.example.com/post-receiver3
7
+ to: receiver@example.com
8
+ error_to: admin@example.com
9
+ exception_notifier:
10
+ subject_label: "[git-utils]"
11
+ sender: sender@example.com
12
+ add_html: true
13
+ owners:
14
+ ranguba:
15
+ to: groonga-commit@rubyforge.org
16
+ repositories:
17
+ examples:
18
+ to: null@example.com
19
+ groonga:
20
+ to: groonga-commit@lists.sourceforge.jp
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'github-web-hooks-receiver/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "github-web-hooks-receiver"
8
+ spec.version = GitHubWebHooksReceiver::VERSION
9
+ spec.authors = ["Kouhei Sutou", "Kenji Okimoto"]
10
+ spec.email = ["kou@clear-code.com", "okimoto@clear-code.com"]
11
+ spec.summary = %q{GitHub web hook receiver}
12
+ spec.description = %q{GitHub web hook receiver}
13
+ spec.homepage = ""
14
+ spec.license = "GPL-3.0+"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "rack"
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "test-unit"
26
+ spec.add_development_dependency "test-unit-rr"
27
+ spec.add_development_dependency "test-unit-capybara"
28
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
2
+ # Copyright (C) 2015 Kenji Okimoto <okimoto@clear-code.com>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require "fileutils"
18
+ require "webrick/httpstatus"
19
+ require "shellwords"
20
+ require "uri"
21
+
22
+ require "rubygems"
23
+ require "json"
24
+
25
+ module GitHubWebHooksReceiver
26
+ end
27
+
28
+ require "github-web-hooks-receiver/app"
29
+ require "github-web-hooks-receiver/version"
@@ -0,0 +1,239 @@
1
+ # Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
2
+ # Copyright (C) 2015 Kenji Okimoto <okimoto@clear-code.com>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require "github-web-hooks-receiver/base"
18
+ require "github-web-hooks-receiver/path-resolver"
19
+ require "github-web-hooks-receiver/payload"
20
+ require "github-web-hooks-receiver/repository"
21
+
22
+ module GitHubWebHooksReceiver
23
+ class App < Base
24
+ include PathResolver
25
+
26
+ private
27
+
28
+ def process_payload(request, response, raw_payload)
29
+ metadata = {
30
+ "x-github-event" => github_event(request),
31
+ }
32
+ payload = Payload.new(raw_payload, metadata)
33
+ case payload.event_name
34
+ when "ping"
35
+ # Do nothing
36
+ when "push", nil # nil is for GitLab
37
+ process_push_payload(request, response, payload)
38
+ when "gollum"
39
+ process_gollum_payload(request, response, payload)
40
+ else
41
+ set_error_response(response,
42
+ :bad_request,
43
+ "Unsupported event: <#{payload.event_name}>")
44
+ end
45
+ end
46
+
47
+ def github_event(request)
48
+ request.env["HTTP_X_GITHUB_EVENT"]
49
+ end
50
+
51
+ def process_push_payload(request, response, payload)
52
+ repository = process_payload_repository(request, response, payload)
53
+ return if repository.nil?
54
+ change = process_push_parameters(request, response, payload)
55
+ return if change.nil?
56
+ repository.process(*change)
57
+ end
58
+
59
+ def process_gollum_payload(request, response, payload)
60
+ repository = process_payload_repository(request, response, payload)
61
+ return if repository.nil?
62
+ change = process_gollum_parameters(request, response, payload)
63
+ return if change.nil?
64
+ repository.process(*change)
65
+ end
66
+
67
+ def process_payload_repository(request, response, payload)
68
+ repository = payload["repository"]
69
+ if repository.nil?
70
+ set_error_response(response, :bad_request,
71
+ "repository information is missing")
72
+ return
73
+ end
74
+
75
+ unless repository.is_a?(Hash)
76
+ set_error_response(response, :bad_request,
77
+ "invalid repository information format: " +
78
+ "<#{repository.inspect}>")
79
+ return
80
+ end
81
+
82
+ repository_uri = repository["url"]
83
+ domain = extract_domain(repository_uri)
84
+ if domain.nil?
85
+ set_error_response(response, :bad_request,
86
+ "invalid repository URI: <#{repository.inspect}>")
87
+ return
88
+ end
89
+
90
+ repository_name = repository["name"]
91
+ if repository_name.nil?
92
+ set_error_response(response, :bad_request,
93
+ "repository name is missing: <#{repository.inspect}>")
94
+ return
95
+ end
96
+
97
+ owner_name = extract_owner_name(repository_uri, payload)
98
+ if owner_name.nil?
99
+ set_error_response(response, :bad_request,
100
+ "repository owner or owner name is missing: " +
101
+ "<#{repository.inspect}>")
102
+ return
103
+ end
104
+
105
+ options = repository_options(domain, owner_name, repository_name)
106
+ repository = repository_class.new(domain, owner_name, repository_name,
107
+ payload, options)
108
+ unless repository.target?
109
+ set_error_response(response, :forbidden,
110
+ "unacceptable repository: " +
111
+ "<#{owner_name.inspect}>:<#{repository_name.inspect}>")
112
+ return
113
+ end
114
+
115
+ repository
116
+ end
117
+
118
+ def extract_domain(repository_uri)
119
+ domain = nil
120
+ case repository_uri
121
+ when /\Agit@/
122
+ domain = repository_uri[/@(.+):/, 1]
123
+ when /\Ahttps:\/\//
124
+ domain = URI.parse(repository_uri).hostname
125
+ else
126
+ return
127
+ end
128
+ domain
129
+ end
130
+
131
+ def extract_owner_name(repository_uri, payload)
132
+ owner_name = nil
133
+ repository = payload["repository"]
134
+ if payload.gitlab?
135
+ case repository_uri
136
+ when /\Agit@/
137
+ owner_name = repository_uri[%r!git@.+:(.+)/.+(?:.git)?!, 1]
138
+ when /\Ahttps:\/\//
139
+ owner_name = URI.parse(repository_uri).path.sub(/\A\//, "")
140
+ else
141
+ return
142
+ end
143
+ else
144
+ owner = repository["owner"]
145
+ return if owner.nil?
146
+
147
+ owner_name = owner["name"] || owner["login"]
148
+ return if owner_name.nil?
149
+ end
150
+ owner_name
151
+ end
152
+
153
+ def process_push_parameters(request, response, payload)
154
+ before = payload["before"]
155
+ if before.nil?
156
+ set_error_response(response, :bad_request,
157
+ "before commit ID is missing")
158
+ return
159
+ end
160
+
161
+ after = payload["after"]
162
+ if after.nil?
163
+ set_error_response(response, :bad_request,
164
+ "after commit ID is missing")
165
+ return
166
+ end
167
+
168
+ reference = payload["ref"]
169
+ if reference.nil?
170
+ set_error_response(response, :bad_request,
171
+ "reference is missing")
172
+ return
173
+ end
174
+
175
+ [before, after, reference]
176
+ end
177
+
178
+ def process_gollum_parameters(request, response, payload)
179
+ pages = payload["pages"]
180
+ if pages.nil?
181
+ set_error_response(response, :bad_request,
182
+ "pages are missing")
183
+ return
184
+ end
185
+ if pages.empty?
186
+ set_error_response(response, :bad_request,
187
+ "no pages")
188
+ end
189
+
190
+ revisions = pages.collect do |page|
191
+ page["sha"]
192
+ end
193
+
194
+ if revisions.size == 1
195
+ after = revisions.first
196
+ before = "#{after}^"
197
+ else
198
+ before = revisions.first
199
+ after = revisions.last
200
+ end
201
+
202
+ reference = "refs/heads/master"
203
+ [before, after, reference]
204
+ end
205
+
206
+ def set_error_response(response, status_keyword, message)
207
+ response.status = status(status_keyword)
208
+ response["Content-Type"] = "text/plain"
209
+ response.write(message)
210
+ end
211
+
212
+ def repository_class
213
+ @options[:repository_class] || Repository
214
+ end
215
+
216
+ def repository_options(domain, owner_name, repository_name)
217
+ domain_options = (@options[:domains] || {})[domain] || {}
218
+ domain_options = symbolize_options(domain_options)
219
+ domain_owner_options = (domain_options[:owners] || {})[owner_name] || {}
220
+ domain_owner_options = symbolize_options(domain_owner_options)
221
+ domain_repository_options = (domain_owner_options[:repositories] || {})[repository_name] || {}
222
+ domain_repository_options = symbolize_options(domain_repository_options)
223
+
224
+ owner_options = (@options[:owners] || {})[owner_name] || {}
225
+ owner_options = symbolize_options(owner_options)
226
+ _repository_options = (owner_options[:repositories] || {})[repository_name] || {}
227
+ _repository_options = symbolize_options(_repository_options)
228
+
229
+ options = @options.merge(owner_options)
230
+ options = options.merge(owner_options)
231
+ options = options.merge(_repository_options)
232
+
233
+ options = options.merge(domain_options)
234
+ options = options.merge(domain_owner_options)
235
+ options = options.merge(domain_repository_options)
236
+ options
237
+ end
238
+ end
239
+ end