pact_broker 2.22.0 → 2.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +1 -0
- data/db/migrations/20180611_make_webhook_pacticipant_ids_optional.rb +11 -0
- data/example/config.ru +2 -0
- data/lib/pact_broker/api.rb +9 -3
- data/lib/pact_broker/api/contracts/webhook_contract.rb +36 -0
- data/lib/pact_broker/api/decorators/pact_decorator.rb +22 -1
- data/lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb +1 -26
- data/lib/pact_broker/api/decorators/triggered_webhook_decorator.rb +33 -0
- data/lib/pact_broker/api/decorators/triggered_webhooks_decorator.rb +19 -0
- data/lib/pact_broker/api/decorators/verification_decorator.rb +6 -0
- data/lib/pact_broker/api/decorators/webhook_decorator.rb +32 -18
- data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +27 -0
- data/lib/pact_broker/api/decorators/{webhook_request_decorator.rb → webhook_request_template_decorator.rb} +1 -1
- data/lib/pact_broker/api/pact_broker_urls.rb +21 -1
- data/lib/pact_broker/api/resources/all_webhooks.rb +82 -0
- data/lib/pact_broker/api/resources/base_resource.rb +18 -0
- data/lib/pact_broker/api/resources/error_handler.rb +5 -1
- data/lib/pact_broker/api/resources/pact_triggered_webhooks.rb +41 -0
- data/lib/pact_broker/api/resources/pact_webhooks.rb +2 -15
- data/lib/pact_broker/api/resources/pact_webhooks_status.rb +1 -1
- data/lib/pact_broker/api/resources/verification_triggered_webhooks.rb +45 -0
- data/lib/pact_broker/api/resources/webhook_execution.rb +1 -5
- data/lib/pact_broker/api/resources/webhooks.rb +69 -6
- data/lib/pact_broker/configuration.rb +5 -2
- data/lib/pact_broker/doc/controllers/app.rb +1 -2
- data/lib/pact_broker/doc/views/pact-webhooks.markdown +3 -0
- data/lib/pact_broker/doc/views/webhooks.markdown +53 -33
- data/lib/pact_broker/domain/pact.rb +4 -0
- data/lib/pact_broker/domain/webhook.rb +19 -3
- data/lib/pact_broker/domain/webhook_execution_result.rb +6 -1
- data/lib/pact_broker/domain/webhook_request.rb +87 -65
- data/lib/pact_broker/locale/en.yml +1 -0
- data/lib/pact_broker/matrix/repository.rb +3 -1
- data/lib/pact_broker/pacts/placeholder_pact.rb +17 -0
- data/lib/pact_broker/pacts/repository.rb +14 -0
- data/lib/pact_broker/pacts/service.rb +6 -2
- data/lib/pact_broker/ui/view_models/index_item.rb +1 -1
- data/lib/pact_broker/verifications/placeholder_verification.rb +23 -0
- data/lib/pact_broker/verifications/repository.rb +9 -0
- data/lib/pact_broker/verifications/service.rb +5 -1
- data/lib/pact_broker/version.rb +1 -1
- data/lib/pact_broker/webhooks/repository.rb +54 -4
- data/lib/pact_broker/webhooks/service.rb +37 -2
- data/lib/pact_broker/webhooks/webhook.rb +4 -3
- data/lib/pact_broker/webhooks/webhook_event.rb +8 -0
- data/lib/pact_broker/webhooks/webhook_request_template.rb +72 -0
- data/pact_broker.gemspec +1 -1
- data/script/seed.rb +32 -51
- data/spec/features/create_webhook_spec.rb +85 -36
- data/spec/features/execute_webhook_spec.rb +9 -18
- data/spec/features/get_triggered_webhooks_for_pact_spec.rb +20 -0
- data/spec/features/get_triggered_webhooks_for_verification_spec.rb +21 -0
- data/spec/fixtures/webhook_valid_with_pacticipants.json +23 -0
- data/spec/integration/webhooks/certificate_spec.rb +2 -2
- data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +98 -2
- data/spec/lib/pact_broker/api/decorators/triggered_webhook_decorator_spec.rb +64 -0
- data/spec/lib/pact_broker/api/decorators/triggered_webhooks_decorator_spec.rb +28 -0
- data/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb +8 -0
- data/spec/lib/pact_broker/api/decorators/webhook_decorator_spec.rb +37 -1
- data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +34 -1
- data/spec/lib/pact_broker/api/decorators/{webhook_request_decorator_spec.rb → webhook_request_template_decorator_spec.rb} +7 -9
- data/spec/lib/pact_broker/api/pact_broker_urls_spec.rb +22 -0
- data/spec/lib/pact_broker/api/resources/{pact_webhooks_spec.rb → all_webhooks_spec.rb} +46 -80
- data/spec/lib/pact_broker/api/resources/error_handler_spec.rb +34 -0
- data/spec/lib/pact_broker/api/resources/pact_triggered_webhooks_spec.rb +54 -0
- data/spec/lib/pact_broker/api/resources/pacticipant_spec.rb +1 -6
- data/spec/lib/pact_broker/api/resources/tag_spec.rb +1 -6
- data/spec/lib/pact_broker/api/resources/verification_triggered_webhooks_spec.rb +68 -0
- data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +2 -8
- data/spec/lib/pact_broker/api/resources/webhooks_spec.rb +216 -21
- data/spec/lib/pact_broker/configuration_spec.rb +30 -0
- data/spec/lib/pact_broker/domain/webhook_request_spec.rb +20 -64
- data/spec/lib/pact_broker/domain/webhook_spec.rb +40 -11
- data/spec/lib/pact_broker/matrix/repository_spec.rb +33 -0
- data/spec/lib/pact_broker/pacts/pact_version_spec.rb +1 -0
- data/spec/lib/pact_broker/pacts/repository_spec.rb +39 -1
- data/spec/lib/pact_broker/ui/view_models/index_item_spec.rb +1 -1
- data/spec/lib/pact_broker/verifications/repository_spec.rb +37 -0
- data/spec/lib/pact_broker/verifications/service_spec.rb +2 -2
- data/spec/lib/pact_broker/webhooks/render_spec.rb +15 -0
- data/spec/lib/pact_broker/webhooks/repository_spec.rb +149 -30
- data/spec/lib/pact_broker/webhooks/service_spec.rb +84 -7
- data/spec/lib/pact_broker/webhooks/webhook_request_template_spec.rb +81 -0
- data/spec/service_consumers/pact_helper.rb +2 -0
- data/spec/service_consumers/provider_states_for_pact_broker_client.rb +8 -0
- data/spec/service_consumers/provider_states_for_pact_ruby.rb +132 -0
- data/spec/support/test_data_builder.rb +30 -7
- metadata +37 -9
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'pact_broker/services'
|
2
|
+
require 'pact_broker/api/resources/base_resource'
|
3
|
+
require 'pact_broker/api/decorators/webhooks_decorator'
|
4
|
+
require 'pact_broker/api/decorators/webhook_decorator'
|
5
|
+
require 'pact_broker/api/contracts/webhook_contract'
|
6
|
+
|
7
|
+
module PactBroker
|
8
|
+
module Api
|
9
|
+
module Resources
|
10
|
+
class AllWebhooks < BaseResource
|
11
|
+
|
12
|
+
def content_types_provided
|
13
|
+
[["application/hal+json", :to_json]]
|
14
|
+
end
|
15
|
+
|
16
|
+
def content_types_accepted
|
17
|
+
[["application/json", :from_json]]
|
18
|
+
end
|
19
|
+
|
20
|
+
def allowed_methods
|
21
|
+
["GET", "POST"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_path
|
25
|
+
webhook_url next_uuid, base_url
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_is_create?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def malformed_request?
|
33
|
+
if request.post?
|
34
|
+
return invalid_json? || validation_errors?(webhook)
|
35
|
+
end
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json
|
40
|
+
Decorators::WebhooksDecorator.new(webhooks).to_json(user_options: decorator_context(resource_title: "Webhooks"))
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_json
|
44
|
+
saved_webhook = webhook_service.create next_uuid, webhook, consumer, provider
|
45
|
+
response.body = Decorators::WebhookDecorator.new(saved_webhook).to_json(user_options: { base_url: base_url })
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def validation_errors? webhook
|
51
|
+
errors = webhook_service.errors(webhook)
|
52
|
+
|
53
|
+
unless errors.empty?
|
54
|
+
set_json_validation_error_messages(errors.messages)
|
55
|
+
end
|
56
|
+
|
57
|
+
!errors.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
def consumer
|
61
|
+
webhook.consumer ? pacticipant_service.find_pacticipant_by_name(webhook.consumer.name) : nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def provider
|
65
|
+
webhook.provider ? pacticipant_service.find_pacticipant_by_name(webhook.provider.name) : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def webhooks
|
69
|
+
webhook_service.find_all
|
70
|
+
end
|
71
|
+
|
72
|
+
def webhook
|
73
|
+
@webhook ||= Decorators::WebhookDecorator.new(PactBroker::Domain::Webhook.new).from_json(request_body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def next_uuid
|
77
|
+
@next_uuid ||= webhook_service.next_uuid
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -152,6 +152,24 @@ module PactBroker
|
|
152
152
|
def with_matrix_refresh &block
|
153
153
|
matrix_service.refresh(identifier_from_path, &block)
|
154
154
|
end
|
155
|
+
|
156
|
+
def find_pacticipant name, role
|
157
|
+
pacticipant_service.find_pacticipant_by_name(name).tap do | pacticipant |
|
158
|
+
set_json_error_message("No #{role} with name '#{name}' found") if pacticipant.nil?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def consumer
|
163
|
+
@consumer ||= identifier_from_path[:consumer_name] && find_pacticipant(identifier_from_path[:consumer_name], "consumer")
|
164
|
+
end
|
165
|
+
|
166
|
+
def provider
|
167
|
+
@provider ||= identifier_from_path[:provider_name] && find_pacticipant(identifier_from_path[:provider_name], "provider")
|
168
|
+
end
|
169
|
+
|
170
|
+
def pact
|
171
|
+
@pact ||= pact_service.find_pact(pact_params)
|
172
|
+
end
|
155
173
|
end
|
156
174
|
end
|
157
175
|
end
|
@@ -11,7 +11,11 @@ module PactBroker
|
|
11
11
|
def self.call e, request, response
|
12
12
|
logger.error e
|
13
13
|
logger.error e.backtrace
|
14
|
-
|
14
|
+
response_body = { error: { :message => e.message } }
|
15
|
+
if PactBroker.configuration.show_backtrace_in_error_response?
|
16
|
+
response_body[:error][:backtrace] = e.backtrace
|
17
|
+
end
|
18
|
+
response.body = response_body.to_json
|
15
19
|
report(e, request) if reportable?(e)
|
16
20
|
end
|
17
21
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pact_broker/api/decorators/triggered_webhooks_decorator'
|
2
|
+
|
3
|
+
module PactBroker
|
4
|
+
module Api
|
5
|
+
module Resources
|
6
|
+
class PactTriggeredWebhooks < BaseResource
|
7
|
+
def allowed_methods
|
8
|
+
["GET"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def content_types_provided
|
12
|
+
[["application/hal+json", :to_json]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def resource_exists?
|
16
|
+
!!pact
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_json
|
20
|
+
Decorators::TriggeredWebhooksDecorator.new(triggered_webhooks).to_json(decorator_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def triggered_webhooks
|
26
|
+
webhook_service.find_triggered_webhooks_for_pact(pact)
|
27
|
+
end
|
28
|
+
|
29
|
+
def resource_title
|
30
|
+
"Webhooks triggered by the publication of the #{pact.name[0].downcase}#{pact.name[1..-1]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def decorator_options
|
34
|
+
{
|
35
|
+
user_options: decorator_context.merge(resource_title: resource_title)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
require 'pact_broker/api/resources/base_resource'
|
3
2
|
require 'pact_broker/api/decorators/webhook_decorator'
|
4
3
|
require 'pact_broker/api/decorators/webhooks_decorator'
|
@@ -24,7 +23,8 @@ module PactBroker
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def resource_exists?
|
27
|
-
consumer &&
|
26
|
+
(identifier_from_path[:consumer_name].nil? || consumer) &&
|
27
|
+
(identifier_from_path[:provider_name].nil? || provider)
|
28
28
|
end
|
29
29
|
|
30
30
|
def malformed_request?
|
@@ -76,19 +76,6 @@ module PactBroker
|
|
76
76
|
@next_uuid ||= webhook_service.next_uuid
|
77
77
|
end
|
78
78
|
|
79
|
-
def consumer
|
80
|
-
@consumer ||= find_pacticipant(identifier_from_path[:consumer_name], "consumer")
|
81
|
-
end
|
82
|
-
|
83
|
-
def provider
|
84
|
-
@provider ||= find_pacticipant(identifier_from_path[:provider_name], "provider")
|
85
|
-
end
|
86
|
-
|
87
|
-
def find_pacticipant name, role
|
88
|
-
pacticipant_service.find_pacticipant_by_name(name).tap do | pacticipant |
|
89
|
-
set_json_error_message("No #{role} with name '#{name}' found") if pacticipant.nil?
|
90
|
-
end
|
91
|
-
end
|
92
79
|
|
93
80
|
end
|
94
81
|
end
|
@@ -27,7 +27,7 @@ module PactBroker
|
|
27
27
|
private
|
28
28
|
|
29
29
|
def latest_triggered_webhooks
|
30
|
-
@latest_triggered_webhooks ||= webhook_service.
|
30
|
+
@latest_triggered_webhooks ||= webhook_service.find_latest_triggered_webhooks_for_pact(pact)
|
31
31
|
end
|
32
32
|
|
33
33
|
def pact
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'pact_broker/api/decorators/triggered_webhooks_decorator'
|
2
|
+
|
3
|
+
module PactBroker
|
4
|
+
module Api
|
5
|
+
module Resources
|
6
|
+
class VerificationTriggeredWebhooks < BaseResource
|
7
|
+
def allowed_methods
|
8
|
+
["GET"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def content_types_provided
|
12
|
+
[["application/hal+json", :to_json]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def resource_exists?
|
16
|
+
!!verification
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_json
|
20
|
+
Decorators::TriggeredWebhooksDecorator.new(triggered_webhooks).to_json(decorator_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def triggered_webhooks
|
26
|
+
webhook_service.find_triggered_webhooks_for_verification(verification)
|
27
|
+
end
|
28
|
+
|
29
|
+
def resource_title
|
30
|
+
"Webhooks triggered by the publication of verification result #{verification.number}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def decorator_options
|
34
|
+
{
|
35
|
+
user_options: decorator_context.merge(resource_title: resource_title)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def verification
|
40
|
+
@verification ||= verification_service.find(identifier_from_path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -14,7 +14,7 @@ module PactBroker
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def process_post
|
17
|
-
webhook_execution_result = webhook_service.
|
17
|
+
webhook_execution_result = webhook_service.test_execution(webhook)
|
18
18
|
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
|
19
19
|
response.body = post_response_body webhook_execution_result
|
20
20
|
if webhook_execution_result.success?
|
@@ -39,10 +39,6 @@ module PactBroker
|
|
39
39
|
@webhook ||= webhook_service.find_by_uuid uuid
|
40
40
|
end
|
41
41
|
|
42
|
-
def pact
|
43
|
-
@pact ||= pact_service.find_latest_pact consumer_name: webhook.consumer_name, provider_name: webhook.provider_name
|
44
|
-
end
|
45
|
-
|
46
42
|
def uuid
|
47
43
|
identifier_from_path[:uuid]
|
48
44
|
end
|
@@ -1,29 +1,92 @@
|
|
1
|
-
require 'pact_broker/services'
|
2
1
|
require 'pact_broker/api/resources/base_resource'
|
2
|
+
require 'pact_broker/api/decorators/webhook_decorator'
|
3
3
|
require 'pact_broker/api/decorators/webhooks_decorator'
|
4
|
+
require 'pact_broker/api/contracts/webhook_contract'
|
4
5
|
|
5
6
|
module PactBroker
|
6
7
|
module Api
|
7
8
|
module Resources
|
8
|
-
|
9
9
|
class Webhooks < BaseResource
|
10
10
|
|
11
|
+
def allowed_methods
|
12
|
+
["POST", "GET"]
|
13
|
+
end
|
14
|
+
|
11
15
|
def content_types_provided
|
12
16
|
[["application/hal+json", :to_json]]
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
["
|
19
|
+
def content_types_accepted
|
20
|
+
[["application/json", :from_json]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_exists?
|
24
|
+
(identifier_from_path[:consumer_name].nil? || consumer) &&
|
25
|
+
(identifier_from_path[:provider_name].nil? || provider)
|
26
|
+
end
|
27
|
+
|
28
|
+
def malformed_request?
|
29
|
+
if request.post?
|
30
|
+
return invalid_json? || validation_errors?(webhook)
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def validation_errors? webhook
|
36
|
+
errors = webhook_service.errors(webhook)
|
37
|
+
|
38
|
+
unless errors.empty?
|
39
|
+
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
|
40
|
+
response.body = { errors: errors.messages }.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
!errors.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_path
|
47
|
+
webhook_url next_uuid, base_url
|
48
|
+
end
|
49
|
+
|
50
|
+
def post_is_create?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def from_json
|
55
|
+
saved_webhook = webhook_service.create next_uuid, webhook, consumer, provider
|
56
|
+
response.body = Decorators::WebhookDecorator.new(saved_webhook).to_json(user_options: { base_url: base_url })
|
17
57
|
end
|
18
58
|
|
19
59
|
def to_json
|
20
|
-
Decorators::WebhooksDecorator.new(webhooks).to_json(user_options: decorator_context(resource_title:
|
60
|
+
Decorators::WebhooksDecorator.new(webhooks).to_json(user_options: decorator_context(resource_title: 'Pact webhooks'))
|
21
61
|
end
|
22
62
|
|
63
|
+
private
|
64
|
+
|
23
65
|
def webhooks
|
24
|
-
webhook_service.
|
66
|
+
webhook_service.find_by_consumer_and_provider consumer, provider
|
67
|
+
end
|
68
|
+
|
69
|
+
def webhook
|
70
|
+
@webhook ||= Decorators::WebhookDecorator.new(PactBroker::Domain::Webhook.new).from_json(request_body)
|
25
71
|
end
|
26
72
|
|
73
|
+
def next_uuid
|
74
|
+
@next_uuid ||= webhook_service.next_uuid
|
75
|
+
end
|
76
|
+
|
77
|
+
def consumer
|
78
|
+
@consumer ||= identifier_from_path[:consumer_name] && find_pacticipant(identifier_from_path[:consumer_name], "consumer")
|
79
|
+
end
|
80
|
+
|
81
|
+
def provider
|
82
|
+
@provider ||= identifier_from_path[:provider_name] && find_pacticipant(identifier_from_path[:provider_name], "provider")
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_pacticipant name, role
|
86
|
+
pacticipant_service.find_pacticipant_by_name(name).tap do | pacticipant |
|
87
|
+
set_json_error_message("No #{role} with name '#{name}' found") if pacticipant.nil?
|
88
|
+
end
|
89
|
+
end
|
27
90
|
end
|
28
91
|
end
|
29
92
|
end
|
@@ -73,8 +73,7 @@ module PactBroker
|
|
73
73
|
config.version_parser = PactBroker::Versions::ParseSemanticVersion
|
74
74
|
config.sha_generator = PactBroker::Pacts::GenerateSha
|
75
75
|
config.base_equality_only_on_content_that_affects_verification_results = false
|
76
|
-
|
77
|
-
config.order_versions_by_date = false
|
76
|
+
config.order_versions_by_date = true
|
78
77
|
config.semver_formats = ["%M.%m.%p%s%d", "%M.%m", "%M"]
|
79
78
|
config.webhook_retry_schedule = [10, 60, 120, 300, 600, 1200] #10 sec, 1 min, 2 min, 5 min, 10 min, 20 min => 38 minutes
|
80
79
|
config.check_for_potential_duplicate_pacticipant_names = true
|
@@ -92,6 +91,10 @@ module PactBroker
|
|
92
91
|
}
|
93
92
|
end
|
94
93
|
|
94
|
+
def show_backtrace_in_error_response?
|
95
|
+
!!(ENV['RACK_ENV'] && ENV['RACK_ENV'].downcase != 'production')
|
96
|
+
end
|
97
|
+
|
95
98
|
def authentication_configured?
|
96
99
|
!!authenticate || !!authenticate_with_basic_auth
|
97
100
|
end
|
@@ -2,27 +2,28 @@
|
|
2
2
|
|
3
3
|
*Collection resource*
|
4
4
|
|
5
|
-
Path: `/webhooks
|
5
|
+
Path: `/webhooks`
|
6
6
|
|
7
7
|
Allowed methods: `GET`, `POST`
|
8
8
|
|
9
9
|
*Individual resource*
|
10
10
|
|
11
|
-
Path: `/webhook
|
12
|
-
s/UUID`
|
11
|
+
Path: `/webhook/UUID`
|
13
12
|
|
14
13
|
Allowed methods: `GET`, `PUT`, `DELETE`
|
15
14
|
|
15
|
+
Webhooks are HTTP requests that are executed asynchronously after certain events occur in the Pact Broker, that can be used to create a workflow or notify people of changes to the data contained in the Pact Broker. The most common use for webhooks is to trigger builds when a pact has changed or a verification result has been published, but they can also be used for conveying information like posting notifications to Slack, or commit statuses to Github.
|
16
|
+
|
16
17
|
### Creating
|
17
18
|
|
18
|
-
|
19
|
-
(Click "Go to Entry Point", then select "latest-pacts", then select the pact you want to create the webhook for.)
|
20
|
-
2. Click the "NON-GET" button for the "pact-webhooks" relation.
|
21
|
-
3. Paste in the webhook JSON (example shown below) in the body section and click "Make Request".
|
19
|
+
To create a webhook, send a `POST` request to `/webhooks` with the body described below. You can do this through the API Browser by clicking on the `NON-GET` button for the `pb:webhooks` relation on the index, pasting in the JSON body, and clicking "Make Request".
|
22
20
|
|
23
|
-
|
21
|
+
Below is an example webhook to trigger a Bamboo job when any contract for the provider "Foo" has changed. Both provider and consumer are optional - omitting either indicates that any pacticipant in that role will be matched. Webhooks with neither provider nor consumer specified are "global" webhooks that will trigger for any consumer/provider pair.
|
24
22
|
|
25
23
|
{
|
24
|
+
"provider": {
|
25
|
+
"name": "Bar"
|
26
|
+
},
|
26
27
|
"events": [{
|
27
28
|
"name": "contract_content_changed"
|
28
29
|
}],
|
@@ -46,45 +47,42 @@ A request body can be specified as well.
|
|
46
47
|
"request": {
|
47
48
|
"method": "POST",
|
48
49
|
"url": "http://example.org/something",
|
50
|
+
"headers": {
|
51
|
+
"Content-Type": "application/json"
|
52
|
+
},
|
49
53
|
"body": {
|
50
54
|
"some" : "json"
|
51
55
|
}
|
52
56
|
}
|
53
57
|
}
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
The recommended set of values to start with are:
|
72
|
-
|
73
|
-
* your CI server's hostname (for triggering builds)
|
74
|
-
* your company chat (eg. Slack, for publishing notifications)
|
75
|
-
* your code repository (eg. Github, for sending commit statuses)
|
59
|
+
To specify an XML body, you will need to use a correctly escaped string (or use single quotes if allowed).
|
60
|
+
|
61
|
+
{
|
62
|
+
"events": [{
|
63
|
+
"name": "contract_content_changed"
|
64
|
+
}],
|
65
|
+
"request": {
|
66
|
+
"method": "POST",
|
67
|
+
"url": "http://example.org/something",
|
68
|
+
"headers": {
|
69
|
+
"Content-Type": "application/xml"
|
70
|
+
},
|
71
|
+
"body": "<xml \"foo\"=\"bar\"></xml>"
|
72
|
+
}
|
73
|
+
}
|
76
74
|
|
77
|
-
|
75
|
+
**BEWARE** While the basic auth password, and any header containing the word `authorization` or `token` will be redacted from the UI and the logs, the password could be reverse engineered from the database, so make a separate account for the Pact Broker to use in your webhooks. Don't use your personal account!
|
78
76
|
|
79
77
|
#### Event types
|
80
78
|
|
81
|
-
`contract_content_changed:` triggered when the content of the contract has changed since the previous publication.
|
79
|
+
`contract_content_changed:` triggered when the content of the contract has changed since the previous publication. If `base_equality_only_on_content_that_affects_verification_results` is set to `true` in the configuration (the default), any changes to whitespace, ordering of keys, or the ordering of the `interactions` or `messages` will be ignored, and will not trigger this event.
|
82
80
|
|
83
81
|
`provider_verification_published:` triggered whenever a provider publishes a verification.
|
84
82
|
|
85
83
|
### Dynamic variable substitution
|
86
84
|
|
87
|
-
The following variables may be used in the request parameters or body, and will be replaced with their appropriate values at runtime.
|
85
|
+
The following variables may be used in the request path, parameters or body, and will be replaced with their appropriate values at runtime.
|
88
86
|
|
89
87
|
`${pactbroker.consumerName}`: the consumer name
|
90
88
|
`${pactbroker.providerName}`: the provider name
|
@@ -111,9 +109,31 @@ Example usage:
|
|
111
109
|
}
|
112
110
|
}
|
113
111
|
|
112
|
+
<a name="whitelist"></a>
|
113
|
+
### Webhook Whitelist
|
114
|
+
|
115
|
+
To ensure that webhooks cannot be used maliciously to expose either data about your contracts or your internal network, the following validation rules are applied to webhooks via the Pact Broker configuration settings.
|
116
|
+
|
117
|
+
* **Scheme**: Must be included in the `webhook_scheme_whitelist`, which by default only includes `https`. You can change this to include `http` if absolutely necessary, however, keep in mind that the body of any http traffic is visible to the network. You can load a self signed certificate into the Pact Broker to be used for https connections using [script/insert-self-signed-certificate-from-url.rb](https://github.com/pact-foundation/pact_broker/blob/master/script/insert-self-signed-certificate-from-url.rb) in the
|
118
|
+
Pact Broker Github repository.
|
119
|
+
|
120
|
+
* **HTTP method**: Must be included in the `webhook_http_method_whitelist`, which by default only includes `POST`. It is highly recommended that only `POST` requests are allowed to ensure that webhooks cannot be used to retrieve sensitive information from hosts within the same network.
|
121
|
+
|
122
|
+
* **Host**: If the `webhook_host_whitelist` contains any entries, the host must match one or more of the entries. By default, it is empty. For security purposes, if the host whitelist is empty, the response details will not be logged to the UI (though they can be seen in the application logs at debug level).
|
123
|
+
|
124
|
+
The host whitelist may contain hostnames (eg `"github.com"`), IPs (eg `"192.0.345.4"`), network ranges (eg `"10.0.0.0/8"`) or regular expressions (eg `/.*\.foo\.com$/`). Note that IPs are not resolved, so if you specify an IP range, you need to use the IP in the webhook URL. If you wish to allow webhooks to any host (not recommended!), you can set `webhook_host_whitelist` to `[/.*/]`. Beware of any sensitive endpoints that may be exposed within the same network.
|
125
|
+
|
126
|
+
The recommended set of values to start with are:
|
127
|
+
|
128
|
+
* your CI server's hostname (for triggering builds)
|
129
|
+
* your company chat (eg. Slack, for publishing notifications)
|
130
|
+
* your code repository (eg. Github, for sending commit statuses)
|
131
|
+
|
132
|
+
Alternatively, you could use a regular expression to limit requests to your company's domain. eg `/.*\.foo\.com$/` (don't forget the end of string anchor). You can test Ruby regular expressions at [rubular.com](http://rubular.com).
|
133
|
+
|
114
134
|
### Testing
|
115
135
|
|
116
|
-
To test a webhook, navigate to the webhook in the HAL browser, then make a POST request to the "execute" relation. The response or error will be shown in the window.
|
136
|
+
To test a webhook, navigate to the webhook in the HAL browser, then make a POST request to the "pb:execute" relation. The latest matching pact/verification will be used in the template, or a placeholder if none exists. The response or error will be shown in the window.
|
117
137
|
|
118
138
|
### Deleting
|
119
139
|
|