vx-service_connector 0.2.12 → 0.2.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/lib/vx/service_connector.rb +3 -0
- data/lib/vx/service_connector/bitbucket.rb +51 -0
- data/lib/vx/service_connector/bitbucket/commits.rb +30 -0
- data/lib/vx/service_connector/bitbucket/deploy_keys.rb +29 -0
- data/lib/vx/service_connector/bitbucket/files.rb +21 -0
- data/lib/vx/service_connector/bitbucket/hooks.rb +58 -0
- data/lib/vx/service_connector/bitbucket/notices.rb +11 -0
- data/lib/vx/service_connector/bitbucket/payload.rb +136 -0
- data/lib/vx/service_connector/bitbucket/repos.rb +56 -0
- data/lib/vx/service_connector/bitbucket/session.rb +113 -0
- data/lib/vx/service_connector/error.rb +1 -0
- data/lib/vx/service_connector/version.rb +1 -1
- data/spec/fixtures/bitbucket/add_deploy_key.json +7 -0
- data/spec/fixtures/bitbucket/commit.json +72 -0
- data/spec/fixtures/bitbucket/create_hook.json +12 -0
- data/spec/fixtures/bitbucket/deploy_keys.json +1 -0
- data/spec/fixtures/bitbucket/hooks.json +38 -0
- data/spec/fixtures/bitbucket/payload/created_pull_request.json +120 -0
- data/spec/fixtures/bitbucket/payload/declined_pull_request.json +80 -0
- data/spec/fixtures/bitbucket/payload/foreign_pull_request.json +120 -0
- data/spec/fixtures/bitbucket/payload/push.json +38 -0
- data/spec/fixtures/bitbucket/payload/updated_pull_request.json +80 -0
- data/spec/fixtures/bitbucket/repos.json +206 -0
- data/spec/fixtures/bitbucket/user_privileges.json +6 -0
- data/spec/fixtures/bitbucket/user_repo.json +63 -0
- data/spec/fixtures/bitbucket/user_repos.json +197 -0
- data/spec/lib/bitbucket_payload_spec.rb +82 -0
- data/spec/lib/bitbucket_spec.rb +144 -0
- data/spec/support/bitbucket_web_mocks.rb +87 -0
- data/vx-service_connector.gemspec +4 -3
- metadata +59 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54d71cae077981d1705c726931668bf9b2340cb9
|
4
|
+
data.tar.gz: c0dea00e6d39a55757f96b6bd6bc6cb9e4428182
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbb12a18dd04bb71320c037805d831f1e809fbe32b0d91055dfdcbd4287b6fd395dacb32fc67df1509f4654c0b19af8910f02b3bfc323f79910d4affc54d82da
|
7
|
+
data.tar.gz: 07315c7f4b03f472551857bbda073f7a8c934dce4fe324b15730194243fc5dd4d7de0fcf72089c08bf3ba34dca0e97d6ee9d7f9f2a33f2f8ee5339bb293a5fce
|
data/Gemfile
CHANGED
data/lib/vx/service_connector.rb
CHANGED
@@ -9,6 +9,7 @@ module Vx
|
|
9
9
|
autoload :Github, File.expand_path("../service_connector/github", __FILE__)
|
10
10
|
autoload :GitlabV5, File.expand_path("../service_connector/gitlab_v5", __FILE__)
|
11
11
|
autoload :GitlabV6, File.expand_path("../service_connector/gitlab_v6", __FILE__)
|
12
|
+
autoload :Bitbucket, File.expand_path("../service_connector/bitbucket", __FILE__)
|
12
13
|
autoload :Model, File.expand_path("../service_connector/model", __FILE__)
|
13
14
|
|
14
15
|
extend self
|
@@ -19,6 +20,8 @@ module Vx
|
|
19
20
|
Github
|
20
21
|
when :gitlab_v6
|
21
22
|
GitlabV6
|
23
|
+
when :bitbucket
|
24
|
+
Bitbucket
|
22
25
|
else
|
23
26
|
raise ArgumentError, "Serivice for #{name.inspect} is not defined"
|
24
27
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
Bitbucket = Struct.new(:login, :options) do
|
4
|
+
|
5
|
+
include ServiceConnector::Base
|
6
|
+
|
7
|
+
def repos
|
8
|
+
Bitbucket::Repos.new(session).to_a
|
9
|
+
end
|
10
|
+
|
11
|
+
def organizations
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
def hooks(repo)
|
16
|
+
Bitbucket::Hooks.new(session, repo)
|
17
|
+
end
|
18
|
+
|
19
|
+
def deploy_keys(repo)
|
20
|
+
Bitbucket::DeployKeys.new(session, repo)
|
21
|
+
end
|
22
|
+
|
23
|
+
def notices(repo)
|
24
|
+
Bitbucket::Notices.new(session, repo)
|
25
|
+
end
|
26
|
+
|
27
|
+
def files(repo)
|
28
|
+
Bitbucket::Files.new(session, repo)
|
29
|
+
end
|
30
|
+
|
31
|
+
def payload(repo, params)
|
32
|
+
Bitbucket::Payload.new(session, params).build
|
33
|
+
end
|
34
|
+
|
35
|
+
def commits(repo, options = {})
|
36
|
+
Bitbucket::Commits.new(session, repo)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_session
|
42
|
+
self.class::Session.new(login, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Dir[File.expand_path("../bitbucket/*.rb", __FILE__)].each do |f|
|
50
|
+
require f
|
51
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
class Bitbucket
|
4
|
+
Commits = Struct.new(:session, :repo) do
|
5
|
+
def last(options = {})
|
6
|
+
begin
|
7
|
+
commits = session.get "api/1.0/repositories/#{repo.full_name}/changesets/?limit=1"
|
8
|
+
commit = commits["changesets"].first
|
9
|
+
author, email = commit["raw_author"].split(/[<>]/)
|
10
|
+
sha = commit["raw_node"]
|
11
|
+
branch = commit["branch"] || 'master'
|
12
|
+
Model::Payload.from_hash(
|
13
|
+
skip: false,
|
14
|
+
pull_request?: false,
|
15
|
+
branch: branch,
|
16
|
+
branch_label: branch,
|
17
|
+
sha: sha,
|
18
|
+
message: commit["message"],
|
19
|
+
author: author.strip,
|
20
|
+
author_email: email,
|
21
|
+
web_url: session.endpoint.to_s + "/#{repo.full_name}/commits/#{sha}"
|
22
|
+
)
|
23
|
+
rescue RequestError
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
class Bitbucket
|
4
|
+
DeployKeys = Struct.new(:session, :repo) do
|
5
|
+
|
6
|
+
def all
|
7
|
+
begin
|
8
|
+
session.get "api/1.0/repositories/#{repo.full_name}/deploy-keys?pagelen=100"
|
9
|
+
rescue RequestError
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(key_name, public_key)
|
15
|
+
session.post "api/1.0/repositories/#{repo.full_name}/deploy-keys", label: key_name, key: public_key
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy(key_name)
|
19
|
+
all.select do |key|
|
20
|
+
key['label'] == key_name
|
21
|
+
end.map do |key|
|
22
|
+
session.delete "api/1.0/repositories/#{repo.full_name}/deploy-keys/#{key['pk']}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module ServiceConnector
|
5
|
+
class Bitbucket
|
6
|
+
Files = Struct.new(:session, :repo) do
|
7
|
+
|
8
|
+
def get(sha, path)
|
9
|
+
begin
|
10
|
+
re = session.get("api/1.0/repositories/#{repo.full_name}/src/#{sha}/#{path}")
|
11
|
+
re['source']
|
12
|
+
rescue RequestError => e
|
13
|
+
$stderr.puts "ERROR: #{e.inspect}"
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
class Bitbucket
|
4
|
+
Hooks = Struct.new(:session, :repo) do
|
5
|
+
|
6
|
+
def all
|
7
|
+
begin
|
8
|
+
session.get hooks_url
|
9
|
+
rescue RequestError
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(url, token)
|
15
|
+
session.post(
|
16
|
+
hooks_url,
|
17
|
+
'type' => 'POST',
|
18
|
+
'URL' => url
|
19
|
+
)
|
20
|
+
session.post(
|
21
|
+
hooks_url,
|
22
|
+
'type' => 'Pull Request POST',
|
23
|
+
'URL' => url,
|
24
|
+
'create/edit/merge/decline' => 'on',
|
25
|
+
'comments' => 'off',
|
26
|
+
'approve/unapprove' => 'off'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy(url_mask)
|
31
|
+
all.select do |hook|
|
32
|
+
url = extract_url(hook)
|
33
|
+
url && url =~ /#{Regexp.escape url_mask}/
|
34
|
+
end.map do |hook|
|
35
|
+
session.delete hook_url(hook['id'])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def extract_url(hook)
|
42
|
+
fields = hook['service']['fields']
|
43
|
+
url = fields.select{|f| f['name'] == 'URL' }.map{|f| f['value'] }.first
|
44
|
+
url
|
45
|
+
end
|
46
|
+
|
47
|
+
def hooks_url
|
48
|
+
"api/1.0/repositories/#{repo.full_name}/services"
|
49
|
+
end
|
50
|
+
|
51
|
+
def hook_url(id)
|
52
|
+
"#{hooks_url}/#{id}"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
class Bitbucket
|
4
|
+
Payload = Struct.new(:session, :params) do
|
5
|
+
|
6
|
+
def build
|
7
|
+
ServiceConnector::Model::Payload.new(
|
8
|
+
!!ignore?,
|
9
|
+
pull_request?,
|
10
|
+
pull_request_number,
|
11
|
+
branch,
|
12
|
+
branch_label,
|
13
|
+
sha,
|
14
|
+
message,
|
15
|
+
author,
|
16
|
+
author_email,
|
17
|
+
web_url
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def pull_request?
|
24
|
+
!key? 'repository'
|
25
|
+
end
|
26
|
+
|
27
|
+
def pull_request_number
|
28
|
+
if pull_request? && pull_request.key?('id')
|
29
|
+
pull_request['id']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def sha
|
34
|
+
if pull_request?
|
35
|
+
pull_request['source']['commit']['hash']
|
36
|
+
else
|
37
|
+
head_commit['raw_node']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def branch
|
42
|
+
@branch ||= begin
|
43
|
+
if pull_request?
|
44
|
+
pull_request['source']['branch']['name']
|
45
|
+
else
|
46
|
+
head_commit['branch']
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def branch_label
|
52
|
+
branch
|
53
|
+
end
|
54
|
+
|
55
|
+
def web_url
|
56
|
+
if pull_request?
|
57
|
+
pull_request['links'] ? pull_request['links']['html']['href'] : nil
|
58
|
+
else
|
59
|
+
"https://bitbucket.org#{params['repository']['absolute_url']}commits/#{head_commit['raw_node']}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def message
|
64
|
+
if pull_request?
|
65
|
+
commit_for_pull_request["message"]
|
66
|
+
else
|
67
|
+
head_commit['message']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def author
|
72
|
+
if pull_request?
|
73
|
+
commit_for_pull_request["author"]["user"]["display_name"]
|
74
|
+
else
|
75
|
+
head_commit['author']
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def author_email
|
80
|
+
if pull_request?
|
81
|
+
commit_for_pull_request["author"]["raw"][/.*<([^>]*)/,1]
|
82
|
+
else
|
83
|
+
head_commit['raw_author'][/.*<([^>]*)/,1]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def close_pull_request?
|
88
|
+
pull_request? && (pull_request['state'] && %w(DECLINED MERGED).include?(pull_request['state']))
|
89
|
+
end
|
90
|
+
|
91
|
+
def pull_request_head_repo_full_name
|
92
|
+
pull_request['source']['repository']['full_name']
|
93
|
+
end
|
94
|
+
|
95
|
+
def pull_request_base_repo_full_name
|
96
|
+
pull_request['destination']['repository']['full_name']
|
97
|
+
end
|
98
|
+
|
99
|
+
def foreign_pull_request?
|
100
|
+
pull_request_head_repo_full_name != pull_request_base_repo_full_name
|
101
|
+
end
|
102
|
+
|
103
|
+
def ignore?
|
104
|
+
if pull_request?
|
105
|
+
close_pull_request? || !foreign_pull_request?
|
106
|
+
else
|
107
|
+
sha == '0000000000000000000000000000000000000000'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def commit_for_pull_request
|
112
|
+
@commit_for_pull_request ||= begin
|
113
|
+
session.get pull_request['source']['commit']['links']['self']['href']
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def head_commit
|
118
|
+
params['commits'].last || {}
|
119
|
+
end
|
120
|
+
|
121
|
+
def pull_request
|
122
|
+
params.first.last
|
123
|
+
end
|
124
|
+
|
125
|
+
def key?(name)
|
126
|
+
params.key? name
|
127
|
+
end
|
128
|
+
|
129
|
+
def [](val)
|
130
|
+
params[val]
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Vx
|
2
|
+
module ServiceConnector
|
3
|
+
class Bitbucket
|
4
|
+
Repos = Struct.new(:session) do
|
5
|
+
|
6
|
+
def to_a
|
7
|
+
@repos ||= user_repositories
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def user_repositories
|
13
|
+
res = session.get("api/1.0/user/repositories")
|
14
|
+
res.select do |repo|
|
15
|
+
git?(repo) && repo_access?(repo['owner'])
|
16
|
+
end.map do |repo|
|
17
|
+
repo_to_model repo
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def repo_to_model(repo)
|
22
|
+
name = repo['owner'] + "/" + repo['slug']
|
23
|
+
Model::Repo.new(
|
24
|
+
name,
|
25
|
+
name,
|
26
|
+
repo['is_private'],
|
27
|
+
"git@#{session.endpoint.host}/#{name}.git",
|
28
|
+
"#{session.endpoint}/#{name}",
|
29
|
+
repo['description']
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def team_admin
|
34
|
+
@team_admin ||= begin
|
35
|
+
values = session.get("api/1.0/user/privileges")['teams']
|
36
|
+
values.select{|k,v| v == 'admin' }.keys
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def login
|
41
|
+
session.login
|
42
|
+
end
|
43
|
+
|
44
|
+
def git?(repo)
|
45
|
+
repo['scm'] == 'git'
|
46
|
+
end
|
47
|
+
|
48
|
+
def repo_access?(repo_owner)
|
49
|
+
repo_owner == login ||
|
50
|
+
team_admin.include?(repo_owner)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
require 'json'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Vx
|
7
|
+
module ServiceConnector
|
8
|
+
class Bitbucket
|
9
|
+
Session = Struct.new(:login, :options) do
|
10
|
+
|
11
|
+
def get(url)
|
12
|
+
wrap do
|
13
|
+
res = agent.get request_url(url)
|
14
|
+
response! res
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def post(url, options = {})
|
19
|
+
wrap do
|
20
|
+
res = agent.post request_url(url), options
|
21
|
+
response! res
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(url)
|
26
|
+
wrap do
|
27
|
+
res = agent.delete request_url(url)
|
28
|
+
response! res
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def endpoint
|
33
|
+
@endpoint ||= URI("https://bitbucket.org")
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.test
|
37
|
+
{
|
38
|
+
consumer_key: "key",
|
39
|
+
consumer_secret: "secret",
|
40
|
+
token: "token",
|
41
|
+
token_secret: "token secret"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
|
48
|
+
def request_url(url)
|
49
|
+
if url.include?(endpoint.to_s)
|
50
|
+
url
|
51
|
+
else
|
52
|
+
"#{endpoint}/#{url}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def response!(res)
|
57
|
+
if (200..204).include?(res.code.to_i)
|
58
|
+
if res.header['Content-Type'].include?("application/json")
|
59
|
+
::JSON.parse(res.body)
|
60
|
+
else
|
61
|
+
res.body
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise RequestError, res.body
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def wrap
|
69
|
+
begin
|
70
|
+
yield
|
71
|
+
rescue Errno::ETIMEDOUT => e
|
72
|
+
raise RequestError, e
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_options!
|
77
|
+
unless options.is_a?(Hash) && options.keys.sort == [:consumer_key, :consumer_secret, :token, :token_secret]
|
78
|
+
raise InvalidArguments, options
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def agent
|
83
|
+
@agent ||= begin
|
84
|
+
validate_options!
|
85
|
+
consumer = OAuth::Consumer.new(
|
86
|
+
options[:consumer_key], options[:consumer_secret],
|
87
|
+
site: endpoint.to_s
|
88
|
+
)
|
89
|
+
token = OAuth::AccessToken.new consumer, options[:token], options[:token_secret]
|
90
|
+
=begin
|
91
|
+
Sawyer::Agent.new(endpoint) do |http|
|
92
|
+
http.headers['content-type'] = 'application/json'
|
93
|
+
http.headers['accept'] = 'application/json'
|
94
|
+
http.ssl.verify = false
|
95
|
+
http.options[:timeout] = 5
|
96
|
+
http.options[:open_timeout] = 5
|
97
|
+
http.request :oauth,
|
98
|
+
consumer_key: "7j4RYZEd8YTQdfTWkJ",
|
99
|
+
consumer_secret: "Y8fSdaDK4GxKzvJn3tKRzyyYXctYSbUV",
|
100
|
+
token: "WhFvryHuZ82XLKW6aP",
|
101
|
+
token_secret: "zLSQe2DwhA4mfzwnPA53pKqLSyZgX5XH"
|
102
|
+
ignore_extra_keys: true
|
103
|
+
http.response :logger
|
104
|
+
end
|
105
|
+
=end
|
106
|
+
token
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|