Skip to content

Commit 0eed596

Browse files
committed
feat(webhook templating): add support for ${pactbroker.pactUrl} in query and body
For pact-foundation#54
1 parent 8424e14 commit 0eed596

File tree

8 files changed

+113
-21
lines changed

8 files changed

+113
-21
lines changed

lib/pact_broker/app.rb

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'pact_broker/project_root'
44
require 'rack-protection'
55
require 'rack/hal_browser'
6+
require 'rack/pact_broker/store_base_url'
67
require 'rack/pact_broker/add_pact_broker_version_header'
78
require 'rack/pact_broker/convert_file_extension_to_accept_header'
89
require 'rack/pact_broker/database_transaction'
@@ -106,6 +107,7 @@ def configure_middleware
106107
# NOTE THAT NONE OF THIS IS PROTECTED BY AUTH - is that ok?
107108
@app_builder.use Rack::Protection, except: [:path_traversal, :remote_token, :session_hijacking, :http_origin]
108109
@app_builder.use Rack::PactBroker::InvalidUriProtection
110+
@app_builder.use Rack::PactBroker::StoreBaseURL
109111
@app_builder.use Rack::PactBroker::AddPactBrokerVersionHeader
110112
@app_builder.use Rack::Static, :urls => ["/stylesheets", "/css", "/fonts", "/js", "/javascripts", "/images"], :root => PactBroker.project_root.join("public")
111113
@app_builder.use Rack::Static, :urls => ["/favicon.ico"], :root => PactBroker.project_root.join("public/images"), header_rules: [[:all, {'Content-Type' => 'image/x-icon'}]]

lib/pact_broker/configuration.rb

+4
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ def enable_badge_resources= enable_badge_resources
123123
self.enable_public_badge_access = enable_badge_resources
124124
end
125125

126+
def base_url
127+
ENV['PACT_BROKER_BASE_URL']
128+
end
129+
126130
def save_to_database
127131
# Can't require a Sequel::Model class before the connection has been set
128132
require 'pact_broker/config/save'

lib/pact_broker/domain/webhook_request.rb

+30-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'pact_broker/messages'
55
require 'net/http'
66
require 'pact_broker/webhooks/redact_logs'
7+
require 'pact_broker/api/pact_broker_urls'
78

89
module PactBroker
910

@@ -51,17 +52,18 @@ def execute pact, options = {}
5152
logs = StringIO.new
5253
execution_logger = Logger.new(logs)
5354
begin
54-
execute_and_build_result(options, logs, execution_logger)
55+
execute_and_build_result(pact, options, logs, execution_logger)
5556
rescue StandardError => e
5657
handle_error_and_build_result(e, options, logs, execution_logger)
5758
end
5859
end
5960

6061
private
6162

62-
def execute_and_build_result options, logs, execution_logger
63-
req = build_request(execution_logger)
64-
response = do_request(req)
63+
def execute_and_build_result pact, options, logs, execution_logger
64+
uri = build_uri(pact)
65+
req = build_request(uri, pact, execution_logger)
66+
response = do_request(uri, req)
6567
log_response(response, execution_logger)
6668
result = WebhookExecutionResult.new(response, logs.string)
6769
log_completion_message(options, execution_logger, result.success?)
@@ -75,9 +77,9 @@ def handle_error_and_build_result e, options, logs, execution_logger
7577
WebhookExecutionResult.new(nil, logs.string, e)
7678
end
7779

78-
def build_request execution_logger
79-
req = http_request
80-
execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials}"
80+
def build_request uri, pact, execution_logger
81+
req = http_request(uri)
82+
execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials(pact)}"
8183

8284
headers.each_pair do | name, value |
8385
execution_logger.info Webhooks::RedactLogs.call("#{name}: #{value}")
@@ -88,17 +90,17 @@ def build_request execution_logger
8890

8991
unless body.nil?
9092
if String === body
91-
req.body = body
93+
req.body = gsub_body(pact, body)
9294
else
93-
req.body = body.to_json
95+
req.body = gsub_body(pact, body.to_json)
9496
end
9597
end
9698

9799
execution_logger.info req.body
98100
req
99101
end
100102

101-
def do_request req
103+
def do_request uri, req
102104
logger.info "Making webhook #{uuid} request #{to_s}"
103105
Net::HTTP.start(uri.hostname, uri.port,
104106
:use_ssl => uri.scheme == 'https') do |http|
@@ -126,19 +128,31 @@ def to_s
126128
"#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{headers}, body=#{body}"
127129
end
128130

129-
def http_request
130-
Net::HTTP.const_get(method.capitalize).new(url)
131+
def http_request(uri)
132+
Net::HTTP.const_get(method.capitalize).new(uri)
131133
end
132134

133-
def uri
134-
URI(url)
135+
def build_uri pact
136+
URI(gsub_url(pact, url))
135137
end
136138

137-
def url_with_credentials
138-
u = URI(url)
139+
def url_with_credentials pact
140+
u = build_uri(pact)
139141
u.userinfo = "#{username}:#{display_password}" if username
140142
u
141143
end
144+
145+
def gsub_body pact, body
146+
base_url = PactBroker.configuration.base_url
147+
body.gsub('${pactbroker.pactUrl}', PactBroker::Api::PactBrokerUrls.pact_url(base_url, pact))
148+
end
149+
150+
def gsub_url pact, url
151+
base_url = PactBroker.configuration.base_url
152+
pact_url = PactBroker::Api::PactBrokerUrls.pact_url(base_url, pact)
153+
escaped_pact_url = CGI::escape(pact_url)
154+
url.gsub('${pactbroker.pactUrl}', escaped_pact_url)
155+
end
142156
end
143157
end
144158
end
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Rack
2+
module PactBroker
3+
class StoreBaseURL
4+
def initialize app
5+
@app = app
6+
end
7+
8+
def call(env)
9+
ENV['PACT_BROKER_BASE_URL'] ||= ::Rack::Request.new(env).base_url
10+
@app.call(env)
11+
end
12+
end
13+
end
14+
end

script/seed.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def publish_pact params = {}
5353
.create_label("microservice")
5454
.create_provider("Bar")
5555
.create_label("microservice")
56-
.create_webhook(method: 'GET', url: 'http://localhost:9393/')
56+
.create_webhook(method: 'GET', url: 'http://localhost:9393?url=${pactbroker.pactUrl}', body: '${pactbroker.pactUrl}')
5757
.create_consumer_version("1.2.100")
5858
.publish_pact
5959
.create_verification(provider_version: "1.4.234", success: true, execution_date: DateTime.now - 15)

script/webhook-server.ru

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
require 'rack/utils'
2+
13
count = 0
24
run -> (env) {
35
count += 1
46
status = (count % 3 == 0) ? 200 : 500
5-
puts "Received request"; [status, {}, ["Hello. This might be an error.\n"]]
7+
puts Rack::Utils.parse_nested_query(env['QUERY_STRING'])
8+
puts env['rack.input'].read
9+
[status, {}, ["Hello. This might be an error.\n"]]
610
}

spec/features/execute_webhook_spec.rb

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
let(:td) { TestDataBuilder.new }
88

99
before do
10-
td.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
11-
.create_webhook(method: 'POST')
10+
ENV['PACT_BROKER_BASE_URL'] = 'http://example.org'
11+
td.create_pact_with_hierarchy("Foo", "1", "Bar")
12+
.create_webhook(method: 'POST', body: '${pactbroker.pactUrl}')
13+
end
14+
15+
after do
16+
ENV.delete('PACT_BROKER_BASE_URL')
1217
end
1318

1419
let(:path) { "/webhooks/#{td.webhook.uuid}/execute" }
@@ -18,7 +23,7 @@
1823

1924
context "when the execution is successful" do
2025
let!(:request) do
21-
stub_request(:post, /http/).to_return(:status => 200)
26+
stub_request(:post, /http/).with(body: 'http://example.org/pacts/provider/Bar/consumer/Foo/version/1').to_return(:status => 200)
2227
end
2328

2429
it "performs the HTTP request" do

spec/lib/pact_broker/domain/webhook_request_spec.rb

+49
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ module PactBroker
77
module Domain
88

99
describe WebhookRequest do
10+
before do
11+
allow(PactBroker::Api::PactBrokerUrls).to receive(:pact_url).and_return('http://example.org/pact-url')
12+
allow(PactBroker.configuration).to receive(:base_url).and_return('http://example.org')
13+
end
1014

1115
let(:username) { nil }
1216
let(:password) { nil }
@@ -29,6 +33,7 @@ module Domain
2933

3034
let(:logs) { subject.execute(pact, options).logs }
3135

36+
3237
describe "description" do
3338
it "returns a brief description of the HTTP request" do
3439
expect(subject.description).to eq 'POST example.org'
@@ -57,6 +62,50 @@ module Domain
5762
to_return(:status => 200, :body => "respbod", :headers => {'Content-Type' => 'text/foo, blah'})
5863
end
5964

65+
describe "when the String body contains a ${pactbroker.pactUrl} parameter" do
66+
let!(:http_request) do
67+
stub_request(:post, "http://example.org/hook").
68+
with(:headers => {'Content-Type'=>'text/plain'}, :body => "<xml><url>http://example.org/pact-url</url></xml>").
69+
to_return(:status => 200)
70+
end
71+
72+
let(:body) { "<xml><url>${pactbroker.pactUrl}</url></xml>" }
73+
74+
it "replaces the token with the live value" do
75+
subject.execute(pact, options)
76+
expect(http_request).to have_been_made
77+
end
78+
end
79+
80+
describe "when the JSON body contains a ${pactbroker.pactUrl} parameter" do
81+
let!(:http_request) do
82+
stub_request(:post, "http://example.org/hook").
83+
with(:headers => {'Content-Type'=>'text/plain'}, :body => '{"url":"http://example.org/pact-url"}').
84+
to_return(:status => 200)
85+
end
86+
87+
let(:body) { { url: '${pactbroker.pactUrl}' } }
88+
89+
it "replaces the token with the live value" do
90+
subject.execute(pact, options)
91+
expect(http_request).to have_been_made
92+
end
93+
end
94+
95+
describe "when the URL contains a ${pactbroker.pactUrl} parameter" do
96+
let!(:http_request) do
97+
stub_request(:post, "http://example.org/hook?url=http%3A%2F%2Fexample.org%2Fpact-url").
98+
to_return(:status => 200)
99+
end
100+
101+
let(:url) { 'http://example.org/hook?url=${pactbroker.pactUrl}' }
102+
103+
it "replaces the token with the live value" do
104+
subject.execute(pact, options)
105+
expect(http_request).to have_been_made
106+
end
107+
end
108+
60109
it "executes the configured request" do
61110
subject.execute(pact, options)
62111
expect(http_request).to have_been_made

0 commit comments

Comments
 (0)