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.
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
+