pact_broker 2.88.0 → 2.89.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release_gem.yml +1 -1
  3. data/.github/workflows/trigger_pact_docs_update.yml +1 -0
  4. data/CHANGELOG.md +14 -0
  5. data/docs/CONFIGURATION.md +1 -1
  6. data/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb +14 -4
  7. data/lib/pact_broker/api/decorators/deployed_version_decorator.rb +2 -1
  8. data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +18 -0
  9. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +3 -1
  10. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +4 -3
  11. data/lib/pact_broker/api/resources/pact.rb +1 -1
  12. data/lib/pact_broker/api/resources/publish_contracts.rb +27 -4
  13. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  14. data/lib/pact_broker/contracts/contracts_publication_results.rb +2 -7
  15. data/lib/pact_broker/contracts/notice.rb +8 -0
  16. data/lib/pact_broker/contracts/service.rb +25 -0
  17. data/lib/pact_broker/doc/views/index/publish-contracts.markdown +32 -2
  18. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +2 -0
  19. data/lib/pact_broker/locale/en.yml +1 -1
  20. data/lib/pact_broker/pacts/content.rb +18 -0
  21. data/lib/pact_broker/pacts/create_formatted_diff.rb +9 -2
  22. data/lib/pact_broker/pacts/selector.rb +35 -21
  23. data/lib/pact_broker/pacts/sort_content.rb +1 -0
  24. data/lib/pact_broker/verifications/required_verification.rb +5 -2
  25. data/lib/pact_broker/verifications/service.rb +14 -7
  26. data/lib/pact_broker/version.rb +1 -1
  27. data/lib/pact_broker/versions/selector.rb +32 -0
  28. data/lib/pact_broker/versions/selectors.rb +20 -0
  29. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +2 -2
  30. data/lib/pact_broker/webhooks/trigger_service.rb +9 -1
  31. data/script/docs/generate-configuration-docs.rb +9 -9
  32. data/script/github-issues/add-branch-support/issue-text.txt +7 -0
  33. data/script/github-issues/add-branch-support/issues.txt +0 -0
  34. data/script/github-issues/add-branch-support/raise-issue-in-client-repos.sh +10 -0
  35. data/script/github-issues/add-branch-support-for-provider-versions/issue-text.txt +9 -0
  36. data/script/github-issues/add-branch-support-for-provider-versions/issues.txt +6 -0
  37. data/script/github-issues/add-branch-support-for-provider-versions/raise-issue-in-client-repos.sh +10 -0
  38. data/script/github-issues/branch-consumer-version-selector/issue-text.txt +52 -0
  39. data/script/github-issues/branch-consumer-version-selector/issues.txt +9 -0
  40. data/script/github-issues/branch-consumer-version-selector/raise-issue-in-client-repos.sh +10 -0
  41. data/script/{issues → github-issues}/consumer-version-selectors-docs/issue-text.txt +0 -0
  42. data/script/{issues → github-issues}/consumer-version-selectors-docs/issues.txt +0 -0
  43. data/script/{issues → github-issues}/consumer-version-selectors-docs/raise-issue-in-client-repos.sh +0 -0
  44. data/script/github-issues/deployed-and-released-selectors-docs/issue-text.txt +26 -0
  45. data/script/github-issues/deployed-and-released-selectors-docs/issues.txt +9 -0
  46. data/script/github-issues/deployed-and-released-selectors-docs/raise-issue-in-client-repos.sh +10 -0
  47. data/script/github-issues/include-pending-by-default/issue-text.txt +5 -0
  48. data/script/github-issues/include-pending-by-default/issues.txt +10 -0
  49. data/script/github-issues/include-pending-by-default/raise-issue-in-client-repos.sh +10 -0
  50. data/spec/features/get_currently_deployed_versions_for_environment_spec.rb +23 -6
  51. data/spec/features/publish_pact_all_in_one_spec.rb +19 -0
  52. data/spec/features/record_deployment_spec.rb +17 -4
  53. data/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_combinations_spec.rb +5 -3
  54. data/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_spec.rb +10 -0
  55. data/spec/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator_spec.rb +10 -0
  56. data/spec/lib/pact_broker/contracts/service_spec.rb +64 -0
  57. data/spec/lib/pact_broker/verifications/service_spec.rb +18 -12
  58. data/spec/lib/pact_broker/webhooks/trigger_service_spec.rb +67 -3
  59. metadata +22 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b886a748e8133256c6b1a48f018263b18abe3e907878bd659941529791052b72
4
- data.tar.gz: a236b15253a54764939b260d80aa77e1c3635448ce486606f6013df9f864029d
3
+ metadata.gz: fb2a319d08e394deac2b0ddab3c97cb09e3ef17025daebe82e54a30577a049b6
4
+ data.tar.gz: b3767941da7f343cff6b14ddeca1398bb86e492b84d2447d03e59658c7d1ae31
5
5
  SHA512:
6
- metadata.gz: cededddf5b3eacacd0bc3347bb3350156d2acb5bd29bfd0f43b2a234470ec7a592f4a0b41c167d47f9ec25815092d5bdde16d688b4ded08cce6c1c57008683b3
7
- data.tar.gz: 683226ee8e28b2055a0e99623c7b2fbbb0bc959308eee60d00f5f255a23411331818c236082f23f56ac4752bd32b63fbe37a702747ce99300e3980dbaa5d4262
6
+ metadata.gz: 4373701434b40c1529c20469d860e352ffb32a100b22a2ea6774c7579ddc4a30956fc7879b1fb3f58d0e6676116cbb7ac504648178a28513b684056918e3818a
7
+ data.tar.gz: a2f2b2d4b5ffb90e4ed31394d47a8a05aa75e90d2329f22cad4092930097f5958f6ccbd53ebb499c05d375bc980de62ddaf31252bd58d22ed9ff09911da106c7
@@ -35,7 +35,7 @@ jobs:
35
35
  needs: release
36
36
  strategy:
37
37
  matrix:
38
- repository: [pact-foundation/pact-broker-docker, DiUS/pact_broker-docker]
38
+ repository: [pact-foundation/pact-broker-docker, DiUS/pact_broker-docker, pact-foundation/pact_broker]
39
39
  runs-on: ubuntu-latest
40
40
  steps:
41
41
  - name: Notify ${{ matrix.repository }} of gem release
@@ -9,6 +9,7 @@ on:
9
9
  repository_dispatch:
10
10
  types:
11
11
  - gem-released
12
+ workflow_dispatch:
12
13
 
13
14
  jobs:
14
15
  build:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ <a name="v2.89.0"></a>
2
+ ### v2.89.0 (2021-10-15)
3
+
4
+ #### Features
5
+
6
+ * add applicationInstance to deployed version resource as a replacement for target ([9bfafc8a](/../../commit/9bfafc8a))
7
+ * apply allow_dangerous_contract_modification setting when publishing using the /contracts/publish endpoint ([956227fe](/../../commit/956227fe))
8
+ * add support for matchingBranch property in consumer version selectors ([48068d29](/../../commit/48068d29))
9
+
10
+ #### Bug Fixes
11
+
12
+ * set missing provider branch name parameter for contract_requiring_verification_published webhooks ([777ccdd2](/../../commit/777ccdd2))
13
+ * correct misnamed database port configuration property causing the PACT_BROKER_DATABASE_PORT not to be respected ([3d14013a](/../../commit/3d14013a))
14
+
1
15
  <a name="v2.88.0"></a>
2
16
  ### v2.88.0 (2021-10-11)
3
17
 
@@ -1,7 +1,7 @@
1
1
  # Pact Broker Configuration
2
2
 
3
3
 
4
- <!-- This is a generated file. Please do not edit it directly. -->
4
+ <!-- This is a generated file. Please do not edit it directly. The source is https://github.com/pact-foundation/pact_broker/blob/master/docs/configuration.yml -->
5
5
 
6
6
  The Pact Broker supports configuration via environment variables or a YAML file from version 2.87.0.1 of the Docker images.
7
7
 
@@ -15,7 +15,7 @@ module PactBroker
15
15
  using PactBroker::HashRefinements
16
16
  using PactBroker::StringRefinements
17
17
 
18
- BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch]
18
+ BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :matchingBranch]
19
19
  ENVIRONMENT_KEYS = [:environment, :deployed, :released, :deployedOrReleased]
20
20
  ALL_KEYS = BRANCH_KEYS + ENVIRONMENT_KEYS
21
21
 
@@ -36,6 +36,7 @@ module PactBroker
36
36
  optional(:mainBranch).filled(included_in?: [true])
37
37
  optional(:tag).filled(:str?)
38
38
  optional(:branch).filled(:str?)
39
+ optional(:matchingBranch).filled(included_in?: [true])
39
40
  optional(:latest).filled(included_in?: [true, false])
40
41
  optional(:fallbackTag).filled(:str?)
41
42
  optional(:fallbackBranch).filled(:str?)
@@ -65,7 +66,7 @@ module PactBroker
65
66
  # This is a ducking joke. Need to get rid of dry-validation
66
67
  if params[:consumerVersionSelectors].is_a?(Array)
67
68
  errors = params[:consumerVersionSelectors].each_with_index.flat_map do | selector, index |
68
- validate_consumer_version_selector(selector, index)
69
+ validate_consumer_version_selector(selector, index, params)
69
70
  end
70
71
  if errors.any?
71
72
  results[:consumerVersionSelectors] ||= []
@@ -82,7 +83,7 @@ module PactBroker
82
83
  # when setting the branch, it doesn't make sense to verify all pacts for a branch,
83
84
  # so latest is not required, but cannot be set to false
84
85
  # rubocop: disable Metrics/CyclomaticComplexity, Metrics/MethodLength
85
- def self.validate_consumer_version_selector(selector, index)
86
+ def self.validate_consumer_version_selector(selector, index, params)
86
87
  errors = []
87
88
 
88
89
  if selector[:fallbackTag] && !selector[:latest]
@@ -97,17 +98,22 @@ module PactBroker
97
98
  not_provided?(selector[:tag]) &&
98
99
  not_provided?(selector[:branch]) &&
99
100
  not_provided?(selector[:environment]) &&
101
+ selector[:matchingBranch] != true &&
100
102
  selector[:deployed] != true &&
101
103
  selector[:released] != true &&
102
104
  selector[:deployedOrReleased] != true &&
103
105
  selector[:latest] != true
104
- errors << "must specify a value for environment or tag or branch, or specify mainBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true (at index #{index})"
106
+ errors << "must specify a value for environment or tag or branch, or specify mainBranch=true, matchingBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true (at index #{index})"
105
107
  end
106
108
 
107
109
  if selector[:mainBranch] && selector.slice(*ALL_KEYS - [:consumer, :mainBranch]).any?
108
110
  errors << "cannot specify mainBranch=true with any other criteria apart from consumer (at index #{index})"
109
111
  end
110
112
 
113
+ if selector[:matchingBranch] && selector.slice(*ALL_KEYS - [:consumer, :matchingBranch]).any?
114
+ errors << "cannot specify matchingBranch=true with any other criteria apart from consumer (at index #{index})"
115
+ end
116
+
111
117
  if selector[:tag] && selector[:branch]
112
118
  errors << "cannot specify both a tag and a branch (at index #{index})"
113
119
  end
@@ -136,6 +142,10 @@ module PactBroker
136
142
  errors << "cannot specify both released=true and deployedOrReleased=true (at index #{index})"
137
143
  end
138
144
 
145
+ if selector[:matchingBranch] && not_provided?(params[:providerVersionBranch])
146
+ errors << "the providerVersionBranch must be specified when the selector matchingBranch=true is used (at index #{index})"
147
+ end
148
+
139
149
  non_environment_fields = selector.slice(*BRANCH_KEYS).keys.sort
140
150
  environment_related_fields = selector.slice(*ENVIRONMENT_KEYS).keys.sort
141
151
 
@@ -9,7 +9,8 @@ module PactBroker
9
9
  class DeployedVersionDecorator < BaseDecorator
10
10
  property :uuid
11
11
  property :currently_deployed, camelize: true
12
- property :target, camelize: true
12
+ property :target, camelize: true # deprecated
13
+ property :applicationInstance, getter: lambda { |_| target }
13
14
  include Timestamps
14
15
  property :undeployedAt, getter: lambda { |_| undeployed_at ? FormatDateTime.call(undeployed_at) : nil }, writeable: false
15
16
 
@@ -21,6 +21,10 @@ module PactBroker
21
21
  represented.branch = fragment
22
22
  represented.latest = true
23
23
  }
24
+ property :matching_branch, setter: -> (fragment:, represented:, **) {
25
+ represented.matching_branch = fragment
26
+ represented.latest = true
27
+ }
24
28
  property :latest,
25
29
  setter: ->(fragment:, represented:, **) {
26
30
  represented.latest = (fragment == "true" || fragment == true)
@@ -53,6 +57,9 @@ module PactBroker
53
57
  result = super(hash&.snakecase_keys)
54
58
 
55
59
  result.consumer_version_selectors = split_out_deployed_or_released_selectors(result.consumer_version_selectors)
60
+ if result.provider_version_branch
61
+ result.consumer_version_selectors = set_branch_for_matching_branch_selectors(result.consumer_version_selectors, result.provider_version_branch)
62
+ end
56
63
 
57
64
  if result.consumer_version_selectors && !result.consumer_version_selectors.is_a?(PactBroker::Pacts::Selectors)
58
65
  result.consumer_version_selectors = PactBroker::Pacts::Selectors.new(result.consumer_version_selectors)
@@ -62,6 +69,17 @@ module PactBroker
62
69
 
63
70
  private
64
71
 
72
+ def set_branch_for_matching_branch_selectors(consumer_version_selectors, provider_version_branch)
73
+ consumer_version_selectors.collect do | consumer_version_selector |
74
+ if consumer_version_selector[:matching_branch]
75
+ consumer_version_selector[:branch] = provider_version_branch
76
+ consumer_version_selector
77
+ else
78
+ consumer_version_selector
79
+ end
80
+ end
81
+ end
82
+
65
83
  def split_out_deployed_or_released_selectors(consumer_version_selectors)
66
84
  consumer_version_selectors.flat_map do | selector |
67
85
  if selector.currently_deployed && selector.currently_supported
@@ -60,7 +60,9 @@ module PactBroker
60
60
  query = Rack::Utils.parse_query(request.env["QUERY_STRING"])
61
61
  q = {}
62
62
  q[:pacticipant_name] = request.query["pacticipant"] if query["pacticipant"]
63
- if query["target"]
63
+ if query["applicationInstance"]
64
+ q[:target] = query["applicationInstance"].blank? ? nil : query["applicationInstance"]
65
+ elsif query["target"]
64
66
  q[:target] = query["target"].blank? ? nil : query["target"]
65
67
  end
66
68
  q
@@ -30,7 +30,7 @@ module PactBroker
30
30
  end
31
31
 
32
32
  def from_json
33
- @deployed_version = deployed_version_service.find_or_create(deployed_version_uuid, version, environment, target)
33
+ @deployed_version = deployed_version_service.find_or_create(deployed_version_uuid, version, environment, application_instance)
34
34
  response.headers["Location"] = deployed_version_url(deployed_version, base_url)
35
35
  response.body = decorator_class(:deployed_version_decorator).new(deployed_version).to_json(decorator_options)
36
36
  end
@@ -72,8 +72,9 @@ module PactBroker
72
72
  end
73
73
 
74
74
  # TODO disallow an empty string because that is used as a NULL indicator in the database
75
- def target
76
- params(default: {})[:target]&.to_s
75
+ def application_instance
76
+ parameters = params(default: {})
77
+ (parameters[:applicationInstance] || parameters[:target])&.to_s
77
78
  end
78
79
 
79
80
  def title
@@ -110,7 +110,7 @@ module PactBroker
110
110
 
111
111
  def disallowed_modification?
112
112
  if request.really_put? && pact_service.disallowed_modification?(pact, pact_params.json_content)
113
- message_params = { consumer_name: pact_params.consumer_name, consumer_version_number: pact_params.consumer_version_number }
113
+ message_params = { consumer_name: pact_params.consumer_name, consumer_version_number: pact_params.consumer_version_number, provider_name: pact_params.provider_name }
114
114
  set_json_error_message(message("errors.validation.pact_content_modification_not_allowed", message_params))
115
115
  true
116
116
  else
@@ -31,11 +31,13 @@ module PactBroker
31
31
  end
32
32
 
33
33
  def process_post
34
- handle_webhook_events(consumer_version_branch: parsed_contracts.branch, build_url: parsed_contracts.build_url) do
35
- results = contract_service.publish(parsed_contracts, base_url: base_url)
36
- response.body = decorator_class(:publish_contracts_results_decorator).new(results).to_json(decorator_options)
34
+ if conflict_notices.any?
35
+ set_conflict_response
36
+ 409
37
+ else
38
+ publish_contracts
39
+ true
37
40
  end
38
- true
39
41
  end
40
42
 
41
43
  def policy_name
@@ -75,6 +77,27 @@ module PactBroker
75
77
  contract["decodedParsedContent"] = PactBroker::Pacts::Parse.call(contract["decodedContent"]) rescue nil
76
78
  end
77
79
  end
80
+
81
+ def publish_contracts
82
+ handle_webhook_events(consumer_version_branch: parsed_contracts.branch, build_url: parsed_contracts.build_url) do
83
+ results = contract_service.publish(parsed_contracts, base_url: base_url)
84
+ response.body = decorator_class(:publish_contracts_results_decorator).new(results).to_json(decorator_options)
85
+ end
86
+ end
87
+
88
+ def set_conflict_response
89
+ response.body = {
90
+ notices: conflict_notices.collect(&:to_h),
91
+ errors: {
92
+ contracts: conflict_notices.select(&:error?).collect(&:text)
93
+ }
94
+ }.to_json
95
+ response.headers["Content-Type"] = "application/json;charset=utf-8"
96
+ end
97
+
98
+ def conflict_notices
99
+ @conflict_notices ||= contract_service.conflict_notices(parsed_contracts)
100
+ end
78
101
  end
79
102
  end
80
103
  end
@@ -93,7 +93,7 @@ module PactBroker
93
93
  password: database_password,
94
94
  host: database_host,
95
95
  database: database_name,
96
- database_port: database_port
96
+ port: database_port
97
97
  }.compact
98
98
  end
99
99
  private :database_credentials
@@ -1,13 +1,8 @@
1
1
  module PactBroker
2
2
  module Contracts
3
- ContractsPublicationResults = Struct.new(:pacticipant, :version, :tags, :contracts, :notices) do
3
+ ContractsPublicationResults = Struct.new(:pacticipant, :version, :tags, :contracts, :notices, keyword_init: true) do
4
4
  def self.from_hash(params)
5
- new(params[:pacticipant],
6
- params[:version],
7
- params[:tags],
8
- params[:contracts],
9
- params[:notices]
10
- )
5
+ new(params)
11
6
  end
12
7
  end
13
8
  end
@@ -20,6 +20,14 @@ module PactBroker
20
20
  def self.success(text)
21
21
  Notice.new("success", text)
22
22
  end
23
+
24
+ def self.error(text)
25
+ Notice.new("error", text)
26
+ end
27
+
28
+ def error?
29
+ type == "error"
30
+ end
23
31
  end
24
32
  end
25
33
  end
@@ -6,6 +6,7 @@ require "pact_broker/contracts/contracts_publication_results"
6
6
  require "pact_broker/contracts/notice"
7
7
  require "pact_broker/events/subscriber"
8
8
  require "pact_broker/api/pact_broker_urls"
9
+ require "pact_broker/pacts/create_formatted_diff"
9
10
 
10
11
  module PactBroker
11
12
  module Contracts
@@ -42,6 +43,30 @@ module PactBroker
42
43
  )
43
44
  end
44
45
 
46
+ def conflict_notices(parsed_contracts)
47
+ notices = []
48
+ parsed_contracts.contracts.collect do | contract_to_publish |
49
+ pact_params = create_pact_params(parsed_contracts, contract_to_publish)
50
+ existing_pact = pact_service.find_pact(pact_params)
51
+ if existing_pact && pact_service.disallowed_modification?(existing_pact, contract_to_publish.decoded_content)
52
+ add_conflict_notice(notices, parsed_contracts, contract_to_publish, existing_pact.json_content, contract_to_publish.decoded_content)
53
+ end
54
+ end
55
+ notices
56
+ end
57
+
58
+ def add_conflict_notice(notices, parsed_contracts, contract_to_publish, existing_json_content, new_json_content)
59
+ message_params = {
60
+ consumer_name: contract_to_publish.provider_name,
61
+ consumer_version_number: parsed_contracts.pacticipant_version_number,
62
+ provider_name: contract_to_publish.provider_name
63
+ }
64
+ notices << Notice.error(message("errors.validation.pact_content_modification_not_allowed", message_params))
65
+ notices << Notice.info(PactBroker::Pacts::CreateFormattedDiff.call(new_json_content, existing_json_content))
66
+ end
67
+
68
+ private :add_conflict_notice
69
+
45
70
  def create_version(parsed_contracts)
46
71
  version_params = {
47
72
  build_url: parsed_contracts.build_url,
@@ -12,6 +12,8 @@ This is the preferred endpoint with which to publish contracts (previously, cont
12
12
 
13
13
  The previous tag and pact endpoints are still supported, however, future features that build on this endpoint may not be able to be backported into those endpoints.
14
14
 
15
+ This endpoint is designed to be used by a command line tool, and hence, the response notices are designed for output to the user in a terminal.
16
+
15
17
  ## Parameters
16
18
 
17
19
  * `pacticipantName`: the name of the application. Required.
@@ -31,7 +33,7 @@ The previous tag and pact endpoints are still supported, however, future feature
31
33
  ### Success
32
34
 
33
35
  * `notices`
34
- * `level`: one of `debug`, `info`, `warning`,`prompt`,`success`
36
+ * `level`: one of `debug`, `info`, `warning`,`prompt`,`success`, `error`, `danger`
35
37
  * `text`: the text of the notice. This is designed to be displayed in the output of a CLI.
36
38
 
37
39
  The `_links` section will contain links to all the resources created by the publication. The relations are:
@@ -43,7 +45,9 @@ The `_links` section will contain links to all the resources created by the publ
43
45
 
44
46
  ### Errors
45
47
 
46
- Any validation errors will be returned in the standard Pact Broker format:
48
+ ### Schema validation errors
49
+
50
+ Any validation errors will be returned in the standard Pact Broker format with a 400 status:
47
51
 
48
52
  {
49
53
  "errors": {
@@ -51,6 +55,32 @@ Any validation errors will be returned in the standard Pact Broker format:
51
55
  }
52
56
  }
53
57
 
58
+ ### Contract conflict errors
59
+
60
+ If there is a conflict with an existing published pact and `allow_dangerous_contract_modification` is set to false, a 409 will be returned with an array of notices, which will contain a diff between the existing pact content and the content that was attempted to be published. For consistency with the existing error responses, the errors hash will also contain the error messages, but there will be no diff included. For CLI usage, when there are notices and errors, just the notices should be displayed to the user.
61
+
62
+ {
63
+ "notices":
64
+ [
65
+ {
66
+ "text": "Cannot change the content of the pact for Foo version 183a77b0 and provider Bar, as race conditions will cause unreliable results for can-i-deploy. Each pact must be published with a unique consumer version number. For more information see https://docs.pact.io/go/versioning",
67
+ "type": "error"
68
+ },
69
+ {
70
+ "text": "<the diff, will include new lines>",
71
+ "type": "info"
72
+ }
73
+ ],
74
+ "errors":
75
+ {
76
+ "content":
77
+ [
78
+ "Cannot change the content of the pact for Foo version 183a77b0 and provider Bar, as race conditions will cause unreliable results for can-i-deploy. Each pact must be published with a unique consumer version number. For more information see https://docs.pact.io/go/versioning"
79
+ ]
80
+ }
81
+ }
82
+
83
+
54
84
  ## Example
55
85
 
56
86
  POST http://broker/contracts/publish
@@ -30,6 +30,8 @@ Example: This data structure represents the way a user might specify "I want to
30
30
 
31
31
  `consumerVersionSelectors.branch`: the branch name of the consumer versions to get the pacts for. Use of this selector requires that the consumer has configured a branch name when publishing the pacts.
32
32
 
33
+ `consumerVersionSelectors.matchingBranch`: if the key is specified, can only be set to `true`. When true, returns the latest pact for any branch with the same name as the specified `providerVersionBranch`.
34
+
33
35
  `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property.
34
36
 
35
37
  `consumerVersionSelectors.deployed`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` CLI.
@@ -74,7 +74,7 @@ en:
74
74
  invalid_http_method: "Invalid HTTP method '%{method}'"
75
75
  invalid_url: "Invalid URL '%{url}'. Expected format: http://example.org"
76
76
  pact_missing_pacticipant_name: "was not found at expected path $.%{pacticipant}.name in the submitted pact file."
77
- pact_content_modification_not_allowed: "Cannot change the content of the pact for %{consumer_name} version %{consumer_version_number}, as race conditions will cause unreliable results for can-i-deploy. Each pact must be published with a unique consumer version number. For more information see https://docs.pact.io/go/versioning"
77
+ pact_content_modification_not_allowed: "Cannot change the content of the pact for %{consumer_name} version %{consumer_version_number} and provider %{provider_name}, as race conditions will cause unreliable results for can-i-deploy. Each pact must be published with a unique consumer version number. For more information see https://docs.pact.io/go/versioning"
78
78
  consumer_version_number_missing: "Please specify the consumer version number by setting the X-Pact-Consumer-Version header."
79
79
  consumer_version_number_header_invalid: "X-Pact-Consumer-Version '%{consumer_version_number}' cannot be parsed to a version number. The expected format (unless this configuration has been overridden) is a semantic version. eg. 1.3.0 or 2.0.4.rc1"
80
80
  consumer_version_number_invalid: "Consumer version number '%{consumer_version_number}' cannot be parsed to a version number. The expected format (unless this configuration has been overridden) is a semantic version. eg. 1.3.0 or 2.0.4.rc1"
@@ -1,11 +1,13 @@
1
1
  require "pact_broker/pacts/parse"
2
2
  require "pact_broker/pacts/sort_content"
3
3
  require "pact_broker/pacts/generate_interaction_sha"
4
+ require "pact_broker/hash_refinements"
4
5
 
5
6
  module PactBroker
6
7
  module Pacts
7
8
  class Content
8
9
  include GenerateInteractionSha
10
+ using PactBroker::HashRefinements
9
11
 
10
12
  def initialize pact_hash
11
13
  @pact_hash = pact_hash
@@ -75,6 +77,18 @@ module PactBroker
75
77
  Content.from_hash(new_pact_hash)
76
78
  end
77
79
 
80
+ def without_ids
81
+ new_pact_hash = pact_hash.dup
82
+ if interactions && interactions.is_a?(Array)
83
+ new_pact_hash["interactions"] = remove_ids(interactions)
84
+ end
85
+
86
+ if messages && messages.is_a?(Array)
87
+ new_pact_hash["messages"] = remove_ids(messages)
88
+ end
89
+ Content.from_hash(new_pact_hash)
90
+ end
91
+
78
92
  def interaction_ids
79
93
  messages_or_interaction_or_empty_array.collect do | interaction |
80
94
  interaction["_id"]
@@ -138,6 +152,10 @@ module PactBroker
138
152
  end
139
153
  end
140
154
 
155
+ def remove_ids(interactions_or_messages)
156
+ interactions_or_messages.collect{ | h | h.without("_id") }
157
+ end
158
+
141
159
  def merge_verification_results(interactions, tests)
142
160
  interactions.collect(&:dup).collect do | interaction |
143
161
  interaction["tests"] = tests.select do | test |
@@ -1,16 +1,23 @@
1
1
  require "pact/matchers"
2
2
  require "pact_broker/json"
3
3
  require "pact/matchers/unix_diff_formatter"
4
+ require "pact_broker/pacts/sort_content"
5
+ require "pact_broker/pacts/content"
4
6
 
5
7
  module PactBroker
6
8
  module Pacts
7
9
  class CreateFormattedDiff
8
-
9
10
  extend Pact::Matchers
10
11
 
11
- def self.call pact_json_content, previous_pact_json_content
12
+ def self.call pact_json_content, previous_pact_json_content, raw: false
12
13
  pact_hash = JSON.load(pact_json_content, nil, PactBroker::PACT_PARSING_OPTIONS)
13
14
  previous_pact_hash = JSON.load(previous_pact_json_content, nil, PactBroker::PACT_PARSING_OPTIONS)
15
+
16
+ if !raw
17
+ pact_hash = SortContent.call(PactBroker::Pacts::Content.from_hash(pact_hash).without_ids.to_hash)
18
+ previous_pact_hash = SortContent.call(PactBroker::Pacts::Content.from_hash(previous_pact_hash).without_ids.to_hash)
19
+ end
20
+
14
21
  difference = diff(previous_pact_hash, pact_hash)
15
22
  Pact::Matchers::UnixDiffFormatter.call(difference, colour: false, include_explanation: false)
16
23
  end
@@ -6,7 +6,7 @@ module PactBroker
6
6
  class Selector < Hash
7
7
  using PactBroker::HashRefinements
8
8
 
9
- PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :main_branch, :currently_supported, :currently_deployed]
9
+ PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :main_branch, :matching_branch, :currently_supported, :currently_deployed]
10
10
 
11
11
  def initialize(properties = {})
12
12
  properties.without(*PROPERTY_NAMES).tap { |it| warn("WARN: Unsupported property for #{self.class.name}: #{it.keys.join(", ")} at #{caller[0..3]}") if it.any? }
@@ -31,6 +31,8 @@ module PactBroker
31
31
  def type
32
32
  if latest_for_branch?
33
33
  :latest_for_branch
34
+ elsif matching_branch?
35
+ :matching_branch
34
36
  elsif currently_deployed?
35
37
  :currently_deployed
36
38
  elsif currently_supported?
@@ -53,6 +55,10 @@ module PactBroker
53
55
  self[:main_branch] = main_branch
54
56
  end
55
57
 
58
+ def matching_branch= matching_branch
59
+ self[:matching_branch] = matching_branch
60
+ end
61
+
56
62
  def tag= tag
57
63
  self[:tag] = tag
58
64
  end
@@ -130,83 +136,83 @@ module PactBroker
130
136
  end
131
137
 
132
138
  def self.overall_latest
133
- Selector.new(latest: true)
139
+ new(latest: true)
134
140
  end
135
141
 
136
142
  def self.for_main_branch
137
- Selector.new(main_branch: true)
143
+ new(main_branch: true)
138
144
  end
139
145
 
140
146
  def self.latest_for_tag(tag)
141
- Selector.new(latest: true, tag: tag)
147
+ new(latest: true, tag: tag)
142
148
  end
143
149
 
144
150
  def self.latest_for_branch(branch)
145
- Selector.new(latest: true, branch: branch)
151
+ new(latest: true, branch: branch)
146
152
  end
147
153
 
148
154
  def self.latest_for_tag_with_fallback(tag, fallback_tag)
149
- Selector.new(latest: true, tag: tag, fallback_tag: fallback_tag)
155
+ new(latest: true, tag: tag, fallback_tag: fallback_tag)
150
156
  end
151
157
 
152
158
  def self.latest_for_branch_with_fallback(branch, fallback_branch)
153
- Selector.new(latest: true, branch: branch, fallback_branch: fallback_branch)
159
+ new(latest: true, branch: branch, fallback_branch: fallback_branch)
154
160
  end
155
161
 
156
162
  def self.all_for_tag(tag)
157
- Selector.new(tag: tag)
163
+ new(tag: tag)
158
164
  end
159
165
 
160
166
  def self.all_for_tag_and_consumer(tag, consumer)
161
- Selector.new(tag: tag, consumer: consumer)
167
+ new(tag: tag, consumer: consumer)
162
168
  end
163
169
 
164
170
  def self.latest_for_tag_and_consumer(tag, consumer)
165
- Selector.new(latest: true, tag: tag, consumer: consumer)
171
+ new(latest: true, tag: tag, consumer: consumer)
166
172
  end
167
173
 
168
174
  def self.latest_for_branch_and_consumer(branch, consumer)
169
- Selector.new(latest: true, branch: branch, consumer: consumer)
175
+ new(latest: true, branch: branch, consumer: consumer)
170
176
  end
171
177
 
172
178
  def self.latest_for_consumer(consumer)
173
- Selector.new(latest: true, consumer: consumer)
179
+ new(latest: true, consumer: consumer)
174
180
  end
175
181
 
176
182
  def self.for_currently_deployed(environment_name = nil)
177
- Selector.new( { currently_deployed: true, environment_name: environment_name }.compact )
183
+ new( { currently_deployed: true, environment_name: environment_name }.compact )
178
184
  end
179
185
 
180
186
  def self.for_currently_supported(environment_name = nil)
181
- Selector.new( { currently_supported: true, environment_name: environment_name }.compact )
187
+ new( { currently_supported: true, environment_name: environment_name }.compact )
182
188
  end
183
189
 
184
190
  def self.for_currently_deployed_and_consumer(consumer)
185
- Selector.new(currently_deployed: true, consumer: consumer)
191
+ new(currently_deployed: true, consumer: consumer)
186
192
  end
187
193
 
188
194
  def self.for_currently_deployed_and_environment_and_consumer(environment_name, consumer)
189
- Selector.new(currently_deployed: true, environment_name: environment_name, consumer: consumer)
195
+ new(currently_deployed: true, environment_name: environment_name, consumer: consumer)
190
196
  end
191
197
 
192
198
  def self.for_currently_supported_and_environment_and_consumer(environment_name, consumer)
193
- Selector.new(currently_supported: true, environment_name: environment_name, consumer: consumer)
199
+ new(currently_supported: true, environment_name: environment_name, consumer: consumer)
194
200
  end
195
201
 
196
202
  def self.for_environment(environment_name)
197
- Selector.new(environment_name: environment_name)
203
+ new(environment_name: environment_name)
198
204
  end
199
205
 
200
206
  def self.for_environment_and_consumer(environment_name, consumer)
201
- Selector.new(environment_name: environment_name, consumer: consumer)
207
+ new(environment_name: environment_name, consumer: consumer)
202
208
  end
203
209
 
204
210
  def self.from_hash hash
205
- Selector.new(hash)
211
+ new(hash)
206
212
  end
207
213
 
208
214
  def for_consumer(consumer)
209
- Selector.new(to_h.merge(consumer: consumer))
215
+ self.class.new(to_h.merge(consumer: consumer))
210
216
  end
211
217
 
212
218
  def latest_for_main_branch?
@@ -233,6 +239,14 @@ module PactBroker
233
239
  self[:branch]
234
240
  end
235
241
 
242
+ def matching_branch
243
+ self[:matching_branch]
244
+ end
245
+
246
+ def matching_branch?
247
+ !!matching_branch
248
+ end
249
+
236
250
  def overall_latest?
237
251
  !!(latest? && !tag && !branch && !main_branch && !currently_deployed && !currently_supported && !environment_name)
238
252
  end