gitlab-system-hooks-receiver 0.0.1
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 +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +8 -0
- data/Gemfile +8 -0
- data/Gemfile.ci +6 -0
- data/LICENSE +674 -0
- data/README.md +96 -0
- data/Rakefile +33 -0
- data/config.ru +51 -0
- data/config.yaml.example +6 -0
- data/gitlab-system-hooks-receiver.gemspec +28 -0
- data/lib/gitlab-system-hooks-receiver.rb +146 -0
- data/lib/gitlab-system-hooks-receiver/version.rb +4 -0
- data/test/run-test.rb +34 -0
- data/test/system-hooks-receiver-test.rb +86 -0
- data/test/test-utils.rb +38 -0
- metadata +146 -0
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# GitLabSystemHooksReceiver
|
2
|
+
|
3
|
+
Process GitLab system hooks.
|
4
|
+
|
5
|
+
For now, this Rack based web application appends web hooks to a new project on GitLab via system hooks.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem "gitlab-system-hooks-receiver"
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install gitlab-system-hooks-receiver
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Use part of git-utils/github-post-receiver
|
24
|
+
|
25
|
+
You can use this web application with [clear-code/git-utils](https://github.com/clear-code/git-utils "clear-code/git-utils").
|
26
|
+
|
27
|
+
Clone [clear-code/git-utils](https://github.com/clear-code/git-utils "clear-code/git-utils").
|
28
|
+
|
29
|
+
```
|
30
|
+
$ git clone https://github.com/clear-code/git-utils.git
|
31
|
+
```
|
32
|
+
|
33
|
+
Add Gemfile.
|
34
|
+
|
35
|
+
```
|
36
|
+
source "https://rubygems.org/"
|
37
|
+
|
38
|
+
gem "rack"
|
39
|
+
gem "racknga"
|
40
|
+
gem "unicorn"
|
41
|
+
gem "gitlab-system-hooks-receiver"
|
42
|
+
```
|
43
|
+
|
44
|
+
NOTE: You can use passenger instead of unicorn.
|
45
|
+
|
46
|
+
Do `bundle install`.
|
47
|
+
|
48
|
+
Edit config.ru.
|
49
|
+
|
50
|
+
```
|
51
|
+
...
|
52
|
+
require "gitlab-system-hooks-receiver"
|
53
|
+
map "/system-hooks-receiver/" do
|
54
|
+
run GitLabSystemHooksReceiver.new(options)
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
Configure your web server if you need.
|
59
|
+
|
60
|
+
### Use standalone
|
61
|
+
|
62
|
+
You can use this web application standalone.
|
63
|
+
|
64
|
+
Clone this repository.
|
65
|
+
|
66
|
+
```
|
67
|
+
$ git clone https://github.com/clear-code/gitlab-system-hooks-receiver.git
|
68
|
+
$ cd gitlab-system-hooks-receiver
|
69
|
+
$ bundle install --path vendor/bundle
|
70
|
+
$ bundle exec unicorn
|
71
|
+
```
|
72
|
+
|
73
|
+
Configure your web server if you need.
|
74
|
+
|
75
|
+
### Configuration
|
76
|
+
|
77
|
+
Add your config.yaml as followings.
|
78
|
+
|
79
|
+
config.yaml
|
80
|
+
```
|
81
|
+
# private token for the user who has admin privilege
|
82
|
+
private_token: VERY_SECRET_TOKEN
|
83
|
+
api_end_point: https://gitlab.example.com/api/v3
|
84
|
+
web_hooks:
|
85
|
+
- http://hook.example.com/post-receiver1
|
86
|
+
- http://hook.example.com/post-receiver2
|
87
|
+
- http://hook.example.com/post-receiver3
|
88
|
+
```
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
1. Fork it ( http://github.com/clear-code/gitlab-system-hooks-receiver/fork )
|
93
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
94
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
95
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
96
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright (C) 2014 Kenji Okimoto <okimoto@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_helper"
|
17
|
+
|
18
|
+
module Bundler
|
19
|
+
class GemHelper
|
20
|
+
def version_tag
|
21
|
+
"#{version}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "bundler/gem_tasks"
|
27
|
+
|
28
|
+
task :default => :test
|
29
|
+
|
30
|
+
desc "Run test"
|
31
|
+
task :test do
|
32
|
+
ruby("test/run-test.rb")
|
33
|
+
end
|
data/config.ru
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Kenji Okimoto <okimoto@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 "gitlab-system-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 "/system-hooks-receiver/" do
|
50
|
+
run GitLabSystemHooksReceiver.new(options)
|
51
|
+
end
|
data/config.yaml.example
ADDED
@@ -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 "gitlab-system-hooks-receiver/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gitlab-system-hooks-receiver"
|
8
|
+
spec.version = GitLabSystemHooksReceiver::VERSION
|
9
|
+
spec.authors = ["Kenji Okimoto"]
|
10
|
+
spec.email = ["okimoto@clear-code.com"]
|
11
|
+
spec.summary = %q{Process GitLab System hooks.}
|
12
|
+
spec.description = %q{Process GitLab System hooks.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "GPLv3 or later"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
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,146 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Kenji Okimoto <okimoto@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 "net/http"
|
19
|
+
require "net/https"
|
20
|
+
require "webrick/httpstatus"
|
21
|
+
require "json"
|
22
|
+
|
23
|
+
class GitLabSystemHooksReceiver
|
24
|
+
|
25
|
+
def initialize(options={})
|
26
|
+
@options = symbolize_options(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
request = Rack::Request.new(env)
|
31
|
+
response = Rack::Response.new
|
32
|
+
process(request, response)
|
33
|
+
response.to_a
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def production?
|
39
|
+
ENV["RACK_ENV"] == "production"
|
40
|
+
end
|
41
|
+
|
42
|
+
def symbolize_options(options)
|
43
|
+
symbolized_options = {}
|
44
|
+
options.each do |key, value|
|
45
|
+
symbolized_options[key.to_sym] = value
|
46
|
+
end
|
47
|
+
symbolized_options
|
48
|
+
end
|
49
|
+
|
50
|
+
KEYWORD_TO_HTTP_STATUS_CODE = {}
|
51
|
+
WEBrick::HTTPStatus::StatusMessage.each do |code, message|
|
52
|
+
KEYWORD_TO_HTTP_STATUS_CODE[message.downcase.gsub(/ +/, '_').intern] = code
|
53
|
+
end
|
54
|
+
|
55
|
+
def status(keyword)
|
56
|
+
code = KEYWORD_TO_HTTP_STATUS_CODE[keyword]
|
57
|
+
if code.nil?
|
58
|
+
raise ArgumentError, "invalid status keyword: #{keyword.inspect}"
|
59
|
+
end
|
60
|
+
code
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_error_response(response, status_keyword, message)
|
64
|
+
response.status = status(status_keyword)
|
65
|
+
response["Content-Type"] = "text/plain"
|
66
|
+
response.write(message)
|
67
|
+
end
|
68
|
+
|
69
|
+
def process(request, response)
|
70
|
+
unless request.post?
|
71
|
+
set_error_response(response, :method_not_allowed, "must POST")
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
payload = parse_payload(request, response)
|
76
|
+
return if payload.nil?
|
77
|
+
process_payload(request, response, payload)
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_payload(request, response)
|
81
|
+
if request.content_type == "application/json"
|
82
|
+
payload = request.body.read
|
83
|
+
else
|
84
|
+
payload = request["payload"]
|
85
|
+
end
|
86
|
+
if payload.nil?
|
87
|
+
set_error_response(response, :bad_request, "payload is missing")
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
JSON.parse(payload)
|
93
|
+
rescue JSON::ParserError
|
94
|
+
set_error_response(response, :bad_request,
|
95
|
+
"invalid JSON format: <#{$!.message}>")
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def process_payload(request, response, payload)
|
101
|
+
event_name = payload["event_name"]
|
102
|
+
__send__("process_#{event_name}_event", request, response, payload)
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_project_create_event(request, response, payload)
|
106
|
+
# TODO notify to owner
|
107
|
+
owner_email = payload["owner_email"]
|
108
|
+
project_id = payload["project_id"]
|
109
|
+
@options[:web_hooks].each do |web_hook|
|
110
|
+
add_project_hook(project_id, web_hook)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def method_missing(name, *args)
|
115
|
+
if name =~ /\Aprocess_.*_event\z/
|
116
|
+
puts name
|
117
|
+
else
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# POST #{GitLabURI}/projects/:id/hooks
|
124
|
+
#
|
125
|
+
def add_project_hook(project_id, hook_uri)
|
126
|
+
api_end_point_uri = URI.parse(@options[:api_end_point])
|
127
|
+
path = File.join(api_end_point_uri.path, "projects", project_id.to_s, "hooks")
|
128
|
+
post_request = Net::HTTP::Post.new(path)
|
129
|
+
# push_events is enabled by default
|
130
|
+
post_request.set_form_data("url" => hook_uri,
|
131
|
+
"private_token" => @options[:private_token])
|
132
|
+
http = Net::HTTP.new(api_end_point_uri.host, api_end_point_uri.port)
|
133
|
+
if api_end_point_uri.scheme == "https"
|
134
|
+
http.use_ssl = true
|
135
|
+
http.ca_path = "/etc/ssl/certs"
|
136
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
137
|
+
http.verify_depth = 5
|
138
|
+
end
|
139
|
+
response = nil
|
140
|
+
http.start do
|
141
|
+
response = http.request(post_request)
|
142
|
+
end
|
143
|
+
|
144
|
+
response
|
145
|
+
end
|
146
|
+
end
|
data/test/run-test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010-2013 Kouhei Sutou <kou@clear-code.com>
|
4
|
+
# Copyright (C) 2014 Kenji Okimoto <okimoto@clear-code.com>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
$VERBOSE = true
|
20
|
+
|
21
|
+
base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
22
|
+
lib_dir = File.join(base_dir, "lib")
|
23
|
+
test_dir = File.join(base_dir, "test")
|
24
|
+
|
25
|
+
require "test-unit"
|
26
|
+
require "test/unit/capybara"
|
27
|
+
require "test/unit/rr"
|
28
|
+
|
29
|
+
$LOAD_PATH.unshift(lib_dir)
|
30
|
+
|
31
|
+
$LOAD_PATH.unshift(test_dir)
|
32
|
+
require "test-utils"
|
33
|
+
|
34
|
+
exit Test::Unit::AutoRunner.run(true, test_dir)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright (C) 2014 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 "yaml"
|
18
|
+
require "gitlab-system-hooks-receiver"
|
19
|
+
|
20
|
+
class SystemHooksReceiverTest < Test::Unit::TestCase
|
21
|
+
include TestUtils
|
22
|
+
|
23
|
+
def setup
|
24
|
+
test_dir = File.dirname(__FILE__)
|
25
|
+
@fixtures_dir = File.join(test_dir, "fixtures")
|
26
|
+
@tmp_dir = File.join(test_dir, "tmp")
|
27
|
+
FileUtils.mkdir_p(@tmp_dir)
|
28
|
+
Capybara.app = app
|
29
|
+
end
|
30
|
+
|
31
|
+
def teardown
|
32
|
+
FileUtils.rm_rf(@tmp_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
def app
|
36
|
+
GitLabSystemHooksReceiver.new(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_get
|
40
|
+
visit "/"
|
41
|
+
assert_response("Method Not Allowed")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_post_without_parameters
|
45
|
+
page.driver.post("/")
|
46
|
+
assert_response("Bad Request")
|
47
|
+
assert_equal("payload is missing", body)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_create_project
|
51
|
+
@options[:web_hooks].each do |web_hook|
|
52
|
+
mock(Capybara.app).add_project_hook(42, web_hook)
|
53
|
+
end
|
54
|
+
payload = {
|
55
|
+
created_at: "2014-03-13T07:30:54Z",
|
56
|
+
event_name: "project_create",
|
57
|
+
name: "ExampleProject",
|
58
|
+
owner_email: "johnsmith@example.com",
|
59
|
+
owner_name: "John Smith",
|
60
|
+
path: "exampleproject",
|
61
|
+
path_with_namespace: "jsmith/exampleproject",
|
62
|
+
project_id: 42,
|
63
|
+
}
|
64
|
+
post_payload(payload)
|
65
|
+
assert_response("OK")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def post_payload(payload)
|
71
|
+
page.driver.post("/", :payload => JSON.generate(payload))
|
72
|
+
end
|
73
|
+
|
74
|
+
def options
|
75
|
+
@options ||= {
|
76
|
+
:private_token => "VERYSECRETTOKEN",
|
77
|
+
:gitlab_api_end_point_uri => "https://gitlab.example.com/api/v3",
|
78
|
+
:web_hooks => [
|
79
|
+
"https://hook.example.com/post-receiver1",
|
80
|
+
"https://hook.example.com/post-receiver2",
|
81
|
+
"https://hook.example.com/post-receiver3"
|
82
|
+
]
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|