webhookd 0.0.7
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 +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
|