pact_broker-client 1.14.1 → 1.15.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.
- 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
|