pact_broker-client 1.14.1 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +21 -0
- data/README.md +27 -1
- data/Rakefile +9 -0
- data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +521 -10
- data/lib/pact_broker/client/base_client.rb +1 -1
- data/lib/pact_broker/client/cli/broker.rb +63 -0
- data/lib/pact_broker/client/cli/can_i_deploy_long_desc.txt +1 -1
- data/lib/pact_broker/client/cli/create_webhook_long_desc.txt +1 -0
- data/lib/pact_broker/client/command_result.rb +12 -0
- data/lib/pact_broker/client/hal.rb +4 -0
- data/lib/pact_broker/client/hal/entity.rb +78 -0
- data/lib/pact_broker/client/hal/entry_point.rb +13 -0
- data/lib/pact_broker/client/hal/http_client.rb +82 -0
- data/lib/pact_broker/client/hal/link.rb +79 -0
- data/lib/pact_broker/client/pacts.rb +7 -1
- data/lib/pact_broker/client/version.rb +1 -1
- data/lib/pact_broker/client/versions.rb +0 -1
- data/lib/pact_broker/client/webhooks/create.rb +114 -0
- data/spec/integration/can_i_deploy_spec.rb +1 -3
- data/spec/integration/create_version_tag_spec.rb +1 -3
- data/spec/lib/pact_broker/client/can_i_deploy_spec.rb +1 -1
- data/spec/lib/pact_broker/client/cli/broker_create_webhook_spec.rb +190 -0
- data/spec/lib/pact_broker/client/hal/entity_spec.rb +93 -0
- data/spec/lib/pact_broker/client/hal/http_client_spec.rb +105 -0
- data/spec/lib/pact_broker/client/hal/link_spec.rb +108 -0
- data/spec/lib/pact_broker/client/webhooks/create_spec.rb +61 -0
- data/spec/pacts/pact_broker_client-pact_broker.json +499 -9
- data/spec/service_providers/pact_broker_client_publish_spec.rb +6 -1
- data/spec/service_providers/pact_broker_client_retrieve_all_pacts_for_provider_spec.rb +1 -1
- data/spec/service_providers/{pact_broker_client_retrive_pact_spec.rb → pact_broker_client_retrieve_pact_spec.rb} +2 -3
- data/spec/service_providers/pact_broker_client_versions_spec.rb +1 -1
- data/spec/service_providers/pact_helper.rb +46 -0
- data/spec/service_providers/webhooks_create_spec.rb +255 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/latest_pacts_for_provider.json +1 -1
- data/spec/support/shared_context.rb +6 -3
- metadata +24 -4
@@ -14,6 +14,7 @@ module PactBroker
|
|
14
14
|
module CLI
|
15
15
|
# Thor::Error will have its backtrace hidden
|
16
16
|
class PactPublicationError < ::Thor::Error; end
|
17
|
+
class WebhookCreationError < ::Thor::Error; end
|
17
18
|
|
18
19
|
class Broker < CustomThor
|
19
20
|
desc 'can-i-deploy', ''
|
@@ -99,6 +100,68 @@ module PactBroker
|
|
99
100
|
exit(1) unless result.success
|
100
101
|
end
|
101
102
|
|
103
|
+
method_option :request, aliases: "-X", desc: "HTTP method", required: true
|
104
|
+
method_option :header, aliases: "-H", type: :array, desc: "Header"
|
105
|
+
method_option :data, aliases: "-d", desc: "Data"
|
106
|
+
method_option :user, aliases: "-u", desc: "Basic auth username and password eg. username:password"
|
107
|
+
method_option :consumer, desc: "Consumer name"
|
108
|
+
method_option :provider, desc: "Provider name"
|
109
|
+
method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
|
110
|
+
method_option :broker_username, aliases: "-u", desc: "Pact Broker basic auth username"
|
111
|
+
method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
|
112
|
+
method_option :contract_content_changed, type: :boolean, desc: "Trigger this webhook when the pact content changes"
|
113
|
+
method_option :provider_verification_published, type: :boolean, desc: "Trigger this webhook when a provider verification result is published"
|
114
|
+
method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
|
115
|
+
|
116
|
+
desc 'create-webhook URL', 'Creates a webhook using the same switches as a curl request.'
|
117
|
+
long_desc File.read(File.join(File.dirname(__FILE__), 'create_webhook_long_desc.txt'))
|
118
|
+
def create_webhook webhook_url
|
119
|
+
require 'pact_broker/client/webhooks/create'
|
120
|
+
|
121
|
+
if !(options.contract_content_changed || options.provider_verification_published)
|
122
|
+
raise PactBroker::Client::Error.new("You must select at least one of --contract-content-changed or --provider-verification-published")
|
123
|
+
end
|
124
|
+
|
125
|
+
username = options.user ? options.user.split(":", 2).first : nil
|
126
|
+
password = options.user ? options.user.split(":", 2).last : nil
|
127
|
+
|
128
|
+
headers = (options.header || []).each_with_object({}) { | header, headers | headers[header.split(":", 2).first.strip] = header.split(":", 2).last.strip }
|
129
|
+
|
130
|
+
body = options.data
|
131
|
+
if body && body.start_with?("@")
|
132
|
+
filepath = body[1..-1]
|
133
|
+
begin
|
134
|
+
body = File.read(filepath)
|
135
|
+
rescue StandardError => e
|
136
|
+
raise PactBroker::Client::Error.new("Couldn't read data from file \"#{filepath}\" due to #{e.class} #{e.message}")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
events = []
|
141
|
+
events << 'contract_content_changed' if options.contract_content_changed
|
142
|
+
events << 'provider_verification_published' if options.provider_verification_published
|
143
|
+
|
144
|
+
params = {
|
145
|
+
http_method: options.request,
|
146
|
+
url: webhook_url,
|
147
|
+
headers: headers,
|
148
|
+
username: username,
|
149
|
+
password: password,
|
150
|
+
body: body,
|
151
|
+
consumer: options.consumer,
|
152
|
+
provider: options.provider,
|
153
|
+
events: events
|
154
|
+
}
|
155
|
+
|
156
|
+
begin
|
157
|
+
result = PactBroker::Client::Webhooks::Create.call(params, options.broker_base_url, pact_broker_client_options)
|
158
|
+
$stdout.puts result.message
|
159
|
+
exit(1) unless result.success
|
160
|
+
rescue PactBroker::Client::Error => e
|
161
|
+
raise WebhookCreationError, "#{e.class} - #{e.message}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
102
165
|
desc 'version', "Show the pact_broker-client gem version"
|
103
166
|
def version
|
104
167
|
$stdout.puts PactBroker::Client::VERSION
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Returns exit code 0 or 1, indicating whether or not the specified pacticipant versions are compatible. Prints out the relevant pact/verification details.
|
2
2
|
|
3
|
-
The environment variables
|
3
|
+
The environment variables PACT_BROKER_BASE_URL, PACT_BROKER_BASE_URL_USERNAME and PACT_BROKER_BASE_URL_PASSWORD may be used instead of their respective command line options.
|
4
4
|
|
5
5
|
SCENARIOS
|
6
6
|
|
@@ -0,0 +1 @@
|
|
1
|
+
Create a curl command that executes the request that you want your webhook to execute, then replace "curl" with "pact-broker create-webhook" and add the consumer, provider, event types and broker details. Note that the URL must be the first parameter when executing create-webhook.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'delegate'
|
3
|
+
require 'pact_broker/client/hal/link'
|
4
|
+
|
5
|
+
module PactBroker
|
6
|
+
module Client
|
7
|
+
module Hal
|
8
|
+
class Entity
|
9
|
+
def initialize(data, http_client, response = nil)
|
10
|
+
@data = data
|
11
|
+
@links = (@data || {}).fetch("_links", {})
|
12
|
+
@client = http_client
|
13
|
+
@response = response
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key, *args)
|
17
|
+
_link(key).get(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(key, *args)
|
21
|
+
_link(key).post(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(key, *args)
|
25
|
+
_link(key).put(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def can?(key)
|
29
|
+
@links.key? key.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def follow(key, http_method, *args)
|
33
|
+
Link.new(@links[key].merge(method: http_method), @client).run(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def _link(key)
|
37
|
+
if @links[key]
|
38
|
+
Link.new(@links[key], @client)
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def success?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def response
|
49
|
+
@response
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch(key)
|
53
|
+
@links[key]
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(method_name, *args, &block)
|
57
|
+
if @data.key?(method_name.to_s)
|
58
|
+
@data[method_name.to_s]
|
59
|
+
elsif @links.key?(method_name)
|
60
|
+
Link.new(@links[method_name], @client).run(*args)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to_missing?(method_name, include_private = false)
|
67
|
+
@data.key?(method_name) || @links.key?(method_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class ErrorEntity < Entity
|
72
|
+
def success?
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'pact_broker/client/retry'
|
2
|
+
|
3
|
+
module PactBroker
|
4
|
+
module Client
|
5
|
+
module Hal
|
6
|
+
class HttpClient
|
7
|
+
attr_reader :username, :password, :verbose
|
8
|
+
|
9
|
+
def initialize options
|
10
|
+
@username = options[:username]
|
11
|
+
@password = options[:password]
|
12
|
+
@verbose = options[:verbose]
|
13
|
+
end
|
14
|
+
|
15
|
+
def get href, params = {}, headers = {}
|
16
|
+
query = params.collect{ |(key, value)| "#{CGI::escape(key)}=#{CGI::escape(value)}" }.join("&")
|
17
|
+
uri = URI(href)
|
18
|
+
uri.query = query
|
19
|
+
perform_request(create_request(uri, 'Get', nil, headers), uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
def put href, body = nil, headers = {}
|
23
|
+
uri = URI(href)
|
24
|
+
perform_request(create_request(uri, 'Put', body, headers), uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
def post href, body = nil, headers = {}
|
28
|
+
uri = URI(href)
|
29
|
+
perform_request(create_request(uri, 'Post', body, headers), uri)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_request uri, http_method, body = nil, headers = {}
|
33
|
+
request = Net::HTTP.const_get(http_method).new(uri.request_uri)
|
34
|
+
request['Content-Type'] = "application/json" if ['Post', 'Put', 'Patch'].include?(http_method)
|
35
|
+
request['Accept'] = "application/hal+json"
|
36
|
+
headers.each do | key, value |
|
37
|
+
request[key] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
request.body = body if body
|
41
|
+
request.basic_auth username, password if username
|
42
|
+
request
|
43
|
+
end
|
44
|
+
|
45
|
+
def perform_request request, uri
|
46
|
+
response = Retry.until_true do
|
47
|
+
http = Net::HTTP.new(uri.host, uri.port, :ENV)
|
48
|
+
http.set_debug_output($stderr) if verbose
|
49
|
+
http.use_ssl = (uri.scheme == 'https')
|
50
|
+
http.start do |http|
|
51
|
+
http.request request
|
52
|
+
end
|
53
|
+
end
|
54
|
+
Response.new(response)
|
55
|
+
end
|
56
|
+
|
57
|
+
class Response < SimpleDelegator
|
58
|
+
def body
|
59
|
+
bod = __getobj__().body
|
60
|
+
if bod && bod != ''
|
61
|
+
JSON.parse(bod)
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def raw_body
|
68
|
+
__getobj__().body
|
69
|
+
end
|
70
|
+
|
71
|
+
def status
|
72
|
+
code.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
def success?
|
76
|
+
__getobj__().code.start_with?("2")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module PactBroker
|
5
|
+
module Client
|
6
|
+
module Hal
|
7
|
+
class Link
|
8
|
+
attr_reader :request_method, :href
|
9
|
+
|
10
|
+
def initialize(attrs, http_client)
|
11
|
+
@attrs = attrs
|
12
|
+
@request_method = attrs.fetch(:method, :get).to_sym
|
13
|
+
@href = attrs.fetch('href')
|
14
|
+
@http_client = http_client
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(payload = nil)
|
18
|
+
response = case request_method
|
19
|
+
when :get
|
20
|
+
get(payload)
|
21
|
+
when :put
|
22
|
+
put(payload)
|
23
|
+
when :post
|
24
|
+
post(payload)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def title_or_name
|
29
|
+
title || name
|
30
|
+
end
|
31
|
+
|
32
|
+
def title
|
33
|
+
@attrs['title']
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
@attrs['name']
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(payload = {}, headers = {})
|
41
|
+
wrap_response(@http_client.get(href, payload, headers))
|
42
|
+
end
|
43
|
+
|
44
|
+
def put(payload = nil, headers = {})
|
45
|
+
wrap_response(@http_client.put(href, payload ? JSON.dump(payload) : nil, headers))
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(payload = nil, headers = {})
|
49
|
+
wrap_response(@http_client.post(href, payload ? JSON.dump(payload) : nil, headers))
|
50
|
+
end
|
51
|
+
|
52
|
+
def expand(params)
|
53
|
+
expanded_url = expand_url(params, href)
|
54
|
+
new_attrs = @attrs.merge('href' => expanded_url)
|
55
|
+
Link.new(new_attrs, @http_client)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def wrap_response(http_response)
|
61
|
+
require 'pact_broker/client/hal/entity' # avoid circular reference
|
62
|
+
if http_response.success?
|
63
|
+
Entity.new(http_response.body, @http_client, http_response)
|
64
|
+
else
|
65
|
+
ErrorEntity.new(http_response.body, @http_client, http_response)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def expand_url(params, url)
|
70
|
+
new_url = url
|
71
|
+
params.each do | key, value |
|
72
|
+
new_url = new_url.gsub('{' + key.to_s + '}', URI.escape(value))
|
73
|
+
end
|
74
|
+
new_url
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -49,7 +49,7 @@ module PactBroker
|
|
49
49
|
url = get_latest_provider_contracts(options)
|
50
50
|
response = self.class.get(url, headers: {})
|
51
51
|
handle_response(response) do
|
52
|
-
map_latest_provider_pacts_to_hash
|
52
|
+
map_latest_provider_pacts_to_hash(pact_links(response))
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -63,6 +63,12 @@ module PactBroker
|
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
+
def pact_links response
|
67
|
+
body = JSON.parse(response.body)
|
68
|
+
# pacts relation is deprecated
|
69
|
+
body["_links"]["pb:pacts"] || body["_links"]["pacts"]
|
70
|
+
end
|
71
|
+
|
66
72
|
#TODO Move into mapper class
|
67
73
|
def map_pact_list_to_hash pacts_list
|
68
74
|
pacts_list.collect do | pact_hash |
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pact_broker/client/hal'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'json'
|
5
|
+
require 'pact_broker/client/command_result'
|
6
|
+
|
7
|
+
module PactBroker
|
8
|
+
module Client
|
9
|
+
module Webhooks
|
10
|
+
class Create
|
11
|
+
|
12
|
+
WEBHOOKS_WITH_OPTIONAL_PACTICICPANTS_NOT_SUPPORTED = "Your version of the Pact Broker requires that both consumer and provider are specified for a webhook. Please upgrade your broker to >= 2.22.0 to create a webhook with optional consumer and provider."
|
13
|
+
|
14
|
+
attr_reader :params, :pact_broker_base_url, :basic_auth_options, :verbose
|
15
|
+
|
16
|
+
def self.call(params, pact_broker_base_url, pact_broker_client_options)
|
17
|
+
new(params, pact_broker_base_url, pact_broker_client_options).call
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(params, pact_broker_base_url, pact_broker_client_options)
|
21
|
+
@params = OpenStruct.new(params)
|
22
|
+
@pact_broker_base_url = pact_broker_base_url
|
23
|
+
@basic_auth_options = pact_broker_client_options[:basic_auth] || {}
|
24
|
+
@verbose = pact_broker_client_options[:verbose]
|
25
|
+
@http_client = PactBroker::Client::Hal::HttpClient.new(basic_auth_options.merge(verbose: verbose))
|
26
|
+
end
|
27
|
+
|
28
|
+
def call
|
29
|
+
if params.consumer && params.provider
|
30
|
+
create_webhook_with_consumer_and_provider
|
31
|
+
else
|
32
|
+
create_webhook_with_optional_consumer_and_provider
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :http_client
|
39
|
+
|
40
|
+
def create_webhook_with_consumer_and_provider
|
41
|
+
webhook_entity = webhook_link.expand(consumer: params.consumer, provider: params.provider).post(request_body)
|
42
|
+
handle_response(webhook_entity)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_webhook_with_optional_consumer_and_provider
|
46
|
+
webhook_entity = index_link.get._link("pb:webhooks").post(request_body_with_optional_consumer_and_provider)
|
47
|
+
|
48
|
+
if webhook_entity.response.status == 405
|
49
|
+
raise PactBroker::Client::Error.new(WEBHOOKS_WITH_OPTIONAL_PACTICICPANTS_NOT_SUPPORTED)
|
50
|
+
end
|
51
|
+
|
52
|
+
handle_response(webhook_entity)
|
53
|
+
end
|
54
|
+
|
55
|
+
def request_body
|
56
|
+
webhook_request_body = JSON.parse(params.body) rescue params.body
|
57
|
+
{
|
58
|
+
events: params.events.collect{ | event | { "name" => event }},
|
59
|
+
request: {
|
60
|
+
url: params.url,
|
61
|
+
method: params.http_method,
|
62
|
+
headers: params.headers,
|
63
|
+
body: webhook_request_body,
|
64
|
+
username: params.username,
|
65
|
+
password: params.password
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def request_body_with_optional_consumer_and_provider
|
71
|
+
body = request_body
|
72
|
+
|
73
|
+
if params.consumer
|
74
|
+
body[:consumer] = { name: params.consumer }
|
75
|
+
end
|
76
|
+
|
77
|
+
if params.provider
|
78
|
+
body[:provider] = { name: params.provider }
|
79
|
+
end
|
80
|
+
|
81
|
+
body
|
82
|
+
end
|
83
|
+
|
84
|
+
def webhook_for_consumer_and_provider_url
|
85
|
+
"#{pact_broker_base_url.chomp("/")}/webhooks/provider/{provider}/consumer/{consumer}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_response(webhook_entity)
|
89
|
+
if webhook_entity.success?
|
90
|
+
success_result(webhook_entity)
|
91
|
+
else
|
92
|
+
error_result(webhook_entity)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def success_result(webhook_entity)
|
97
|
+
CommandResult.new(true, "Webhook #{webhook_entity._link('self').title_or_name.inspect} created")
|
98
|
+
end
|
99
|
+
|
100
|
+
def error_result(webhook_entity)
|
101
|
+
CommandResult.new(false, "Error creating webhook. response status=#{webhook_entity.response.status} body=#{webhook_entity.response.raw_body}")
|
102
|
+
end
|
103
|
+
|
104
|
+
def index_link
|
105
|
+
PactBroker::Client::Hal::EntryPoint.new(pact_broker_base_url, http_client)
|
106
|
+
end
|
107
|
+
|
108
|
+
def webhook_link
|
109
|
+
PactBroker::Client::Hal::EntryPoint.new(webhook_for_consumer_and_provider_url, http_client)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|