right_hook 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +123 -0
- data/Rakefile +6 -0
- data/lib/right_hook/app.rb +43 -0
- data/lib/right_hook/authenticated_client.rb +14 -0
- data/lib/right_hook/authenticator.rb +38 -0
- data/lib/right_hook/commenter.rb +11 -0
- data/lib/right_hook/subscriber.rb +67 -0
- data/lib/right_hook/version.rb +3 -0
- data/lib/right_hook.rb +5 -0
- data/right_hook.gemspec +31 -0
- data/spec/app/issue_spec.rb +113 -0
- data/spec/app/pull_request_spec.rb +205 -0
- data/spec/app_spec.rb +26 -0
- data/spec/authenticated_client_spec.rb +11 -0
- data/spec/authenticator_spec.rb +37 -0
- data/spec/captain_hook_spec.rb +7 -0
- data/spec/commenter_spec.rb +15 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/subscriber_spec.rb +71 -0
- data/spec/support/spec_helpers.rb +10 -0
- metadata +227 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mark Rushakoff
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# Right Hook
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mark-rushakoff/right_hook)
|
4
|
+
[](https://codeclimate.com/github/mark-rushakoff/right_hook)
|
5
|
+
[](https://coveralls.io/r/mark-rushakoff/right_hook)
|
6
|
+
|
7
|
+
Right Hook is a collection of tools to aid in setting up a web app to handle GitHub repo hooks.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'right_hook'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install right_hook
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Your App
|
26
|
+
|
27
|
+
Create an application by subclassing `RightHook::App`:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# app.rb
|
31
|
+
require 'right_hook/app'
|
32
|
+
|
33
|
+
class MyApp < RightHook::App
|
34
|
+
# You must supply a secret for each repository and hook.
|
35
|
+
# The secret should only be known by GitHub; that's how we know the request is coming from GitHub's servers.
|
36
|
+
# (You'll specify that secret when you go through subscription.)
|
37
|
+
def secret(owner, repo_name, event_type)
|
38
|
+
if owner == 'octocat' && repo_name == 'Spoon-Fork' && event_type == 'pull_request'
|
39
|
+
'qwertyuiop'
|
40
|
+
else
|
41
|
+
raise 'unrecognized!'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Code to execute for GitHub's pull request hook.
|
46
|
+
# The secret has already been verified if your code is being called.
|
47
|
+
# See app.rb and spec/app/*_spec.rb for signatures and examples of the valid handlers.
|
48
|
+
def on_pull_request(owner, repo_name, action, number, pull_request_json)
|
49
|
+
message = <<-MSG
|
50
|
+
GitHub user #{pull_request_json['user']['login']} has opened pull request ##{number}
|
51
|
+
on repository #{owner}/#{repo_name}!
|
52
|
+
MSG
|
53
|
+
send_text_message(MY_PHONE_NUMBER, message) # or whatever you want
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
You'll need to host your app online and hold on to the base URL so you can subscribe to hooks.
|
59
|
+
|
60
|
+
### GitHub Authentication
|
61
|
+
|
62
|
+
To create hooks on GitHub repositories, you need to be authenticated as a collaborator on that repository.
|
63
|
+
GitHub's UI currently only supports configuring push hooks, so you'll want to authenticate through Right Hook to set up custom hooks.
|
64
|
+
|
65
|
+
Right Hook never stores your password.
|
66
|
+
He always uses OAuth tokens.
|
67
|
+
The only time he asks for your password is when he is creating a new token or listing existing tokens.
|
68
|
+
|
69
|
+
Right Hook doesn't store your tokens, either.
|
70
|
+
It's your duty to manage storage of tokens.
|
71
|
+
|
72
|
+
Here's one way you can generate and list tokens:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
require 'right_hook/authenticator'
|
76
|
+
|
77
|
+
puts "Please enter your username:"
|
78
|
+
username = $stdin.gets
|
79
|
+
|
80
|
+
# Prompt the user for their password, without displaying it in the terminal
|
81
|
+
authenticator = RightHook::Client.interactive_build(username)
|
82
|
+
|
83
|
+
# Note for the token (this will be displayed in the user's settings on GitHub)
|
84
|
+
note = "Created in my awesome script"
|
85
|
+
authenticator.create_authorization(note)
|
86
|
+
|
87
|
+
authenticator.authorizations.each do |token|
|
88
|
+
puts "Token: #{auth.token}\nNote: #{auth.note}\n\n"
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
### Subscribing to Hooks
|
93
|
+
|
94
|
+
Right Hook provides a way to subscribe to hooks.
|
95
|
+
It's easy!
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'right_hook/subscriber'
|
99
|
+
|
100
|
+
default_opts = {
|
101
|
+
base_url: "http://right-hook.example.com",
|
102
|
+
oauth_token: ENV['RIGHT_HOOK_TOKEN']
|
103
|
+
}
|
104
|
+
|
105
|
+
subscriber = RightHook::Subscriber.new(default_opts)
|
106
|
+
|
107
|
+
subscriber.subscribe(
|
108
|
+
owner: 'octocat',
|
109
|
+
repo_name: 'Hello-World',
|
110
|
+
event_type: 'pull_request',
|
111
|
+
secret: 'secret_for_hello_world'
|
112
|
+
)
|
113
|
+
```
|
114
|
+
|
115
|
+
(For more details, consult the RDoc documentation.)
|
116
|
+
|
117
|
+
## Contributing
|
118
|
+
|
119
|
+
1. Fork it
|
120
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
121
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
122
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
123
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module RightHook
|
5
|
+
class App < Sinatra::Base
|
6
|
+
KNOWN_EVENT_TYPES = %w(
|
7
|
+
pull_request
|
8
|
+
issue
|
9
|
+
)
|
10
|
+
|
11
|
+
post '/hook/:owner/:repo_name/:event_type' do
|
12
|
+
owner = params[:owner]
|
13
|
+
repo_name = params[:repo_name]
|
14
|
+
event_type = params[:event_type]
|
15
|
+
content = request.body.read
|
16
|
+
|
17
|
+
halt 404 unless KNOWN_EVENT_TYPES.include?(event_type)
|
18
|
+
halt 501 unless respond_to?("on_#{event_type}")
|
19
|
+
|
20
|
+
require_valid_signature(content, owner, repo_name, event_type)
|
21
|
+
|
22
|
+
json = JSON.parse(content)
|
23
|
+
case event_type
|
24
|
+
when 'pull_request'
|
25
|
+
on_pull_request(owner, repo_name, json['number'], json['action'], json['pull_request'])
|
26
|
+
when 'issue'
|
27
|
+
on_issue(owner, repo_name, json['action'], json['issue'])
|
28
|
+
else
|
29
|
+
halt 500
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def require_valid_signature(content, owner, repo_name, event_type)
|
35
|
+
s = secret(owner, repo_name, event_type)
|
36
|
+
expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), s, content)
|
37
|
+
|
38
|
+
# http://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#authednotify
|
39
|
+
# "If the signature does not match, subscribers MUST still return a 2xx success response to acknowledge receipt, but locally ignore the message as invalid."
|
40
|
+
halt 202 unless request.env['X-Hub-Signature'] == "sha1=#{expected_signature}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
module RightHook
|
4
|
+
# A base class for RightHook actors that interact with the GitHub API.
|
5
|
+
class AuthenticatedClient
|
6
|
+
# Create a new client, authenticating with the given OAuth token.
|
7
|
+
def initialize(token)
|
8
|
+
@client = Octokit::Client.new(access_token: token)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
attr_reader :client
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RightHook
|
2
|
+
# The authenticator provides an interface to retrieving or creating GitHub authorizations.
|
3
|
+
class Authenticator
|
4
|
+
class << self
|
5
|
+
# Build a client with a username and an explicit password.
|
6
|
+
def build(username, password)
|
7
|
+
new(Octokit::Client.new(login: username, password: password))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Prompt the user for their password (without displaying the entered keys).
|
11
|
+
# This approach is offered for convenience to make it easier to not store passwords on disk.
|
12
|
+
def interactive_build(username)
|
13
|
+
new(Octokit::Client.new(login: username, password: $stdin.noecho(&:gets).chomp))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# :nodoc:
|
18
|
+
def initialize(_client)
|
19
|
+
@_client = _client
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a new GitHub authorization with the given note.
|
23
|
+
# If one already exists with that note, it will not create a duplicate.
|
24
|
+
def create_authorization(note)
|
25
|
+
_client.create_authorization(scopes: %w(repo), note: note, idempotent: true).token
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an array of all of the authorizations for the authenticated account.
|
29
|
+
def list_authorizations
|
30
|
+
_client.list_authorizations
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
attr_reader :_client
|
35
|
+
# Enforce use of build methods
|
36
|
+
private_class_method :new
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'right_hook/authenticated_client'
|
2
|
+
require 'octokit'
|
3
|
+
|
4
|
+
module RightHook
|
5
|
+
# Provides an interface for adding comments on GitHub
|
6
|
+
class Commenter < AuthenticatedClient
|
7
|
+
def comment_on_issue(owner, repo_name, issue_number, comment)
|
8
|
+
client.add_comment("#{owner}/#{repo_name}", issue_number, comment)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module RightHook
|
4
|
+
# Subscriber can subscribe and unsubscribe GitHub hooks to a hosted instance of a specified {RightHook::App}.
|
5
|
+
# See the README for sample usage.
|
6
|
+
class Subscriber
|
7
|
+
# The base URL for the binding (where your {RightHook::App} is hosted).
|
8
|
+
attr_accessor :base_url
|
9
|
+
|
10
|
+
# The OAuth token to use for authenticating with GitHub.
|
11
|
+
# The token must belong to an account that has the +repo+ scope and collaborator privilege on the given repository.
|
12
|
+
attr_accessor :oauth_token
|
13
|
+
|
14
|
+
# The owner of the named repository.
|
15
|
+
attr_accessor :owner
|
16
|
+
|
17
|
+
# The event type of the hook.
|
18
|
+
# See http://developer.github.com/v3/repos/hooks/ for a complete list of valid types.
|
19
|
+
attr_accessor :event_type
|
20
|
+
|
21
|
+
# Initialize takes options which will be used as default values in other methods.
|
22
|
+
# The valid keys in the options are [+base_url+, +oauth_token+, +owner+, and +event_type+].
|
23
|
+
def initialize(default_opts = {})
|
24
|
+
@base_url = default_opts[:base_url]
|
25
|
+
@oauth_token = default_opts[:oauth_token]
|
26
|
+
@owner = default_opts[:owner]
|
27
|
+
@event_type = default_opts[:event_type]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Subscribe an instance of {RightHook::App} hosted at +base_url+ to a hook for +owner+/+repo_name+, authenticating with +oauth_token+.
|
31
|
+
# +repo_name+ and +secret+ are required options and they are intentionally not stored as defaults on the +Subscriber+ instance.
|
32
|
+
# @return [bool success] Whether the request was successful.
|
33
|
+
def subscribe(opts)
|
34
|
+
hub_request_with_mode('subscribe', opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Unsubscribe an instance of {RightHook::App} hosted at +base_url+ to a hook for +owner+/+repo_name+, authenticating with +oauth_token+.
|
38
|
+
# +repo_name+ and +secret+ are required options and they are intentionally not stored as defaults on the +Subscriber+ instance.
|
39
|
+
# (NB: It's possible that GitHub's API *doesn't* require secret; I haven't checked.)
|
40
|
+
# @return [bool success] Whether the request was successful.
|
41
|
+
def unsubscribe(opts)
|
42
|
+
hub_request_with_mode('unsubscribe', opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def hub_request_with_mode(mode, opts)
|
47
|
+
repo_name = opts.fetch(:repo_name) # explicitly not defaulted
|
48
|
+
secret = opts.fetch(:secret) # explicitly not defaulted
|
49
|
+
oauth_token = opts.fetch(:oauth_token) { self.oauth_token }
|
50
|
+
owner = opts.fetch(:owner) { self.owner }
|
51
|
+
base_url = opts.fetch(:base_url) { self.base_url }
|
52
|
+
|
53
|
+
HTTParty.post('https://api.github.com/hub',
|
54
|
+
headers: {
|
55
|
+
# http://developer.github.com/v3/#authentication
|
56
|
+
'Authorization' => "token #{oauth_token}"
|
57
|
+
},
|
58
|
+
body: {
|
59
|
+
'hub.mode' => mode,
|
60
|
+
'hub.topic' => "https://github.com/#{owner}/#{repo_name}/events/#{event_type}",
|
61
|
+
'hub.callback' => "#{base_url}/hook/#{owner}/#{repo_name}/#{event_type}",
|
62
|
+
'hub.secret' => secret
|
63
|
+
}
|
64
|
+
).success?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/right_hook.rb
ADDED
data/right_hook.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'right_hook/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "right_hook"
|
8
|
+
spec.version = RightHook::VERSION
|
9
|
+
spec.authors = ["Mark Rushakoff"]
|
10
|
+
spec.email = ["mark.rushakoff@gmail.com"]
|
11
|
+
spec.description = %q{A simple sinatra app ready to go for GitHub service hooks.}
|
12
|
+
spec.summary = %q{Right Hook is a foundation to use when you just want to write a GitHub service hook.}
|
13
|
+
spec.homepage = "https://github.com/mark-rushakoff/right_hook"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
24
|
+
spec.add_development_dependency "rack-test"
|
25
|
+
spec.add_development_dependency "webmock", "~> 1.13"
|
26
|
+
spec.add_development_dependency "coveralls"
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "sinatra", "~> 1.4"
|
29
|
+
spec.add_runtime_dependency "httparty", "~> 0.11.0"
|
30
|
+
spec.add_runtime_dependency "octokit", "~> 2.2"
|
31
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
describe RightHook::App do
|
5
|
+
describe 'Issues' do
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
class IssueApp < RightHook::App
|
9
|
+
class << self
|
10
|
+
attr_accessor :owner, :repo_name, :action, :issue_json
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_issue(owner, repo_name, action, issue_json)
|
14
|
+
self.class.owner = owner
|
15
|
+
self.class.repo_name = repo_name
|
16
|
+
self.class.action = action
|
17
|
+
self.class.issue_json = issue_json
|
18
|
+
end
|
19
|
+
|
20
|
+
def secret(owner, repo_name, event_type)
|
21
|
+
'issue' if owner == 'mark-rushakoff' && repo_name == 'right_hook' && event_type == 'issue'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def app
|
26
|
+
IssueApp
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
app.owner = app.repo_name = app.action = app.issue_json = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'captures the interesting data' do
|
34
|
+
post '/hook/mark-rushakoff/right_hook/issue', ISSUE_JSON, generate_secret_header('issue', ISSUE_JSON)
|
35
|
+
expect(last_response.status).to eq(200)
|
36
|
+
expect(app.owner).to eq('mark-rushakoff')
|
37
|
+
expect(app.repo_name).to eq('right_hook')
|
38
|
+
expect(app.action).to eq('opened')
|
39
|
+
|
40
|
+
# if it has one key it probably has them all
|
41
|
+
expect(app.issue_json['title']).to eq('Found a bug')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'fails when the secret is wrong' do
|
45
|
+
post '/hook/mark-rushakoff/right_hook/issue', ISSUE_JSON, generate_secret_header('wrong', ISSUE_JSON)
|
46
|
+
expect(last_response.status).to eq(202)
|
47
|
+
expect(app.owner).to be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# from http://developer.github.com/v3/issues/#get-a-single-issue
|
53
|
+
ISSUE_JSON = <<-JSON
|
54
|
+
{
|
55
|
+
"action": "opened",
|
56
|
+
"issue": {
|
57
|
+
"url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
|
58
|
+
"html_url": "https://github.com/octocat/Hello-World/issues/1347",
|
59
|
+
"number": 1347,
|
60
|
+
"state": "open",
|
61
|
+
"title": "Found a bug",
|
62
|
+
"body": "I'm having a problem with this.",
|
63
|
+
"user": {
|
64
|
+
"login": "octocat",
|
65
|
+
"id": 1,
|
66
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
67
|
+
"gravatar_id": "somehexcode",
|
68
|
+
"url": "https://api.github.com/users/octocat"
|
69
|
+
},
|
70
|
+
"labels": [
|
71
|
+
{
|
72
|
+
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
|
73
|
+
"name": "bug",
|
74
|
+
"color": "f29513"
|
75
|
+
}
|
76
|
+
],
|
77
|
+
"assignee": {
|
78
|
+
"login": "octocat",
|
79
|
+
"id": 1,
|
80
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
81
|
+
"gravatar_id": "somehexcode",
|
82
|
+
"url": "https://api.github.com/users/octocat"
|
83
|
+
},
|
84
|
+
"milestone": {
|
85
|
+
"url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
|
86
|
+
"number": 1,
|
87
|
+
"state": "open",
|
88
|
+
"title": "v1.0",
|
89
|
+
"description": "",
|
90
|
+
"creator": {
|
91
|
+
"login": "octocat",
|
92
|
+
"id": 1,
|
93
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
94
|
+
"gravatar_id": "somehexcode",
|
95
|
+
"url": "https://api.github.com/users/octocat"
|
96
|
+
},
|
97
|
+
"open_issues": 4,
|
98
|
+
"closed_issues": 8,
|
99
|
+
"created_at": "2011-04-10T20:09:31Z",
|
100
|
+
"due_on": null
|
101
|
+
},
|
102
|
+
"comments": 0,
|
103
|
+
"pull_request": {
|
104
|
+
"html_url": "https://github.com/octocat/Hello-World/pull/1347",
|
105
|
+
"diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
|
106
|
+
"patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"
|
107
|
+
},
|
108
|
+
"closed_at": null,
|
109
|
+
"created_at": "2011-04-22T13:33:48Z",
|
110
|
+
"updated_at": "2011-04-22T13:33:48Z"
|
111
|
+
}
|
112
|
+
}
|
113
|
+
JSON
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
describe RightHook::App do
|
5
|
+
describe 'Pull requests' do
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
class PullRequestApp < RightHook::App
|
9
|
+
class << self
|
10
|
+
attr_accessor :owner, :repo_name, :action, :number, :pull_request_json
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_pull_request(owner, repo_name, number, action, pull_request_json)
|
14
|
+
self.class.owner = owner
|
15
|
+
self.class.repo_name = repo_name
|
16
|
+
self.class.action = action
|
17
|
+
self.class.number = number
|
18
|
+
self.class.pull_request_json = pull_request_json
|
19
|
+
end
|
20
|
+
|
21
|
+
def secret(owner, repo_name, event_type)
|
22
|
+
'pull_request' if owner == 'mark-rushakoff' && repo_name == 'right_hook' && event_type == 'pull_request'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def app
|
27
|
+
PullRequestApp
|
28
|
+
end
|
29
|
+
|
30
|
+
before do
|
31
|
+
app.owner = app.repo_name = app.action = app.number = app.pull_request_json = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'captures the interesting data' do
|
35
|
+
post '/hook/mark-rushakoff/right_hook/pull_request', PULL_REQUEST_JSON, generate_secret_header('pull_request', PULL_REQUEST_JSON)
|
36
|
+
expect(last_response.status).to eq(200)
|
37
|
+
expect(app.owner).to eq('mark-rushakoff')
|
38
|
+
expect(app.repo_name).to eq('right_hook')
|
39
|
+
expect(app.action).to eq('opened')
|
40
|
+
expect(app.number).to eq(1)
|
41
|
+
|
42
|
+
# if it has one key it probably has them all
|
43
|
+
expect(app.pull_request_json['body']).to eq('Please pull these awesome changes')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'fails when the secret is wrong' do
|
47
|
+
post '/hook/mark-rushakoff/right_hook/pull_request', PULL_REQUEST_JSON, generate_secret_header('wrong', PULL_REQUEST_JSON)
|
48
|
+
expect(last_response.status).to eq(202)
|
49
|
+
expect(app.owner).to be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# from http://developer.github.com/v3/pulls/#get-a-single-pull-request
|
55
|
+
PULL_REQUEST_JSON = <<-JSON
|
56
|
+
{
|
57
|
+
"action": "opened",
|
58
|
+
"number": 1,
|
59
|
+
"pull_request": {
|
60
|
+
"url": "https://api.github.com/octocat/Hello-World/pulls/1",
|
61
|
+
"html_url": "https://github.com/octocat/Hello-World/pull/1",
|
62
|
+
"diff_url": "https://github.com/octocat/Hello-World/pulls/1.diff",
|
63
|
+
"patch_url": "https://github.com/octocat/Hello-World/pulls/1.patch",
|
64
|
+
"issue_url": "https://github.com/octocat/Hello-World/issue/1",
|
65
|
+
"number": 1,
|
66
|
+
"state": "open",
|
67
|
+
"title": "new-feature",
|
68
|
+
"body": "Please pull these awesome changes",
|
69
|
+
"created_at": "2011-01-26T19:01:12Z",
|
70
|
+
"updated_at": "2011-01-26T19:01:12Z",
|
71
|
+
"closed_at": "2011-01-26T19:01:12Z",
|
72
|
+
"merged_at": "2011-01-26T19:01:12Z",
|
73
|
+
"head": {
|
74
|
+
"label": "new-topic",
|
75
|
+
"ref": "new-topic",
|
76
|
+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
77
|
+
"user": {
|
78
|
+
"login": "octocat",
|
79
|
+
"id": 1,
|
80
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
81
|
+
"gravatar_id": "somehexcode",
|
82
|
+
"url": "https://api.github.com/users/octocat"
|
83
|
+
},
|
84
|
+
"repo": {
|
85
|
+
"id": 1296269,
|
86
|
+
"owner": {
|
87
|
+
"login": "octocat",
|
88
|
+
"id": 1,
|
89
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
90
|
+
"gravatar_id": "somehexcode",
|
91
|
+
"url": "https://api.github.com/users/octocat"
|
92
|
+
},
|
93
|
+
"name": "Hello-World",
|
94
|
+
"full_name": "octocat/Hello-World",
|
95
|
+
"description": "This your first repo!",
|
96
|
+
"private": false,
|
97
|
+
"fork": false,
|
98
|
+
"url": "https://api.github.com/repos/octocat/Hello-World",
|
99
|
+
"html_url": "https://github.com/octocat/Hello-World",
|
100
|
+
"clone_url": "https://github.com/octocat/Hello-World.git",
|
101
|
+
"git_url": "git://github.com/octocat/Hello-World.git",
|
102
|
+
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
103
|
+
"svn_url": "https://svn.github.com/octocat/Hello-World",
|
104
|
+
"mirror_url": "git://git.example.com/octocat/Hello-World",
|
105
|
+
"homepage": "https://github.com",
|
106
|
+
"language": null,
|
107
|
+
"forks": 9,
|
108
|
+
"forks_count": 9,
|
109
|
+
"watchers": 80,
|
110
|
+
"watchers_count": 80,
|
111
|
+
"size": 108,
|
112
|
+
"master_branch": "master",
|
113
|
+
"open_issues": 0,
|
114
|
+
"open_issues_count": 0,
|
115
|
+
"pushed_at": "2011-01-26T19:06:43Z",
|
116
|
+
"created_at": "2011-01-26T19:01:12Z",
|
117
|
+
"updated_at": "2011-01-26T19:14:43Z"
|
118
|
+
}
|
119
|
+
},
|
120
|
+
"base": {
|
121
|
+
"label": "master",
|
122
|
+
"ref": "master",
|
123
|
+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
124
|
+
"user": {
|
125
|
+
"login": "octocat",
|
126
|
+
"id": 1,
|
127
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
128
|
+
"gravatar_id": "somehexcode",
|
129
|
+
"url": "https://api.github.com/users/octocat"
|
130
|
+
},
|
131
|
+
"repo": {
|
132
|
+
"id": 1296269,
|
133
|
+
"owner": {
|
134
|
+
"login": "octocat",
|
135
|
+
"id": 1,
|
136
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
137
|
+
"gravatar_id": "somehexcode",
|
138
|
+
"url": "https://api.github.com/users/octocat"
|
139
|
+
},
|
140
|
+
"name": "Hello-World",
|
141
|
+
"full_name": "octocat/Hello-World",
|
142
|
+
"description": "This your first repo!",
|
143
|
+
"private": false,
|
144
|
+
"fork": false,
|
145
|
+
"url": "https://api.github.com/repos/octocat/Hello-World",
|
146
|
+
"html_url": "https://github.com/octocat/Hello-World",
|
147
|
+
"clone_url": "https://github.com/octocat/Hello-World.git",
|
148
|
+
"git_url": "git://github.com/octocat/Hello-World.git",
|
149
|
+
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
150
|
+
"svn_url": "https://svn.github.com/octocat/Hello-World",
|
151
|
+
"mirror_url": "git://git.example.com/octocat/Hello-World",
|
152
|
+
"homepage": "https://github.com",
|
153
|
+
"language": null,
|
154
|
+
"forks": 9,
|
155
|
+
"forks_count": 9,
|
156
|
+
"watchers": 80,
|
157
|
+
"watchers_count": 80,
|
158
|
+
"size": 108,
|
159
|
+
"master_branch": "master",
|
160
|
+
"open_issues": 0,
|
161
|
+
"open_issues_count": 0,
|
162
|
+
"pushed_at": "2011-01-26T19:06:43Z",
|
163
|
+
"created_at": "2011-01-26T19:01:12Z",
|
164
|
+
"updated_at": "2011-01-26T19:14:43Z"
|
165
|
+
}
|
166
|
+
},
|
167
|
+
"_links": {
|
168
|
+
"self": {
|
169
|
+
"href": "https://api.github.com/octocat/Hello-World/pulls/1"
|
170
|
+
},
|
171
|
+
"html": {
|
172
|
+
"href": "https://github.com/octocat/Hello-World/pull/1"
|
173
|
+
},
|
174
|
+
"comments": {
|
175
|
+
"href": "https://api.github.com/octocat/Hello-World/issues/1/comments"
|
176
|
+
},
|
177
|
+
"review_comments": {
|
178
|
+
"href": "https://api.github.com/octocat/Hello-World/pulls/1/comments"
|
179
|
+
}
|
180
|
+
},
|
181
|
+
"user": {
|
182
|
+
"login": "octocat",
|
183
|
+
"id": 1,
|
184
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
185
|
+
"gravatar_id": "somehexcode",
|
186
|
+
"url": "https://api.github.com/users/octocat"
|
187
|
+
},
|
188
|
+
"merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6",
|
189
|
+
"merged": false,
|
190
|
+
"mergeable": true,
|
191
|
+
"merged_by": {
|
192
|
+
"login": "octocat",
|
193
|
+
"id": 1,
|
194
|
+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
195
|
+
"gravatar_id": "somehexcode",
|
196
|
+
"url": "https://api.github.com/users/octocat"
|
197
|
+
},
|
198
|
+
"comments": 10,
|
199
|
+
"commits": 3,
|
200
|
+
"additions": 100,
|
201
|
+
"deletions": 3,
|
202
|
+
"changed_files": 5
|
203
|
+
}
|
204
|
+
}
|
205
|
+
JSON
|
data/spec/app_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
describe RightHook::App do
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
class BareApp < RightHook::App
|
8
|
+
def secret(owner, repo_name, event_type)
|
9
|
+
''
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def app
|
14
|
+
BareApp
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is status 501 for a non-implemented hook' do
|
18
|
+
post '/hook/mark-rushakoff/right_hook/issue', '{}', generate_secret_header('secret', '{}')
|
19
|
+
expect(last_response.status).to eq(501)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is 404 for an unknown hook' do
|
23
|
+
post '/hook/mark-rushakoff/right_hook/foobar', '{}', generate_secret_header('secret', '{}')
|
24
|
+
expect(last_response.status).to eq(404)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RightHook::AuthenticatedClient do
|
4
|
+
describe '.new' do
|
5
|
+
it 'creates an Octokit client with the given token' do
|
6
|
+
Octokit::Client.should_receive(:new).with(access_token: 'the_token')
|
7
|
+
|
8
|
+
described_class.new('the_token')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'octokit'
|
3
|
+
|
4
|
+
describe RightHook::Authenticator do
|
5
|
+
describe '.build' do
|
6
|
+
it 'delegates to Octokit' do
|
7
|
+
Octokit::Client.should_receive(:new).with(login: 'octocat', password: 'pass')
|
8
|
+
|
9
|
+
described_class.build('octocat', 'pass')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.interactive_build' do
|
14
|
+
it 'delegates to Octokit and stdin.noecho' do
|
15
|
+
$stdin.should_receive(:noecho).and_return("pass\n")
|
16
|
+
Octokit::Client.should_receive(:new).with(login: 'octocat', password: 'pass')
|
17
|
+
|
18
|
+
described_class.interactive_build('octocat')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#create_authorization' do
|
23
|
+
it 'delegates to create_authorization' do
|
24
|
+
Octokit::Client.any_instance.should_receive(:create_authorization).with(scopes: %w(repo), note: 'test note', idempotent: true).and_return(OpenStruct.new(token: 'my_token'))
|
25
|
+
|
26
|
+
expect(described_class.build('octocat', 'pass').create_authorization('test note')).to eq('my_token')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#list_authorizations' do
|
31
|
+
it 'delegates to list_authorizations' do
|
32
|
+
Octokit::Client.any_instance.should_receive(:list_authorizations).and_return(:the_authorizations)
|
33
|
+
|
34
|
+
expect(described_class.build('octocat', 'pass').list_authorizations).to eq(:the_authorizations)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RightHook::Commenter do
|
4
|
+
subject(:commenter) do
|
5
|
+
described_class.new('a_token')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.comment_on_issue' do
|
9
|
+
it 'delegates to Octokit' do
|
10
|
+
Octokit::Client.any_instance.should_receive(:add_comment).with('octocat/Spoon-Fork', 100, "Wow, 100!")
|
11
|
+
|
12
|
+
commenter.comment_on_issue("octocat", "Spoon-Fork", 100, "Wow, 100!")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'right_hook'
|
3
|
+
require 'right_hook/app'
|
4
|
+
require 'right_hook/authenticated_client'
|
5
|
+
require 'right_hook/authenticator'
|
6
|
+
require 'right_hook/commenter'
|
7
|
+
require 'right_hook/subscriber'
|
8
|
+
|
9
|
+
require_relative './support/spec_helpers.rb'
|
10
|
+
|
11
|
+
require 'webmock/rspec'
|
12
|
+
|
13
|
+
require 'coveralls'
|
14
|
+
Coveralls.wear!
|
15
|
+
|
16
|
+
RSpec.configure do |c|
|
17
|
+
c.include RightHook::SpecHelpers
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RightHook::Subscriber do
|
4
|
+
subject(:subscriber) do
|
5
|
+
described_class.new(
|
6
|
+
oauth_token: 'my_token',
|
7
|
+
owner: 'mark-rushakoff',
|
8
|
+
base_url: 'http://example.com',
|
9
|
+
event_type: 'issue',
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.subscribe' do
|
14
|
+
let!(:stubbed_request) do
|
15
|
+
stub_request(:post, 'https://api.github.com/hub').
|
16
|
+
with(:body => 'hub.mode=subscribe&hub.topic=https%3A%2F%2Fgithub.com%2Fmark-rushakoff%2Fright_hook%2Fevents%2Fissue&hub.callback=http%3A%2F%2Fexample.com%2Fhook%2Fmark-rushakoff%2Fright_hook%2Fissue&hub.secret=the-secret',
|
17
|
+
:headers => {'Authorization' => 'token my_token'}
|
18
|
+
).to_return(:status => status_code, :body => '', :headers => {})
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'When the request succeeds' do
|
22
|
+
let(:status_code) { 200 }
|
23
|
+
it 'returns true' do
|
24
|
+
result = subscriber.subscribe(repo_name: 'right_hook', secret: 'the-secret')
|
25
|
+
expect(result).to eq(true)
|
26
|
+
|
27
|
+
expect(stubbed_request).to have_been_requested
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'When the request fails' do
|
32
|
+
let(:status_code) { 404 }
|
33
|
+
it 'returns false' do
|
34
|
+
result = subscriber.subscribe(repo_name: 'right_hook', secret: 'the-secret')
|
35
|
+
expect(result).to eq(false)
|
36
|
+
|
37
|
+
expect(stubbed_request).to have_been_requested
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.unsubscribe' do
|
43
|
+
let!(:stubbed_request) do
|
44
|
+
stub_request(:post, 'https://api.github.com/hub').
|
45
|
+
with(:body => 'hub.mode=unsubscribe&hub.topic=https%3A%2F%2Fgithub.com%2Fmark-rushakoff%2Fright_hook%2Fevents%2Fissue&hub.callback=http%3A%2F%2Fexample.com%2Fhook%2Fmark-rushakoff%2Fright_hook%2Fissue&hub.secret=the-secret',
|
46
|
+
:headers => {'Authorization' => 'token my_token'}
|
47
|
+
).to_return(:status => status_code, :body => '', :headers => {})
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
context 'When the request succeeds' do
|
52
|
+
let(:status_code) { 200 }
|
53
|
+
it 'returns true' do
|
54
|
+
result = subscriber.unsubscribe(repo_name: 'right_hook', secret: 'the-secret')
|
55
|
+
expect(result).to eq(true)
|
56
|
+
|
57
|
+
expect(stubbed_request).to have_been_requested
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'When the request fails' do
|
62
|
+
let(:status_code) { 404 }
|
63
|
+
it 'returns false' do
|
64
|
+
result = subscriber.unsubscribe(repo_name: 'right_hook', secret: 'the-secret')
|
65
|
+
expect(result).to eq(false)
|
66
|
+
|
67
|
+
expect(stubbed_request).to have_been_requested
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: right_hook
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Rushakoff
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.14'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rack-test
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: webmock
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.13'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.13'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: coveralls
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sinatra
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.4'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.4'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: httparty
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.11.0
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 0.11.0
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: octokit
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '2.2'
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '2.2'
|
158
|
+
description: A simple sinatra app ready to go for GitHub service hooks.
|
159
|
+
email:
|
160
|
+
- mark.rushakoff@gmail.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- .gitignore
|
166
|
+
- .rspec
|
167
|
+
- .travis.yml
|
168
|
+
- Gemfile
|
169
|
+
- LICENSE.txt
|
170
|
+
- README.md
|
171
|
+
- Rakefile
|
172
|
+
- lib/right_hook.rb
|
173
|
+
- lib/right_hook/app.rb
|
174
|
+
- lib/right_hook/authenticated_client.rb
|
175
|
+
- lib/right_hook/authenticator.rb
|
176
|
+
- lib/right_hook/commenter.rb
|
177
|
+
- lib/right_hook/subscriber.rb
|
178
|
+
- lib/right_hook/version.rb
|
179
|
+
- right_hook.gemspec
|
180
|
+
- spec/app/issue_spec.rb
|
181
|
+
- spec/app/pull_request_spec.rb
|
182
|
+
- spec/app_spec.rb
|
183
|
+
- spec/authenticated_client_spec.rb
|
184
|
+
- spec/authenticator_spec.rb
|
185
|
+
- spec/captain_hook_spec.rb
|
186
|
+
- spec/commenter_spec.rb
|
187
|
+
- spec/spec_helper.rb
|
188
|
+
- spec/subscriber_spec.rb
|
189
|
+
- spec/support/spec_helpers.rb
|
190
|
+
homepage: https://github.com/mark-rushakoff/right_hook
|
191
|
+
licenses:
|
192
|
+
- MIT
|
193
|
+
post_install_message:
|
194
|
+
rdoc_options: []
|
195
|
+
require_paths:
|
196
|
+
- lib
|
197
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ! '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
requirements: []
|
210
|
+
rubyforge_project:
|
211
|
+
rubygems_version: 1.8.23
|
212
|
+
signing_key:
|
213
|
+
specification_version: 3
|
214
|
+
summary: Right Hook is a foundation to use when you just want to write a GitHub service
|
215
|
+
hook.
|
216
|
+
test_files:
|
217
|
+
- spec/app/issue_spec.rb
|
218
|
+
- spec/app/pull_request_spec.rb
|
219
|
+
- spec/app_spec.rb
|
220
|
+
- spec/authenticated_client_spec.rb
|
221
|
+
- spec/authenticator_spec.rb
|
222
|
+
- spec/captain_hook_spec.rb
|
223
|
+
- spec/commenter_spec.rb
|
224
|
+
- spec/spec_helper.rb
|
225
|
+
- spec/subscriber_spec.rb
|
226
|
+
- spec/support/spec_helpers.rb
|
227
|
+
has_rdoc:
|