webhookd 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +176 -0
- data/Rakefile +11 -0
- data/bin/webhookd +8 -0
- data/config.ru +12 -0
- data/etc/example.yml +19 -0
- data/etc/example.yml.dist +12 -0
- data/lib/webhookd.rb +3 -0
- data/lib/webhookd/app.rb +149 -0
- data/lib/webhookd/cli.rb +57 -0
- data/lib/webhookd/command_runner.rb +33 -0
- data/lib/webhookd/configuration.rb +36 -0
- data/lib/webhookd/logging.rb +49 -0
- data/lib/webhookd/payloadtype/bitbucket.rb +43 -0
- data/lib/webhookd/payloadtype/gitlab.rb +31 -0
- data/lib/webhookd/version.rb +3 -0
- data/scripts/test/curl-bitbucket-explicit-repo-and-branch.sh +2 -0
- data/scripts/test/curl-bitbucket-explicit-repo-wrong-branch.sh +2 -0
- data/scripts/test/curl-bitbucket-nocommand.sh +2 -0
- data/scripts/test/curl-bitbucket-unknown-repo.sh +2 -0
- data/scripts/test/curl-gitlab-unknown-repo.sh +2 -0
- data/scripts/webhookd.default +9 -0
- data/scripts/webhookd.init +67 -0
- data/test/helper.rb +14 -0
- data/test/test_basics.rb +24 -0
- data/test/test_bitbucket.rb +49 -0
- data/webhookd.gemspec +30 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 117c81bc0145b051d53dd51e0c85b54c99cc546a
|
4
|
+
data.tar.gz: d1874340cf8f379b5f2e7d9d2f76d7da699dab54
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 59510634125b6e8de310ba419c9b05867dfa6fab2892a472475c888a0bb1d04dc62cf85e6ba0fb58f2eb826ae125e86fd4eff67092d708c3771a9029894ec146
|
7
|
+
data.tar.gz: b2ad48474b9d31a74e171abf5d9b57d1d084821b22142689ff7f6e2e40a86489c1bb68a689556f012be7f8d4fdb61a081885ea400b8a56e7eb943b591cb48d71
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Tobias Brunner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Webhookd
|
2
|
+
|
3
|
+
**Flexible, configurable universal webhook receiver**
|
4
|
+
|
5
|
+
This app is a flexible, configurable universal webhook receiver, built with
|
6
|
+
sinatra.
|
7
|
+
It can receive a webhook, parse its payload and take action according to the
|
8
|
+
configuration.
|
9
|
+
|
10
|
+
Example: A git push to Gitlab sends a webhook to the webhookd. The webhookd then
|
11
|
+
parses the payload which contains the name and the branch of the pushed commit.
|
12
|
+
After that it looks up in the configuration what to do: run a different script per
|
13
|
+
repo or even per branch.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Just install the GEM:
|
18
|
+
|
19
|
+
$ gem install webhookd
|
20
|
+
|
21
|
+
The GEM has some dependencies which maybe need to build native extensions. Therefore on Ubuntu
|
22
|
+
and Debian the packages `ruby-dev` and `build-essential` are needed.
|
23
|
+
|
24
|
+
Some very basic Debian packaging effort can be found under [tobru/webhookd-debian-packaging](https://github.com/tobru/webhookd-debian-packaging).
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Starting and stopping
|
29
|
+
|
30
|
+
The webhookd uses thin as rack server by default. It has a small CLI utility
|
31
|
+
to start and stop the service, called `webhookd`:
|
32
|
+
|
33
|
+
```
|
34
|
+
Commands:
|
35
|
+
webhookd help [COMMAND] # Describe available commands or one specific command
|
36
|
+
webhookd start # Starts the webhookd server
|
37
|
+
webhookd stop # Stops the thin server
|
38
|
+
```
|
39
|
+
|
40
|
+
To see the options for `thin`, run `webhookd start -h`. They can simply be added to the `start` command.
|
41
|
+
F.e. `webhookd start -d --config-file=/path/to/config.yml`
|
42
|
+
|
43
|
+
**Starting the webhookd server**
|
44
|
+
|
45
|
+
`webhookd start --config-file=/path/to/config.yml`
|
46
|
+
|
47
|
+
Test it with `curl -XGET http://username:password@localhost:8088`
|
48
|
+
|
49
|
+
**Stopping the webhookd server**
|
50
|
+
|
51
|
+
`webhookd stop`
|
52
|
+
|
53
|
+
### Init script
|
54
|
+
|
55
|
+
There is an example init script which uses the Debian `/etc/default` mechanism to configured the
|
56
|
+
daemon options. Just place the file `scripts/webhookd.init` to `/etc/init.d/webhookd`, the
|
57
|
+
file `scripts/webhookd.default` to `/etc/default/webhookd` and update the parameters in the
|
58
|
+
defaults file to match your system.
|
59
|
+
|
60
|
+
It also has some configuration options to use SSL with the thin server. Set `SSL` to `yes` and update
|
61
|
+
the parameters `SSK_KEY` and `SSL_CERT`. The daemon starts now with ssl enabled.
|
62
|
+
On Debian and Ubuntu you maybe need to install `libssl-dev` and re-install the `eventmachine` gem.
|
63
|
+
|
64
|
+
You can test the SSL connection with `curl -XGET https://username:password@localhost:8088 -k` or
|
65
|
+
`openssl s_client -showcerts -connect localhost:8088`
|
66
|
+
|
67
|
+
### Configuration
|
68
|
+
|
69
|
+
The configuration is written in YAML. To see an example have a look at `etc/example.yml`.
|
70
|
+
|
71
|
+
**Global configuration**
|
72
|
+
This section holds some global parameters:
|
73
|
+
|
74
|
+
```YAML
|
75
|
+
global:
|
76
|
+
loglevel: 'debug'
|
77
|
+
logfile: 'app.log'
|
78
|
+
username: 'deployer'
|
79
|
+
password: 'Deploy1T'
|
80
|
+
```
|
81
|
+
|
82
|
+
* *loglevel*: One of: debug, info, warn, error, fatal
|
83
|
+
* *logfile*: Path (including filename) to the application log
|
84
|
+
* *username*: Username for the basic authentication to the application
|
85
|
+
* *password*: Password for the basic authentication to the application
|
86
|
+
|
87
|
+
**Payload type specific configuration**
|
88
|
+
Per payload type configuration. Available payload types: vcs. (More to come)
|
89
|
+
|
90
|
+
**Payload type 'vcs'**
|
91
|
+
This is meant for payload types coming from a version control system like git.
|
92
|
+
|
93
|
+
```YAML
|
94
|
+
vcs:
|
95
|
+
myrepo:
|
96
|
+
_all:
|
97
|
+
command: 'echo _all with branch <%= branch_name %>'
|
98
|
+
production:
|
99
|
+
command: '/usr/local/bin/deploy-my-app'
|
100
|
+
otherbranch:
|
101
|
+
command: '/bin/true'
|
102
|
+
myotherrepo:
|
103
|
+
master:
|
104
|
+
command: 'cd /my/local/path; /usr/bin/git pull'
|
105
|
+
_all:
|
106
|
+
master:
|
107
|
+
command: 'echo applies to all master branches of not specifically configured repos'
|
108
|
+
_all:
|
109
|
+
command: 'echo will be applied to ALL repos and branches if not more specifically configured'
|
110
|
+
```
|
111
|
+
|
112
|
+
There should be an entry per repository. If needed there can be a catch-all name which applies
|
113
|
+
to all repositories: `_all`. On the next level comes the name of the branch. Here could also be a
|
114
|
+
catch-all name specified, also called `_all`.
|
115
|
+
|
116
|
+
The `command` parameter is parsed with the ERB templating system. Available variables:
|
117
|
+
* *branch_name*
|
118
|
+
* *repo_name*
|
119
|
+
|
120
|
+
**Examples:**
|
121
|
+
|
122
|
+
```YAML
|
123
|
+
vcs:
|
124
|
+
repo1:
|
125
|
+
_all:
|
126
|
+
command: 'echo this is the branch <%= branch_name %>'
|
127
|
+
_all:
|
128
|
+
master:
|
129
|
+
command: 'echo this is the repo <%= repo_name.upcase %>'
|
130
|
+
```
|
131
|
+
|
132
|
+
### Testing
|
133
|
+
|
134
|
+
There are some tests in place using `minitest`. Run `rake test` to run all available test.
|
135
|
+
It should output a lot of log messages and at the end a summary of all test without any errors.
|
136
|
+
For the testcases to succeed, the configuration file `etc/example.yml` is used.
|
137
|
+
|
138
|
+
## Contributing
|
139
|
+
|
140
|
+
1. Fork it ( https://github.com/tobru/webhookd/fork )
|
141
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
142
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
143
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
144
|
+
5. Create a new Pull Request
|
145
|
+
|
146
|
+
### Payload types
|
147
|
+
|
148
|
+
Payload types are part of the business logic in `lib/webhookd/app.rb`.
|
149
|
+
It is defined in the payload endpoint in `lib/webhookd/payloadtype/<payloadtype>.rb`.
|
150
|
+
For an example have a look at `lib/webhookd/payloadtype/bitbucket.rb`.
|
151
|
+
|
152
|
+
Adding a new type would involve the following steps:
|
153
|
+
1. Write a payload parser in `lib/webhookd/payloadtype/`
|
154
|
+
1. Add business logic for the payload type in `lib/webhookd/app.rb` under `case parsed_data[:type]`
|
155
|
+
|
156
|
+
### Payload parser
|
157
|
+
|
158
|
+
The payload parser parses the payload data received from the webhook sender into a standard hash
|
159
|
+
which will be consumed by the business logic to take action.
|
160
|
+
|
161
|
+
**vcs**
|
162
|
+
|
163
|
+
The `vcs` payload type has the following hash signature:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
data[:type]
|
167
|
+
data[:source]
|
168
|
+
data[:repo_name]
|
169
|
+
data[:branch_name]
|
170
|
+
data[:author_name]
|
171
|
+
```
|
172
|
+
|
173
|
+
## TODO / Ideas
|
174
|
+
|
175
|
+
* Regex match for repository and branch names
|
176
|
+
* Notification mechanism (jabber, irc)
|
data/Rakefile
ADDED
data/bin/webhookd
ADDED
data/config.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
begin
|
2
|
+
require 'webhookd'
|
3
|
+
rescue LoadError => e
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
path = File.expand_path '../../lib', __FILE__
|
7
|
+
$:.unshift(path) if File.directory?(path) && !$:.include?(path)
|
8
|
+
Bundler.setup
|
9
|
+
require 'webhookd'
|
10
|
+
end
|
11
|
+
|
12
|
+
run Webhookd::App
|
data/etc/example.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
global:
|
3
|
+
loglevel: 'debug'
|
4
|
+
logfile: '/tmp/webhookd_dev.log'
|
5
|
+
username: 'deployer'
|
6
|
+
password: 'Deploy1T'
|
7
|
+
vcs:
|
8
|
+
puppet-control:
|
9
|
+
_all:
|
10
|
+
command: 'echo this is <%= branch_name %>'
|
11
|
+
production:
|
12
|
+
command: 'echo this is production'
|
13
|
+
nocommandbranch:
|
14
|
+
somethingunknown: 'whats that'
|
15
|
+
emptyconfigbranch:
|
16
|
+
encdata:
|
17
|
+
master:
|
18
|
+
command: 'git pull'
|
19
|
+
|
data/lib/webhookd.rb
ADDED
data/lib/webhookd/app.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
require 'thin'
|
4
|
+
require 'webhookd/version'
|
5
|
+
require 'webhookd/command_runner'
|
6
|
+
require 'webhookd/logging'
|
7
|
+
require 'webhookd/configuration'
|
8
|
+
|
9
|
+
module Webhookd
|
10
|
+
class App < Sinatra::Base
|
11
|
+
configuration_file = ENV["CONFIG_FILE"] || 'etc/example.yml'
|
12
|
+
Configuration.load!(configuration_file)
|
13
|
+
include Logging
|
14
|
+
|
15
|
+
# Sinatra configuration
|
16
|
+
set :show_exceptions, false
|
17
|
+
set server: 'thin', connections: [], history_file: 'history.yml'
|
18
|
+
|
19
|
+
# helpers
|
20
|
+
helpers do
|
21
|
+
def protected!
|
22
|
+
return if authorized?
|
23
|
+
headers['WWW-Authenticate'] = 'Basic realm="Webhookd authentication"'
|
24
|
+
halt 401, "Not authorized\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorized?
|
28
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
29
|
+
user = Configuration.settings[:global][:username]
|
30
|
+
password = Configuration.settings[:global][:password]
|
31
|
+
@auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == [user,password]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# error handling
|
36
|
+
not_found do
|
37
|
+
"Route not found. Do you know what you want to do?\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
error do |err|
|
41
|
+
"I'm so sorry, there was an application error: #{err}\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
### Sinatra routes
|
45
|
+
# we don't have anything to show
|
46
|
+
get '/' do
|
47
|
+
protected!
|
48
|
+
logger.info "incoming request from #{request.ip} for GET /"
|
49
|
+
"I'm running. Nice, isn't it?\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
post '/payload/:payloadtype' do
|
53
|
+
protected!
|
54
|
+
logger.info "incoming request from #{request.ip} for payload type #{params[:payloadtype]}"
|
55
|
+
|
56
|
+
begin
|
57
|
+
logger.debug "try to load webhookd/payloadtype/#{params[:payloadtype]}.rb"
|
58
|
+
load "webhookd/payloadtype/#{params[:payloadtype]}.rb"
|
59
|
+
rescue LoadError
|
60
|
+
logger.error "file not found: webhookd/payloadtype/#{params[:payloadtype]}.rb"
|
61
|
+
halt 400, "Payload type unknown\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
parser = ParsePayload.new(request.body.read)
|
65
|
+
parsed_data = parser.parse
|
66
|
+
|
67
|
+
# see if the payloadtype is known
|
68
|
+
if Configuration.settings.has_key?(parsed_data[:type].to_sym)
|
69
|
+
case parsed_data[:type]
|
70
|
+
when 'vcs'
|
71
|
+
# reload configuration
|
72
|
+
Configuration.load!(configuration_file)
|
73
|
+
|
74
|
+
branch_name = parsed_data[:branch_name]
|
75
|
+
repo_name = parsed_data[:repo_name]
|
76
|
+
repo_config = nil
|
77
|
+
branch_config = nil
|
78
|
+
command = nil
|
79
|
+
|
80
|
+
# is the repository configured?
|
81
|
+
if Configuration.settings[:vcs].has_key?(repo_name.to_sym)
|
82
|
+
logger.debug "repository configuration found"
|
83
|
+
repo_config = Configuration.settings[:vcs][repo_name.to_sym]
|
84
|
+
elsif Configuration.settings[:vcs].has_key?(:_all)
|
85
|
+
logger.debug "repository configuration not found, but there is an '_all' rule"
|
86
|
+
repo_config = Configuration.settings[:vcs][:_all]
|
87
|
+
else
|
88
|
+
error_msg = "repository configuration not found: '#{repo_name}' is not configured\n"
|
89
|
+
logger.fatal error_msg
|
90
|
+
halt 500, "#{error_msg}\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
# check if there is a repo_config available
|
94
|
+
if repo_config
|
95
|
+
# is the branch explicitely configured?
|
96
|
+
if repo_config.has_key?(branch_name.to_sym)
|
97
|
+
logger.debug "branch configuration found"
|
98
|
+
branch_config = repo_config[branch_name.to_sym]
|
99
|
+
elsif repo_config.has_key?(:_all)
|
100
|
+
logger.debug "branch configuration not found, but there is an '_all' rule"
|
101
|
+
branch_config = repo_config[:_all]
|
102
|
+
else
|
103
|
+
error_msg = "branch configuration not found: '#{branch_name}' in repo '#{repo_name}' is not configured\n"
|
104
|
+
logger.fatal error_msg
|
105
|
+
halt 500, "#{error_msg}\n"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# check if there is branch configuration data available
|
110
|
+
if branch_config
|
111
|
+
# is there a command configured?
|
112
|
+
if branch_config[:command]
|
113
|
+
command = branch_config[:command]
|
114
|
+
else
|
115
|
+
error_msg = "no command configuration found\n"
|
116
|
+
logger.fatal error_msg
|
117
|
+
halt 500, error_msg
|
118
|
+
end
|
119
|
+
else
|
120
|
+
error_msg = "branch configuration is empty\n"
|
121
|
+
logger.fatal error_msg
|
122
|
+
halt 500, error_msg
|
123
|
+
end
|
124
|
+
|
125
|
+
# check for a command to run and then run it
|
126
|
+
if command
|
127
|
+
parsed_command = ERB.new(command).result(binding)
|
128
|
+
command_runner = Commandrunner.new(parsed_command)
|
129
|
+
command_runner.run
|
130
|
+
end
|
131
|
+
# we don't know the type of this known payload
|
132
|
+
else
|
133
|
+
error_msg = "webhook payload type #{parsed_data[:type]} unknown"
|
134
|
+
logger.fatal error_msg
|
135
|
+
halt 500, "#{error_msg}\n"
|
136
|
+
end
|
137
|
+
# this type of payload is not configured
|
138
|
+
else
|
139
|
+
error_msg = "webhook payload of type #{parsed_data[:type]} not configured"
|
140
|
+
logger.info error_msg
|
141
|
+
halt 500, "#{error_msg}\n"
|
142
|
+
end
|
143
|
+
|
144
|
+
logger.debug "using configuration file #{configuration_file}"
|
145
|
+
# output to the requester
|
146
|
+
"webhook received\n"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/webhookd/cli.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Webhookd
|
4
|
+
class CLI < Thor
|
5
|
+
include Thor::Actions
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
desc "start", "Starts the webhookd server"
|
10
|
+
method_option :config_file, :desc => "Path to the configuration file"
|
11
|
+
def start(*args)
|
12
|
+
port_option = args.include?('-p') ? '' : ' -p 8088'
|
13
|
+
args = args.join(' ')
|
14
|
+
command = "thin -R #{get_rackup_config} start#{port_option} #{args}"
|
15
|
+
command.prepend "export CONFIG_FILE=#{options[:config_file]}; " if options[:config_file]
|
16
|
+
begin
|
17
|
+
run_command(command)
|
18
|
+
rescue SystemExit, Interrupt
|
19
|
+
puts "Program interrupted"
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "stop", "Stops the thin server"
|
25
|
+
def stop
|
26
|
+
command = "thin -R #{get_rackup_config} stop"
|
27
|
+
run_command(command)
|
28
|
+
end
|
29
|
+
|
30
|
+
# map some commands
|
31
|
+
map 's' => :start
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_rackup_config
|
36
|
+
begin
|
37
|
+
spec = Gem::Specification.find_by_name('webhookd')
|
38
|
+
"#{spec.gem_dir}/config.ru"
|
39
|
+
rescue Gem::LoadError
|
40
|
+
if File.exist?('/etc/webhookd/config.ru')
|
41
|
+
'/etc/webhookd/config.ru'
|
42
|
+
else
|
43
|
+
'./config.ru'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_command(command)
|
49
|
+
system(command)
|
50
|
+
end
|
51
|
+
|
52
|
+
def require_file(file)
|
53
|
+
require file
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'webhookd/logging'
|
3
|
+
|
4
|
+
module Webhookd
|
5
|
+
class Commandrunner
|
6
|
+
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def initialize(command)
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
begin
|
15
|
+
logger.info "Running command: #{@command}"
|
16
|
+
Open3::popen2e(@command) { |stdin, stdout_err, wait_thr|
|
17
|
+
while line = stdout_err.gets
|
18
|
+
logger.debug("Command output: #{line.strip}")
|
19
|
+
end
|
20
|
+
if wait_thr.value.success?
|
21
|
+
logger.info "command successful"
|
22
|
+
return true
|
23
|
+
else
|
24
|
+
logger.error "command failed"
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
}
|
28
|
+
rescue Exception => e
|
29
|
+
logger.fatal "Completely failed: #{e.message}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Configuration
|
4
|
+
# we don't want to instantiate this class - it's a singleton,
|
5
|
+
# so just keep it as a self-extended module
|
6
|
+
extend self
|
7
|
+
|
8
|
+
@settings = {}
|
9
|
+
attr_reader :settings
|
10
|
+
|
11
|
+
def load!(filename, options = {})
|
12
|
+
begin
|
13
|
+
@settings = symbolize_keys(YAML::load_file(filename))
|
14
|
+
rescue Errno::ENOENT
|
15
|
+
puts "[FATAL] configuration file '#{filename}' not found. Exiting."; exit 1
|
16
|
+
rescue Psych::SyntaxError
|
17
|
+
puts "[FATAL] configuration file '#{filename}' contains invalid syntax. Exiting."; exit 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def symbolize_keys(hash)
|
22
|
+
hash.inject({}){|result, (key, value)|
|
23
|
+
new_key = case key
|
24
|
+
when String then key.to_sym
|
25
|
+
else key
|
26
|
+
end
|
27
|
+
new_value = case value
|
28
|
+
when Hash then symbolize_keys(value)
|
29
|
+
else value
|
30
|
+
end
|
31
|
+
result[new_key] = new_value
|
32
|
+
result
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'webhookd/configuration'
|
3
|
+
|
4
|
+
module Logging
|
5
|
+
class MultiIO
|
6
|
+
def initialize(*targets)
|
7
|
+
@targets = targets
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(*args)
|
11
|
+
@targets.each {|t| t.write(*args)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
@targets.each(&:close)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# This is the magical bit that gets mixed into your classes
|
20
|
+
def logger
|
21
|
+
@logger ||= Logging.logger_for(self.class.name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
25
|
+
@loggers = {}
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def logger_for(classname)
|
29
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure_logger_for(classname)
|
33
|
+
logfile = File.open(Configuration.settings[:global][:logfile], 'a')
|
34
|
+
logfile.sync = true
|
35
|
+
logger = Logger.new MultiIO.new(STDOUT, logfile)
|
36
|
+
case Configuration.settings[:global][:loglevel]
|
37
|
+
when 'debug' then logger.level = Logger::DEBUG
|
38
|
+
when 'info' then logger.level = Logger::INFO
|
39
|
+
when 'warn' then logger.level = Logger::WARN
|
40
|
+
when 'error' then logger.level = Logger::ERROR
|
41
|
+
when 'fatal' then logger.level = Logger::FATAL
|
42
|
+
when 'unknown' then logger.level = Logger::UNKNOWN
|
43
|
+
else logger.level = Logger::DEBUG
|
44
|
+
end
|
45
|
+
logger.progname = classname
|
46
|
+
logger
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'webhookd/logging'
|
2
|
+
module Webhookd
|
3
|
+
class ParsePayload
|
4
|
+
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(payload)
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
require 'json'
|
13
|
+
logger.debug 'parsing payload type bitbucket'
|
14
|
+
|
15
|
+
prepared = URI.unescape(@payload.gsub("payload=","").gsub("+"," "))
|
16
|
+
json_parsed = JSON.parse(URI.unescape(prepared))
|
17
|
+
logger.debug "raw received data: #{json_parsed}"
|
18
|
+
|
19
|
+
# loop through commits to find the branch
|
20
|
+
branch_name = '_notfound'
|
21
|
+
author_name = '_notfound'
|
22
|
+
json_parsed['commits'].each do |commit|
|
23
|
+
if commit['branch']
|
24
|
+
branch_name = commit['branch']
|
25
|
+
author_name = commit['author']
|
26
|
+
break
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
data = Hash.new
|
31
|
+
data[:type] = 'vcs'
|
32
|
+
data[:source] = 'bitbucket'
|
33
|
+
data[:repo_name] = json_parsed['repository']['name']
|
34
|
+
data[:branch_name] = branch_name
|
35
|
+
data[:author_name] = author_name
|
36
|
+
|
37
|
+
logger.debug "parsed from the bitbucket data: #{data}"
|
38
|
+
|
39
|
+
# return the hash
|
40
|
+
data
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'webhookd/logging'
|
2
|
+
module Webhookd
|
3
|
+
class ParsePayload
|
4
|
+
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(payload)
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
require 'json'
|
13
|
+
logger.debug 'parsing payload type gitlab'
|
14
|
+
|
15
|
+
json_parsed = JSON.parse(@payload)
|
16
|
+
logger.debug "raw received data: #{json_parsed}"
|
17
|
+
|
18
|
+
data = Hash.new
|
19
|
+
data[:type] = 'vcs'
|
20
|
+
data[:source] = 'gitlab'
|
21
|
+
data[:repo_name] = json_parsed['repository']['name']
|
22
|
+
data[:branch_name] = json_parsed['ref'].split("/")[2]
|
23
|
+
data[:author_name] = json_parsed['user_name']
|
24
|
+
|
25
|
+
logger.debug "parsed from the gitlab data: #{data}"
|
26
|
+
|
27
|
+
# return the hash
|
28
|
+
data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,2 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
curl -X POST --data 'payload=%7B%22repository%22%3A+%7B%22website%22%3A+%22%22%2C+%22fork%22%3A+false%2C+%22name%22%3A+%22puppet-control%22%2C+%22scm%22%3A+%22git%22%2C+%22owner%22%3A+%22theowner%22%2C+%22absolute_url%22%3A+%22%2Ftheowner%2Fpuppet-control%2F%22%2C+%22slug%22%3A+%22puppet-control%22%2C+%22is_private%22%3A+true%7D%2C+%22truncated%22%3A+false%2C+%22commits%22%3A+%5B%7B%22node%22%3A+%22634d71461df8%22%2C+%22files%22%3A+%5B%7B%22type%22%3A+%22modified%22%2C+%22file%22%3A+%22hooktest.txt%22%7D%5D%2C+%22raw_author%22%3A+%22Tobias+Brunner+%3Ctobias%40mydomain.ch%3E%22%2C+%22utctimestamp%22%3A+%222014-12-03+16%3A04%3A36%2B00%3A00%22%2C+%22author%22%3A+%22tobru%22%2C+%22timestamp%22%3A+%222014-12-03+17%3A04%3A36%22%2C+%22raw_node%22%3A+%22634d71461df82f2095f18955cf967b606cb25f84%22%2C+%22parents%22%3A+%5B%22d94b7645874e%22%5D%2C+%22branch%22%3A+%22production%22%2C+%22message%22%3A+%22hook+test+3%5Cn%22%2C+%22revision%22%3A+null%2C+%22size%22%3A+-1%7D%5D%2C+%22canon_url%22%3A+%22https%3A%2F%2Fbitbucket.org%22%2C+%22user%22%3A+%22tobru%22%7D' http://deployer:Deploy1T@localhost:8088/payload/bitbucket
|
@@ -0,0 +1,2 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
curl -X POST --data 'payload=%7B%22repository%22%3A+%7B%22website%22%3A+%22%22%2C+%22fork%22%3A+false%2C+%22name%22%3A+%22puppet-control%22%2C+%22scm%22%3A+%22git%22%2C+%22owner%22%3A+%22theowner%22%2C+%22absolute_url%22%3A+%22%2Ftheowner%2Fpuppet-control%2F%22%2C+%22slug%22%3A+%22puppet-control%22%2C+%22is_private%22%3A+true%7D%2C+%22truncated%22%3A+false%2C+%22commits%22%3A+%5B%7B%22node%22%3A+%22634d71461df8%22%2C+%22files%22%3A+%5B%7B%22type%22%3A+%22modified%22%2C+%22file%22%3A+%22hooktest.txt%22%7D%5D%2C+%22raw_author%22%3A+%22Tobias+Brunner+%3Ctobias%40domain.com%3E%22%2C+%22utctimestamp%22%3A+%222014-12-03+16%3A04%3A36%2B00%3A00%22%2C+%22author%22%3A+%22tobru%22%2C+%22timestamp%22%3A+%222014-12-03+17%3A04%3A36%22%2C+%22raw_node%22%3A+%22634d71461df82f2095f18955cf967b606cb25f84%22%2C+%22parents%22%3A+%5B%22d94b7645874e%22%5D%2C+%22branch%22%3A+%22prorduction%22%2C+%22message%22%3A+%22hook+test+3%5Cn%22%2C+%22revision%22%3A+null%2C+%22size%22%3A+-1%7D%5D%2C+%22canon_url%22%3A+%22https%3A%2F%2Fbitbucket.org%22%2C+%22user%22%3A+%22tobru%22%7D' http://deployer:Deploy1T@localhost:8088/payload/bitbucket
|
@@ -0,0 +1,2 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
curl -X POST --data 'payload=%7B%22repository%22%3A+%7B%22website%22%3A+%22%22%2C+%22fork%22%3A+false%2C+%22name%22%3A+%22puppet-control%22%2C+%22scm%22%3A+%22git%22%2C+%22owner%22%3A+%22theowner%22%2C+%22absolute_url%22%3A+%22%2Ftheowner%2Fpuppet-control%2F%22%2C+%22slug%22%3A+%22puppet-control%22%2C+%22is_private%22%3A+true%7D%2C+%22truncated%22%3A+false%2C+%22commits%22%3A+%5B%7B%22node%22%3A+%22634d71461df8%22%2C+%22files%22%3A+%5B%7B%22type%22%3A+%22modified%22%2C+%22file%22%3A+%22hooktest.txt%22%7D%5D%2C+%22raw_author%22%3A+%22Tobias+Brunner+%3Ctobias%40domain.com%3E%22%2C+%22utctimestamp%22%3A+%222014-12-03+16%3A04%3A36%2B00%3A00%22%2C+%22author%22%3A+%22tobru%22%2C+%22timestamp%22%3A+%222014-12-03+17%3A04%3A36%22%2C+%22raw_node%22%3A+%22634d71461df82f2095f18955cf967b606cb25f84%22%2C+%22parents%22%3A+%5B%22d94b7645874e%22%5D%2C+%22branch%22%3A+%22nocommandbranch%22%2C+%22message%22%3A+%22hook+test+3%5Cn%22%2C+%22revision%22%3A+null%2C+%22size%22%3A+-1%7D%5D%2C+%22canon_url%22%3A+%22https%3A%2F%2Fbitbucket.org%22%2C+%22user%22%3A+%22tobru%22%7D' http://deployer:Deploy1T@localhost:8088/payload/bitbucket
|
@@ -0,0 +1,2 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
curl -X POST --data 'payload=%7B%22repository%22%3A+%7B%22website%22%3A+%22%22%2C+%22fork%22%3A+false%2C+%22name%22%3A+%22pudppet-control%22%2C+%22scm%22%3A+%22git%22%2C+%22owner%22%3A+%22theowner%22%2C+%22absolute_url%22%3A+%22%2Ftheowner%2Fpuppet-control%2F%22%2C+%22slug%22%3A+%22puppet-control%22%2C+%22is_private%22%3A+true%7D%2C+%22truncated%22%3A+false%2C+%22commits%22%3A+%5B%7B%22node%22%3A+%22634d71461df8%22%2C+%22files%22%3A+%5B%7B%22type%22%3A+%22modified%22%2C+%22file%22%3A+%22hooktest.txt%22%7D%5D%2C+%22raw_author%22%3A+%22Tobias+Brunner+%3Ctobias%40domain.com%3E%22%2C+%22utctimestamp%22%3A+%222014-12-03+16%3A04%3A36%2B00%3A00%22%2C+%22author%22%3A+%22tobru%22%2C+%22timestamp%22%3A+%222014-12-03+17%3A04%3A36%22%2C+%22raw_node%22%3A+%22634d71461df82f2095f18955cf967b606cb25f84%22%2C+%22parents%22%3A+%5B%22d94b7645874e%22%5D%2C+%22branch%22%3A+%22production%22%2C+%22message%22%3A+%22hook+test+3%5Cn%22%2C+%22revision%22%3A+null%2C+%22size%22%3A+-1%7D%5D%2C+%22canon_url%22%3A+%22https%3A%2F%2Fbitbucket.org%22%2C+%22user%22%3A+%22tobru%22%7D' http://deployer:Deploy1T@localhost:8088/payload/bitbucket
|
@@ -0,0 +1,2 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
curl -X POST --data '{"before":"0d066524996aab4e849f9fec39bfd68c67e8f433","after":"2fb697300387b7b083ac339257e5fc21ebbab290","ref":"refs/heads/master","user_id":2,"user_name":"User Name","project_id":16,"repository":{"name":"unknownrepo","url":"git@git.domain.net:project/unknownrepo.git","description":"The unknownrepo","homepage":"https://git.domain.net"},"commits":[{"id":"2fb697300387b7b083ac339257e5fc21ebbab290","message":"a message","timestamp":"2015-01-16T16:50:35+01:00","url":"https://git.domain.net/project/unknownrepo/commit/2fb697300387b7b083ac339257e5fc21ebbab290","author":{"name":"The Author","email":"name@domain.net"}}],"total_commits_count":3}' http://deployer:Deploy1T@localhost:8088/payload/gitlab
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
### BEGIN INIT INFO
|
4
|
+
# Provides: webhookd_start
|
5
|
+
# Required-Start: $network $syslog
|
6
|
+
# Required-Stop:
|
7
|
+
# Default-Start: 2 3 4 5
|
8
|
+
# Default-Stop:
|
9
|
+
# Short-Description: Starts up the webhookd server
|
10
|
+
### END INIT INFO
|
11
|
+
|
12
|
+
set -e
|
13
|
+
|
14
|
+
# Default settings
|
15
|
+
AS_USER=root
|
16
|
+
AS_GROUP=root
|
17
|
+
PORT=8088
|
18
|
+
CONFIGFILE=/etc/webhookd.yaml
|
19
|
+
SSL=no
|
20
|
+
|
21
|
+
# You should only need to edit the default/webhookd file and not
|
22
|
+
# this init script directly
|
23
|
+
if [ -r "/etc/default/webhookd" ] ; then
|
24
|
+
. /etc/default/webhookd
|
25
|
+
fi
|
26
|
+
|
27
|
+
SSL_PARAMS=""
|
28
|
+
if [ "${SSL}" == "yes" ] ; then
|
29
|
+
SSL_PARAMS="--ssl --ssl-key-file ${SSK_KEY} --ssl-cert-file ${SSL_CERT}"
|
30
|
+
fi
|
31
|
+
|
32
|
+
TIMEOUT=${TIMEOUT-60}
|
33
|
+
PID=/run/webhookd.${PORT}.pid
|
34
|
+
CMD="webhookd start -s1 -d -p ${PORT} -P /run/webhookd.pid --config-file=${CONFIGFILE} -d -u ${AS_USER} -g ${AS_GROUP} --tag webhookd ${SSL_PARAMS}"
|
35
|
+
|
36
|
+
set -u
|
37
|
+
|
38
|
+
sig () {
|
39
|
+
test -s "$PID" && kill -$1 `cat $PID`
|
40
|
+
}
|
41
|
+
|
42
|
+
run () {
|
43
|
+
eval $1
|
44
|
+
}
|
45
|
+
|
46
|
+
case "$1" in
|
47
|
+
start)
|
48
|
+
sig 0 && echo >&2 "Already running" && exit 0
|
49
|
+
run "$CMD"
|
50
|
+
;;
|
51
|
+
status)
|
52
|
+
[ ! -e "$PID" ] && echo "Not running" && exit 0
|
53
|
+
sig 0 && echo >&2 "Running" && exit 0
|
54
|
+
;;
|
55
|
+
stop)
|
56
|
+
sig QUIT && rm "$PID" && echo "Stopped" && exit 0
|
57
|
+
echo >&2 "Not running"
|
58
|
+
;;
|
59
|
+
restart)
|
60
|
+
sig HUP && echo reloaded OK && exit 0
|
61
|
+
echo >&2 "Couldn't reload, starting '$CMD' instead"
|
62
|
+
run "$CMD"
|
63
|
+
;;
|
64
|
+
*) echo "usage: $0 start|stop|restart" >&2
|
65
|
+
exit 1
|
66
|
+
;;
|
67
|
+
esac
|
data/test/helper.rb
ADDED
data/test/test_basics.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
describe 'GET /' do
|
4
|
+
user = "deployer"
|
5
|
+
pass = "Deploy1T"
|
6
|
+
it 'should request authentication' do
|
7
|
+
get '/'
|
8
|
+
last_response.status.must_equal 401
|
9
|
+
end
|
10
|
+
it 'should respond with authentication' do
|
11
|
+
authorize user, pass
|
12
|
+
get '/'
|
13
|
+
assert last_response.ok?
|
14
|
+
assert last_response.body.must_match "I'm running. Nice, isn't it?"
|
15
|
+
end
|
16
|
+
# payload has a known repository and branch name (aka configured)
|
17
|
+
it 'should respond with 500 - webhook payload of type unknowntype not configured' do
|
18
|
+
authorize user, pass
|
19
|
+
post '/payload/unknowntype', 'payload' => 'empty'
|
20
|
+
assert_equal 400, last_response.status
|
21
|
+
assert last_response.body.must_match "Payload type unknown"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require_relative 'helper'
|
3
|
+
|
4
|
+
describe 'Bitbucket payload' do
|
5
|
+
user = "deployer"
|
6
|
+
pass = "Deploy1T"
|
7
|
+
# payload has a known repository and branch name (aka configured)
|
8
|
+
it 'should output webhook received - known repo and branch' do
|
9
|
+
authorize user, pass
|
10
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22puppet-control%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fpuppet-control%2F%22%2C %22slug%22%3A %22puppet-control%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22production%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
11
|
+
assert last_response.ok?
|
12
|
+
assert last_response.body.must_match "webhook received"
|
13
|
+
end
|
14
|
+
# payload has a known repository and unknown branch (uses '_all' branch)
|
15
|
+
it 'should output webhook received - known repo and unknown branch' do
|
16
|
+
authorize user, pass
|
17
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22puppet-control%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fpuppet-control%2F%22%2C %22slug%22%3A %22puppet-control%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22unknownbranch%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
18
|
+
assert last_response.ok?
|
19
|
+
assert last_response.body.must_match "webhook received"
|
20
|
+
end
|
21
|
+
# payload has a known repository and branch without command configured
|
22
|
+
it 'should output 500 - no command configuration found' do
|
23
|
+
authorize user, pass
|
24
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22puppet-control%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fpuppet-control%2F%22%2C %22slug%22%3A %22puppet-control%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22nocommandbranch%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
25
|
+
assert_equal 500, last_response.status
|
26
|
+
assert last_response.body.must_match "no command configuration found"
|
27
|
+
end
|
28
|
+
# payload has a known repository and branch without any configuration
|
29
|
+
it 'should output 500 - branch configuration is empty' do
|
30
|
+
authorize user, pass
|
31
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22puppet-control%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fpuppet-control%2F%22%2C %22slug%22%3A %22puppet-control%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22emptyconfigbranch%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
32
|
+
assert_equal 500, last_response.status
|
33
|
+
assert last_response.body.must_match "branch configuration is empty"
|
34
|
+
end
|
35
|
+
# payload has a known repository and unknown branch and no '_all' configuration
|
36
|
+
it 'should output 500 - branch configuration not found: unknownbranch in repo encdata is not configured' do
|
37
|
+
authorize user, pass
|
38
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22encdata%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fencdata%2F%22%2C %22slug%22%3A %22encdata%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22unknownbranch%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
39
|
+
assert_equal 500, last_response.status
|
40
|
+
assert last_response.body.must_match "branch configuration not found: 'unknownbranch' in repo 'encdata' is not configured"
|
41
|
+
end
|
42
|
+
# payload has a unknown repository
|
43
|
+
it 'should output 500 - repository configuration not found: unknownrepo is not configured' do
|
44
|
+
authorize user, pass
|
45
|
+
post '/payload/bitbucket', 'payload' => '%7B%22repository%22%3A %7B%22website%22%3A %22%22%2C %22fork%22%3A false%2C %22name%22%3A %22unknownrepo%22%2C %22scm%22%3A %22git%22%2C %22owner%22%3A %22theowner%22%2C %22absolute_url%22%3A %22%2Ftheowner%2Fencdata%2F%22%2C %22slug%22%3A %22encdata%22%2C %22is_private%22%3A true%7D%2C %22truncated%22%3A false%2C %22commits%22%3A %5B%7B%22node%22%3A %22634d71461df8%22%2C %22files%22%3A %5B%7B%22type%22%3A %22modified%22%2C %22file%22%3A %22hooktest.txt%22%7D%5D%2C %22raw_author%22%3A %22Tobias Brunner %3Ctobias%40domain.com%3E%22%2C %22utctimestamp%22%3A %222014-12-03 16%3A04%3A36%2B00%3A00%22%2C %22author%22%3A %22tobru%22%2C %22timestamp%22%3A %222014-12-03 17%3A04%3A36%22%2C %22raw_node%22%3A %22634d71461df82f2095f18955cf967b606cb25f84%22%2C %22parents%22%3A %5B%22d94b7645874e%22%5D%2C %22branch%22%3A %22unknownbranch%22%2C %22message%22%3A %22hook test 3%5Cn%22%2C %22revision%22%3A null%2C %22size%22%3A -1%7D%5D%2C %22canon_url%22%3A %22https%3A%2F%2Fbitbucket.org%22%2C %22user%22%3A %22tobru%22%7D'
|
46
|
+
assert_equal 500, last_response.status
|
47
|
+
assert last_response.body.must_match "repository configuration not found: 'unknownrepo' is not configured"
|
48
|
+
end
|
49
|
+
end
|
data/webhookd.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'webhookd/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "webhookd"
|
8
|
+
spec.version = Webhookd::VERSION
|
9
|
+
spec.authors = ["Tobias Brunner"]
|
10
|
+
spec.email = ["tobias@tobru.ch"]
|
11
|
+
spec.summary = %q{Flexible, configurable universal webhook receiver}
|
12
|
+
spec.description = %q{This app is a flexible, configurable universal webhook receiver, built with sinatra. It can receive a webhook, parse its payload and take action according to the configuration.}
|
13
|
+
spec.homepage = "https://github.com/tobru/webhookd"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.default_executable = "webhookd"
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rack-test', '>= 0.6.0'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'sinatra', '~> 1.4', '>= 1.4.5'
|
27
|
+
spec.add_runtime_dependency 'thor', '~> 0.18', '>= 0.18.1'
|
28
|
+
spec.add_runtime_dependency 'thin', '~> 1.6', '>= 1.6.3'
|
29
|
+
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: webhookd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Brunner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-test
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 1.4.5
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '1.4'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.4.5
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: thor
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.18'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 0.18.1
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0.18'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 0.18.1
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: thin
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.6'
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.6.3
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1.6'
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: 1.6.3
|
115
|
+
description: This app is a flexible, configurable universal webhook receiver, built
|
116
|
+
with sinatra. It can receive a webhook, parse its payload and take action according
|
117
|
+
to the configuration.
|
118
|
+
email:
|
119
|
+
- tobias@tobru.ch
|
120
|
+
executables:
|
121
|
+
- webhookd
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- ".gitignore"
|
126
|
+
- CHANGELOG.md
|
127
|
+
- Gemfile
|
128
|
+
- LICENSE
|
129
|
+
- README.md
|
130
|
+
- Rakefile
|
131
|
+
- bin/webhookd
|
132
|
+
- config.ru
|
133
|
+
- etc/example.yml
|
134
|
+
- etc/example.yml.dist
|
135
|
+
- lib/webhookd.rb
|
136
|
+
- lib/webhookd/app.rb
|
137
|
+
- lib/webhookd/cli.rb
|
138
|
+
- lib/webhookd/command_runner.rb
|
139
|
+
- lib/webhookd/configuration.rb
|
140
|
+
- lib/webhookd/logging.rb
|
141
|
+
- lib/webhookd/payloadtype/bitbucket.rb
|
142
|
+
- lib/webhookd/payloadtype/gitlab.rb
|
143
|
+
- lib/webhookd/version.rb
|
144
|
+
- scripts/test/curl-bitbucket-explicit-repo-and-branch.sh
|
145
|
+
- scripts/test/curl-bitbucket-explicit-repo-wrong-branch.sh
|
146
|
+
- scripts/test/curl-bitbucket-nocommand.sh
|
147
|
+
- scripts/test/curl-bitbucket-unknown-repo.sh
|
148
|
+
- scripts/test/curl-gitlab-unknown-repo.sh
|
149
|
+
- scripts/webhookd.default
|
150
|
+
- scripts/webhookd.init
|
151
|
+
- test/helper.rb
|
152
|
+
- test/test_basics.rb
|
153
|
+
- test/test_bitbucket.rb
|
154
|
+
- webhookd.gemspec
|
155
|
+
homepage: https://github.com/tobru/webhookd
|
156
|
+
licenses:
|
157
|
+
- MIT
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.2.2
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Flexible, configurable universal webhook receiver
|
179
|
+
test_files:
|
180
|
+
- test/helper.rb
|
181
|
+
- test/test_basics.rb
|
182
|
+
- test/test_bitbucket.rb
|