gitlab-system-hooks-receiver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,6 @@
1
+ private_token: VERY_SECRET_TOKEN
2
+ api_end_point: https://gitlab.example.com/api/v3
3
+ web_hooks:
4
+ - http://git-utils.example.com/post-receiver1
5
+ - http://git-utils.example.com/post-receiver2
6
+ - http://git-utils.example.com/post-receiver3
@@ -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
@@ -0,0 +1,4 @@
1
+ # -*- coding: us-ascii -*-
2
+ class GitLabSystemHooksReceiver
3
+ VERSION = "0.0.1"
4
+ 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
+