pact_broker 2.122.0 โ 2.124.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/CHANGELOG.md +53 -0
- data/docs/CONFIGURATION.md +12 -0
- data/lib/pact_broker/api/contracts/consumer_version_selector_contract.rb +17 -1
- data/lib/pact_broker/api/contracts/utf_8_validation.rb +5 -1
- data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +1 -0
- data/lib/pact_broker/api/decorators/publish_contract_decorator.rb +4 -3
- data/lib/pact_broker/api/decorators/verification_decorator.rb +12 -2
- data/lib/pact_broker/api/decorators/verification_summary_decorator.rb +6 -0
- data/lib/pact_broker/api/resources/publish_contracts.rb +10 -6
- data/lib/pact_broker/config/runtime_configuration.rb +15 -0
- data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +10 -2
- data/lib/pact_broker/domain/version.rb +9 -5
- data/lib/pact_broker/hash_refinements.rb +2 -2
- data/lib/pact_broker/locale/en.yml +4 -0
- data/lib/pact_broker/matrix/parse_query.rb +3 -1
- data/lib/pact_broker/matrix/service.rb +5 -0
- data/lib/pact_broker/pacts/generate_sha.rb +7 -4
- data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +41 -21
- data/lib/pact_broker/pacts/repository.rb +1 -0
- data/lib/pact_broker/pacts/selected_pact.rb +4 -0
- data/lib/pact_broker/pacts/selector.rb +20 -2
- data/lib/pact_broker/pacts/selectors.rb +4 -0
- data/lib/pact_broker/ui/controllers/can_i_deploy.rb +1 -1
- data/lib/pact_broker/ui/controllers/matrix.rb +2 -2
- data/lib/pact_broker/verifications/repository.rb +1 -1
- data/lib/pact_broker/verifications/summary_for_consumer_version.rb +4 -0
- data/lib/pact_broker/version.rb +1 -1
- data/pact_broker.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a0262f7dd8bce1f0c350ce3ec9b2b6920e8e36ec672bc34c327cd24adfaaf6a
|
|
4
|
+
data.tar.gz: b3a306b5074d7370d6693f4c5d4219a0c4a069a304035226eff502d6bd9ed3f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d363ce226b34c06e1cf6659d876c4699900598c6df64d9f5bc8c06a5a4b3d514431f4873307d71189fcfd5cd992f8caf582b26af92d2c5bc36cc61ad50a4a17
|
|
7
|
+
data.tar.gz: dcbce49bf421fb8f0294aba8bdf22be64d49e23f74a671531d9bf802717aefdbc58b154b4bfcc02a84e7fcd00709626e898a492f07c7cdd10e30b3d894398f38
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
## [2.124.0] _2026-11-11_
|
|
2
|
+
|
|
3
|
+
### ๐ Features
|
|
4
|
+
|
|
5
|
+
- _(matrix)_ Add matrix_max_limit to cap the matrix query limit
|
|
6
|
+
|
|
7
|
+
### ๐ Bug Fixes
|
|
8
|
+
|
|
9
|
+
- _(matrix)_ Support an environment in a selector alongside a version or latest
|
|
10
|
+
|
|
11
|
+
## [2.123.0] _2026-11-11_
|
|
12
|
+
|
|
13
|
+
### ๐ Features
|
|
14
|
+
|
|
15
|
+
- Add fallbackToMainBranch consumer version selector option
|
|
16
|
+
|
|
17
|
+
### ๐ Bug Fixes
|
|
18
|
+
|
|
19
|
+
- Stop the consumer version verification summary querying per verification
|
|
20
|
+
- Apply fallbackTag per consumer
|
|
21
|
+
- Honour fallbackBranch in consumer version selectors
|
|
22
|
+
|
|
23
|
+
### ๐ Refactor
|
|
24
|
+
|
|
25
|
+
- Name the fallback resolver for tags and branches
|
|
26
|
+
|
|
27
|
+
### โก๏ธ Performance
|
|
28
|
+
|
|
29
|
+
- Speed up publishing large contracts (PACT-7601) ([#1067](https://github.com/pact-foundation/pact_broker/issues/1067))
|
|
30
|
+
> **Migration note:** when contracts are published with /contracts/publish,
|
|
31
|
+
> the configured sha_generator is now called with a second argument,
|
|
32
|
+
- Exclude consumers in the fallback tag query
|
|
33
|
+
- Exclude consumers in the fallback branch query
|
|
34
|
+
|
|
35
|
+
### ๐ Documentation
|
|
36
|
+
|
|
37
|
+
- Say fallbackTag applies per consumer
|
|
38
|
+
- Say fallbackBranch applies per consumer
|
|
39
|
+
|
|
40
|
+
### ๐งช Testing
|
|
41
|
+
|
|
42
|
+
- Share one SQL query counter across feature specs
|
|
43
|
+
- Pin fallbackTag semantics in a table
|
|
44
|
+
- Cover fallbackTag dedup, branches and notices
|
|
45
|
+
- Pin fallbackBranch semantics in a table
|
|
46
|
+
- Cover the fallbackBranch request and notices end to end
|
|
47
|
+
- Pin fallbackToMainBranch semantics in the fallback branch table
|
|
48
|
+
- Cover the fallbackToMainBranch request and notices end to end
|
|
49
|
+
|
|
50
|
+
### ๐ ๏ธ Miscellaneous Tasks
|
|
51
|
+
|
|
52
|
+
- Drop a TODO the selector contract already resolves
|
|
53
|
+
|
|
1
54
|
## [2.122.0] _2026-10-01_
|
|
2
55
|
|
|
3
56
|
### ๐ Features
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -703,6 +703,18 @@ and at large numbers, the graph will not render due to browser performance issue
|
|
|
703
703
|
**Default:** `150`<br/>
|
|
704
704
|
**Allowed values:** A positive integer<br/>
|
|
705
705
|
|
|
706
|
+
### matrix_max_limit
|
|
707
|
+
|
|
708
|
+
The maximum value accepted for the `limit` parameter of a matrix query (`/matrix`).
|
|
709
|
+
A request with a larger `limit` is rejected with a 400 response. Very large limits can cause expensive database queries.
|
|
710
|
+
When not set, there is no maximum. A matrix query without a `limit` uses a limit of 100,
|
|
711
|
+
so the value must be 100 or greater, or the Pact Broker will not start.
|
|
712
|
+
|
|
713
|
+
**Environment variable name:** `PACT_BROKER_MATRIX_MAX_LIMIT`<br/>
|
|
714
|
+
**YAML configuration key name:** `matrix_max_limit`<br/>
|
|
715
|
+
**Default:** No maximum<br/>
|
|
716
|
+
**Allowed values:** An integer of 100 or greater<br/>
|
|
717
|
+
|
|
706
718
|
<br/>
|
|
707
719
|
|
|
708
720
|
## Miscellaneous
|
|
@@ -6,7 +6,7 @@ module PactBroker
|
|
|
6
6
|
class ConsumerVersionSelectorContract < BaseContract
|
|
7
7
|
option :parent # the parent hash in which the ConsumerVersionSelector is embedded
|
|
8
8
|
|
|
9
|
-
BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :matchingBranch, :mainBranch]
|
|
9
|
+
BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :fallbackToMainBranch, :matchingBranch, :mainBranch]
|
|
10
10
|
ENVIRONMENT_KEYS = [:environment, :deployed, :released, :deployedOrReleased]
|
|
11
11
|
ALL_KEYS = BRANCH_KEYS + ENVIRONMENT_KEYS + [:consumer]
|
|
12
12
|
|
|
@@ -18,6 +18,7 @@ module PactBroker
|
|
|
18
18
|
optional(:latest).filled(included_in?: [true, false])
|
|
19
19
|
optional(:fallbackTag).filled(:str?)
|
|
20
20
|
optional(:fallbackBranch).filled(:str?)
|
|
21
|
+
optional(:fallbackToMainBranch).filled(included_in?: [true])
|
|
21
22
|
optional(:consumer).filled(:str?)
|
|
22
23
|
optional(:deployed).filled(included_in?: [true])
|
|
23
24
|
optional(:released).filled(included_in?: [true])
|
|
@@ -108,6 +109,21 @@ module PactBroker
|
|
|
108
109
|
end
|
|
109
110
|
end
|
|
110
111
|
|
|
112
|
+
# fallbackToMainBranch
|
|
113
|
+
rule(:fallbackToMainBranch, :fallbackBranch, :branch, :latest) do
|
|
114
|
+
if values[:fallbackToMainBranch] && !values[:branch]
|
|
115
|
+
base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_requires_branch"))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if values[:fallbackToMainBranch] && values[:fallbackBranch]
|
|
119
|
+
base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_and_fallback_branch_disallowed"))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if values[:fallbackToMainBranch] && values[:latest] == false
|
|
123
|
+
base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_and_latest_false_disallowed"))
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
111
127
|
# branch/latest
|
|
112
128
|
rule(:branch, :latest) do
|
|
113
129
|
if values[:branch] && values[:latest] == false
|
|
@@ -5,7 +5,11 @@ module PactBroker
|
|
|
5
5
|
extend self
|
|
6
6
|
|
|
7
7
|
def fragment_before_invalid_utf_8_char(string)
|
|
8
|
-
|
|
8
|
+
# Iterating over each char allocates a String per char, which is very slow for large strings,
|
|
9
|
+
# so only do it to find the location of the invalid char once we know there is one.
|
|
10
|
+
return nil if string.force_encoding("UTF-8").valid_encoding?
|
|
11
|
+
|
|
12
|
+
string.each_char.with_index do | char, index |
|
|
9
13
|
if !char.valid_encoding?
|
|
10
14
|
fragment = index < 100 ? string[0...index] : string[index-100...index]
|
|
11
15
|
return index + 1, fragment
|
|
@@ -11,10 +11,11 @@ module PactBroker
|
|
|
11
11
|
property :provider_name
|
|
12
12
|
property :specification
|
|
13
13
|
property :content_type
|
|
14
|
-
property :decoded_content, setter: -> (fragment:, represented:, user_options:, **) {
|
|
14
|
+
property :decoded_content, setter: -> (fragment:, represented:, user_options:, doc:, **) {
|
|
15
15
|
represented.decoded_content = fragment
|
|
16
|
-
# Set the pact version sha when we set the content
|
|
17
|
-
|
|
16
|
+
# Set the pact version sha when we set the content, reusing the content that has already been
|
|
17
|
+
# parsed in the resource, as parsing very large pacts is expensive
|
|
18
|
+
represented.pact_version_sha = user_options.fetch(:sha_generator).call(fragment, parsed_content: doc["decodedParsedContent"])
|
|
18
19
|
}
|
|
19
20
|
property :on_conflict, default: "overwrite"
|
|
20
21
|
|
|
@@ -24,7 +24,7 @@ module PactBroker
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
link :self do | options |
|
|
27
|
-
pact = options
|
|
27
|
+
pact = pact_for(options)
|
|
28
28
|
{
|
|
29
29
|
title: "Verification result",
|
|
30
30
|
name: "Verification result #{represented.number} for #{pact.name}",
|
|
@@ -33,7 +33,7 @@ module PactBroker
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
link "pb:pact-version" do | options |
|
|
36
|
-
pact = options
|
|
36
|
+
pact = pact_for(options)
|
|
37
37
|
{
|
|
38
38
|
title: "Pact",
|
|
39
39
|
name: pact.name,
|
|
@@ -47,6 +47,16 @@ module PactBroker
|
|
|
47
47
|
href: verification_triggered_webhooks_url(represented, options.fetch(:base_url))
|
|
48
48
|
}
|
|
49
49
|
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# A decorator rendering many verifications passes :pacts_by_pact_version_sha
|
|
54
|
+
# so each one avoids its own lookup.
|
|
55
|
+
def pact_for(options)
|
|
56
|
+
options[:pact] ||
|
|
57
|
+
options[:pacts_by_pact_version_sha]&.[](represented.pact_version_sha) ||
|
|
58
|
+
represented.latest_pact_publication
|
|
59
|
+
end
|
|
50
60
|
end
|
|
51
61
|
end
|
|
52
62
|
end
|
|
@@ -25,6 +25,12 @@ module PactBroker
|
|
|
25
25
|
}
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# representable passes through the kwargs from to_json as normal args
|
|
29
|
+
def to_hash(options)
|
|
30
|
+
user_options = options.fetch(:user_options, {}).merge(pacts_by_pact_version_sha: represented.pacts_by_pact_version_sha)
|
|
31
|
+
super(options.merge(user_options: user_options))
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
def provider_summary
|
|
29
35
|
OpenStruct.new(
|
|
30
36
|
successful: represented.select(&:success).collect(&:provider_name),
|
|
@@ -56,16 +56,20 @@ module PactBroker
|
|
|
56
56
|
@parsed_contracts ||= decorator_class(:publish_contracts_decorator).new(PactBroker::Contracts::ContractsToPublish.new).from_hash(params, { user_options: { sha_generator: PactBroker.configuration.sha_generator } } )
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Memoized because decoding and parsing very large contracts is expensive,
|
|
60
|
+
# and this is called multiple times per request (validation, authorization and publication)
|
|
59
61
|
def params
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
p["contracts"].
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
@params_with_decoded_contracts ||= begin
|
|
63
|
+
p = super(default: {}, symbolize_names: false)
|
|
64
|
+
if p["contracts"].is_a?(Array)
|
|
65
|
+
p["contracts"].each do | contract |
|
|
66
|
+
if contract.is_a?(Hash)
|
|
67
|
+
decode_and_parse_content(contract)
|
|
68
|
+
end
|
|
65
69
|
end
|
|
66
70
|
end
|
|
71
|
+
p
|
|
67
72
|
end
|
|
68
|
-
p
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def schema
|
|
@@ -20,6 +20,7 @@ require "pact_broker/config/runtime_configuration_basic_auth_methods"
|
|
|
20
20
|
require "pact_broker/string_refinements"
|
|
21
21
|
require "pact_broker/hash_refinements"
|
|
22
22
|
require "pact_broker/error"
|
|
23
|
+
require "pact_broker/matrix/parse_query"
|
|
23
24
|
|
|
24
25
|
module PactBroker
|
|
25
26
|
module Config
|
|
@@ -97,9 +98,12 @@ module PactBroker
|
|
|
97
98
|
semver_formats: ["%M.%m.%p%s%d", "%M.%m", "%M"],
|
|
98
99
|
seed_example_data: true,
|
|
99
100
|
network_diagram_max_pacticipants: 150,
|
|
101
|
+
matrix_max_limit: nil,
|
|
100
102
|
features: {}
|
|
101
103
|
)
|
|
102
104
|
|
|
105
|
+
on_load :validate_matrix_max_limit!
|
|
106
|
+
|
|
103
107
|
def self.getter_and_setter_method_names
|
|
104
108
|
extra_methods = [
|
|
105
109
|
:warning_error_classes,
|
|
@@ -114,6 +118,7 @@ module PactBroker
|
|
|
114
118
|
coerce_types(
|
|
115
119
|
features: COERCE_FEATURES,
|
|
116
120
|
network_diagram_max_pacticipants: :integer,
|
|
121
|
+
matrix_max_limit: :integer,
|
|
117
122
|
webhook_certificates: COERCE_WEBHOOKS
|
|
118
123
|
)
|
|
119
124
|
sensitive_values(:database_url, :database_password)
|
|
@@ -226,6 +231,16 @@ module PactBroker
|
|
|
226
231
|
end
|
|
227
232
|
end
|
|
228
233
|
|
|
234
|
+
# A matrix query without a limit gets the default limit, so a lower matrix_max_limit
|
|
235
|
+
# would reject every request that does not set one.
|
|
236
|
+
# An empty or non-numeric value is coerced to 0, so it is rejected here too.
|
|
237
|
+
def validate_matrix_max_limit!
|
|
238
|
+
default_limit = PactBroker::Matrix::ParseQuery::DEFAULT_LIMIT
|
|
239
|
+
if matrix_max_limit && matrix_max_limit < default_limit
|
|
240
|
+
raise_validation_error("matrix_max_limit must be at least the default matrix limit (#{default_limit}), or not set")
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
229
244
|
def raise_validation_error(msg)
|
|
230
245
|
raise PactBroker::ConfigurationError, msg
|
|
231
246
|
end
|
|
@@ -32,7 +32,15 @@ Example: This data structure represents the way a user might specify "I want to
|
|
|
32
32
|
|
|
33
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
34
|
|
|
35
|
-
`consumerVersionSelectors.fallbackBranch`: the name of the branch to
|
|
35
|
+
`consumerVersionSelectors.fallbackBranch`: the name of the branch to fall back to for each consumer that has no pact from the specified `branch`. 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.
|
|
36
|
+
|
|
37
|
+
`consumerVersionSelectors.fallbackToMainBranch`: if the key is specified, can only be set to `true`. Used with `branch`. For each consumer that has no pact for the specified `branch`, returns the latest pact from that consumer's configured `mainBranch` instead. Consumers without a configured `mainBranch` have no fallback. Use this instead of `fallbackBranch` when the consumers have different main branch names, or when a main branch may be renamed, so the provider does not need to know the name. It cannot be used together with `fallbackBranch`. The same caveat as `fallbackBranch` applies.
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
"consumerVersionSelectors": [
|
|
41
|
+
{ "branch": "feat-x", "fallbackToMainBranch": true }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
36
44
|
|
|
37
45
|
`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.
|
|
38
46
|
|
|
@@ -48,7 +56,7 @@ Example: This data structure represents the way a user might specify "I want to
|
|
|
48
56
|
|
|
49
57
|
`consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
|
|
50
58
|
|
|
51
|
-
`consumerVersionSelectors.fallbackTag`: the name of the tag to
|
|
59
|
+
`consumerVersionSelectors.fallbackTag`: the name of the tag to fall back to for each consumer that has no pact with the specified `tag`. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use two separate selectors - one with the main branch name and one with the feature branch name.*
|
|
52
60
|
|
|
53
61
|
`providerVersionBranch`: the repository branch name for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not.
|
|
54
62
|
|
|
@@ -85,20 +85,24 @@ module PactBroker
|
|
|
85
85
|
# end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# Filters on a UNION of version ids so the versions dataset stays unwrapped for later criteria.
|
|
89
|
+
# An OR of two IN subqueries is equivalent, but cannot use the versions primary key index.
|
|
88
90
|
def currently_in_environment(environment_name, pacticipant_name)
|
|
89
|
-
|
|
91
|
+
version_ids = version_ids_currently_deployed_to_environment(environment_name, pacticipant_name)
|
|
92
|
+
.union(version_ids_currently_supported_in_environment(environment_name, pacticipant_name), from_self: false)
|
|
93
|
+
where(Sequel[:versions][:id] => version_ids)
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
def
|
|
96
|
+
def version_ids_currently_deployed_to_environment(environment_name, pacticipant_name)
|
|
93
97
|
deployed_version_query = PactBroker::Deployments::DeployedVersion.currently_deployed.for_environment_name(environment_name)
|
|
94
98
|
deployed_version_query = deployed_version_query.for_pacticipant_name(pacticipant_name) if pacticipant_name
|
|
95
|
-
|
|
99
|
+
deployed_version_query.select(:version_id)
|
|
96
100
|
end
|
|
97
101
|
|
|
98
|
-
def
|
|
102
|
+
def version_ids_currently_supported_in_environment(environment_name, pacticipant_name)
|
|
99
103
|
supported_version_query = PactBroker::Deployments::ReleasedVersion.currently_supported.for_environment_name(environment_name)
|
|
100
104
|
supported_version_query = supported_version_query.for_pacticipant_name(pacticipant_name) if pacticipant_name
|
|
101
|
-
|
|
105
|
+
supported_version_query.select(:version_id)
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
def currently_deployed
|
|
@@ -80,8 +80,8 @@ module PactBroker
|
|
|
80
80
|
def symbolize_keys_private(params)
|
|
81
81
|
case params
|
|
82
82
|
when Hash
|
|
83
|
-
params.
|
|
84
|
-
result
|
|
83
|
+
params.each_with_object({}) do |(key, value), result|
|
|
84
|
+
result[key.to_sym] = symbolize_keys_private(value)
|
|
85
85
|
end
|
|
86
86
|
when Array
|
|
87
87
|
params.collect { |value| symbolize_keys_private(value) }
|
|
@@ -97,6 +97,7 @@ en:
|
|
|
97
97
|
cannot_set_currently_deployed_true: The currentlyDeployed property cannot be set back to true. Please record a new deployment.
|
|
98
98
|
cannot_set_currently_supported_true: The currentlySupported property cannot be set back to true. Please record a new deployment.
|
|
99
99
|
invalid_limit: The limit must be 1 or greater.
|
|
100
|
+
matrix_limit_exceeds_maximum: "The limit must be %{max_limit} or less."
|
|
100
101
|
pacts_for_verification_selector_fallback_tag: "fallbackTag can only be set if latest is true"
|
|
101
102
|
pacts_for_verification_selector_matching_branch_requires_provider_version_branch: the providerVersionBranch must be specified when the selector matchingBranch=true is used
|
|
102
103
|
pacts_for_verification_selector_tag_and_branch_disallowed: "cannot specify both a tag and a branch"
|
|
@@ -105,6 +106,9 @@ en:
|
|
|
105
106
|
pacts_for_verification_selector_branch_and_latest_false_disallowed: cannot specify a branch with latest=false
|
|
106
107
|
pacts_for_verification_selector_main_branch_and_latest_false_disallowed: cannot specify mainBranch=true with latest=false
|
|
107
108
|
pacts_for_verification_selector_fallback_branch_and_latest_false_disallowed: fallbackBranch can only be set if latest is true
|
|
109
|
+
pacts_for_verification_selector_fallback_to_main_branch_requires_branch: "a branch must be specified when fallbackToMainBranch=true is specified"
|
|
110
|
+
pacts_for_verification_selector_fallback_to_main_branch_and_fallback_branch_disallowed: cannot specify both fallbackBranch and fallbackToMainBranch=true
|
|
111
|
+
pacts_for_verification_selector_fallback_to_main_branch_and_latest_false_disallowed: fallbackToMainBranch can only be set if latest is true
|
|
108
112
|
pacts_for_verification_selector_required_params_missing: 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
|
|
109
113
|
pacts_for_verification_selector_main_branch_with_other_param_disallowed: cannot specify mainBranch=true with any other criteria apart from consumer
|
|
110
114
|
pacts_for_verification_selector_matching_branch_with_other_param_disallowed: cannot specify matchingBranch=true with any other criteria apart from consumer
|
|
@@ -4,6 +4,8 @@ require "pact_broker/matrix/unresolved_selector"
|
|
|
4
4
|
module PactBroker
|
|
5
5
|
module Matrix
|
|
6
6
|
class ParseQuery
|
|
7
|
+
DEFAULT_LIMIT = 100
|
|
8
|
+
|
|
7
9
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
8
10
|
# rubocop: disable Metrics/MethodLength
|
|
9
11
|
def self.call query
|
|
@@ -34,7 +36,7 @@ module PactBroker
|
|
|
34
36
|
if params.key?("limit") && params["limit"] != ""
|
|
35
37
|
options[:limit] = params["limit"]
|
|
36
38
|
else
|
|
37
|
-
options[:limit] =
|
|
39
|
+
options[:limit] = DEFAULT_LIMIT.to_s
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
if params.key?("latest") && params["latest"] != ""
|
|
@@ -140,6 +140,11 @@ module PactBroker
|
|
|
140
140
|
error_messages << message("errors.validation.invalid_limit")
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
max_limit = PactBroker.configuration.matrix_max_limit
|
|
144
|
+
if max_limit && options[:limit] && options[:limit].to_i > max_limit
|
|
145
|
+
error_messages << message("errors.validation.matrix_limit_exceeds_maximum", max_limit: max_limit)
|
|
146
|
+
end
|
|
147
|
+
|
|
143
148
|
error_messages
|
|
144
149
|
end
|
|
145
150
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
@@ -11,17 +11,20 @@ module PactBroker
|
|
|
11
11
|
include PactBroker::Logging
|
|
12
12
|
|
|
13
13
|
# @param [String] json_content
|
|
14
|
-
|
|
14
|
+
# @param [Hash] options
|
|
15
|
+
# @option options [Hash] :parsed_content the result of parsing the json_content, if it has already been parsed.
|
|
16
|
+
# Used to avoid parsing very large pacts again. Must be the parsed json_content, or the sha will be wrong.
|
|
17
|
+
def self.call(json_content, options = {})
|
|
15
18
|
content_for_sha = if PactBroker.configuration.base_equality_only_on_content_that_affects_verification_results
|
|
16
|
-
extract_verifiable_content_for_sha(json_content)
|
|
19
|
+
extract_verifiable_content_for_sha(json_content, options[:parsed_content])
|
|
17
20
|
else
|
|
18
21
|
json_content
|
|
19
22
|
end
|
|
20
23
|
measure_info("Generating SHA1 hexdigest for pact", payload: { length: content_for_sha.length } ){ Digest::SHA1.hexdigest(content_for_sha) }
|
|
21
24
|
end
|
|
22
25
|
|
|
23
|
-
def self.extract_verifiable_content_for_sha(json_content)
|
|
24
|
-
objects = Content.from_json(json_content)
|
|
26
|
+
def self.extract_verifiable_content_for_sha(json_content, parsed_content = nil)
|
|
27
|
+
objects = parsed_content.is_a?(Hash) ? Content.from_hash(parsed_content) : Content.from_json(json_content)
|
|
25
28
|
sorted_content = measure_info("Sorting content", payload: { length: json_content.length }){ objects.sort }
|
|
26
29
|
sorted_content.content_that_affects_verification_results.to_json
|
|
27
30
|
end
|
|
@@ -28,7 +28,9 @@ module PactBroker
|
|
|
28
28
|
# @return [VerifiablePact] an array of VerifiablePact objects
|
|
29
29
|
def find(provider_name, consumer_version_selectors)
|
|
30
30
|
selected_pacts = find_pacts_by_selector(provider_name, consumer_version_selectors)
|
|
31
|
-
selected_pacts = selected_pacts +
|
|
31
|
+
selected_pacts = selected_pacts +
|
|
32
|
+
find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors) +
|
|
33
|
+
find_pacts_for_fallback_branches(selected_pacts, provider_name, consumer_version_selectors)
|
|
32
34
|
merge_selected_pacts(selected_pacts)
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -178,22 +180,32 @@ module PactBroker
|
|
|
178
180
|
end
|
|
179
181
|
end
|
|
180
182
|
|
|
183
|
+
# The fallback is applied per consumer, so a consumer without a pact for the tag
|
|
184
|
+
# still gets its fallback pact when another consumer does have a pact for the tag.
|
|
181
185
|
def find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
selected_pact.latest_for_tag?(selector.tag)
|
|
186
|
+
consumer_version_selectors.select(&:fallback_tag?).flat_map do | selector |
|
|
187
|
+
consumer_ids_with_a_pact_for_the_tag = consumer_ids_of(selected_pacts.select { | selected_pact | selected_pact.latest_for_tag?(selector.tag) })
|
|
188
|
+
find_fallback_pacts(provider_name, selector, consumer_ids_with_a_pact_for_the_tag) do | query |
|
|
189
|
+
query.for_latest_consumer_versions_with_tag(selector.fallback_tag)
|
|
187
190
|
end
|
|
188
191
|
end
|
|
192
|
+
end
|
|
189
193
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
# The fallback is applied per consumer, so a consumer without a pact for the branch
|
|
195
|
+
# still gets its fallback pact when another consumer does have a pact for the branch.
|
|
196
|
+
def find_pacts_for_fallback_branches(selected_pacts, provider_name, consumer_version_selectors)
|
|
197
|
+
consumer_version_selectors.select { | selector | selector.fallback_branch? || selector.fallback_to_main_branch? }.flat_map do | selector |
|
|
198
|
+
consumer_ids_with_a_pact_for_the_branch = consumer_ids_of(selected_pacts.select { | selected_pact | selected_pact.latest_for_branch?(selector.branch) })
|
|
199
|
+
find_fallback_pacts(provider_name, selector, consumer_ids_with_a_pact_for_the_branch) do | query |
|
|
200
|
+
selector.fallback_to_main_branch? ? query.latest_for_main_branches : query.latest_for_consumer_branch(selector.fallback_branch)
|
|
201
|
+
end
|
|
194
202
|
end
|
|
195
203
|
end
|
|
196
204
|
|
|
205
|
+
def consumer_ids_of(selected_pacts)
|
|
206
|
+
selected_pacts.collect { | selected_pact | selected_pact.pact.consumer.id }.uniq
|
|
207
|
+
end
|
|
208
|
+
|
|
197
209
|
def find_pacts_by_selector(provider_name, consumer_version_selectors)
|
|
198
210
|
provider = pacticipant_repository.find_by_name(provider_name)
|
|
199
211
|
|
|
@@ -248,17 +260,25 @@ module PactBroker
|
|
|
248
260
|
selectors
|
|
249
261
|
end
|
|
250
262
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
end
|
|
263
|
+
# Yields the provider's pacts so the caller can narrow them to the latest for the fallback.
|
|
264
|
+
def find_fallback_pacts(provider_name, selector, consumer_ids_to_exclude)
|
|
265
|
+
query = yield scope_for(PactPublication).eager_for_domain_with_content.for_provider_name(provider_name)
|
|
266
|
+
query = query.for_consumer_name(selector.consumer) if selector.consumer
|
|
267
|
+
query = query.exclude(Sequel[:pact_publications][:consumer_id] => consumer_ids_to_exclude) if consumer_ids_to_exclude.any?
|
|
268
|
+
query.all.collect do | pact_publication |
|
|
269
|
+
SelectedPact.new(
|
|
270
|
+
pact_publication.to_domain,
|
|
271
|
+
Selectors.new(resolve_selector_for_fallback(selector, pact_publication))
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def resolve_selector_for_fallback(selector, pact_publication)
|
|
277
|
+
if selector.fallback_to_main_branch?
|
|
278
|
+
selector.resolve_for_fallback_to_main_branch(pact_publication.consumer_version, pact_publication.consumer.main_branch)
|
|
279
|
+
else
|
|
280
|
+
selector.resolve_for_fallback(pact_publication.consumer_version)
|
|
281
|
+
end
|
|
262
282
|
end
|
|
263
283
|
|
|
264
284
|
def find_provider_tags_for_which_pact_publication_id_is_pending(pact_publication, successfully_verified_head_pact_publication_ids_for_each_provider_tag)
|
|
@@ -202,6 +202,7 @@ module PactBroker
|
|
|
202
202
|
def find_by_consumer_version consumer_name, consumer_version_number
|
|
203
203
|
scope_for(PactPublication)
|
|
204
204
|
.eager_for_domain_with_content
|
|
205
|
+
.eager(pact_version: :latest_verification)
|
|
205
206
|
.for_consumer_name_and_maybe_version_number(consumer_name, consumer_version_number)
|
|
206
207
|
.remove_overridden_revisions_from_complete_query
|
|
207
208
|
.all
|
|
@@ -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, :matching_branch, :currently_supported, :currently_deployed]
|
|
9
|
+
PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :fallback_to_main_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? }
|
|
@@ -14,13 +14,19 @@ module PactBroker
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def resolve(consumer_version)
|
|
17
|
-
ResolvedSelector.new(self.to_h.without(:fallback_tag, :fallback_branch), consumer_version)
|
|
17
|
+
ResolvedSelector.new(self.to_h.without(:fallback_tag, :fallback_branch, :fallback_to_main_branch), consumer_version)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def resolve_for_fallback(consumer_version)
|
|
21
21
|
ResolvedSelector.new(self.to_h, consumer_version)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Records the consumer's main branch as the fallback branch, so the verification
|
|
25
|
+
# notice names the branch that was actually used.
|
|
26
|
+
def resolve_for_fallback_to_main_branch(consumer_version, main_branch)
|
|
27
|
+
ResolvedSelector.new(self.to_h.without(:fallback_to_main_branch).merge(fallback_branch: main_branch), consumer_version)
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
def resolve_for_environment(consumer_version, environment, target = nil)
|
|
25
31
|
ResolvedSelector.new(self.to_h.merge({ environment: environment, target: target }.compact), consumer_version)
|
|
26
32
|
end
|
|
@@ -87,6 +93,10 @@ module PactBroker
|
|
|
87
93
|
self[:fallback_branch] = fallback_branch
|
|
88
94
|
end
|
|
89
95
|
|
|
96
|
+
def fallback_to_main_branch= fallback_to_main_branch
|
|
97
|
+
self[:fallback_to_main_branch] = fallback_to_main_branch
|
|
98
|
+
end
|
|
99
|
+
|
|
90
100
|
def fallback_tag
|
|
91
101
|
self[:fallback_tag]
|
|
92
102
|
end
|
|
@@ -95,6 +105,10 @@ module PactBroker
|
|
|
95
105
|
self[:fallback_branch]
|
|
96
106
|
end
|
|
97
107
|
|
|
108
|
+
def fallback_to_main_branch
|
|
109
|
+
self[:fallback_to_main_branch]
|
|
110
|
+
end
|
|
111
|
+
|
|
98
112
|
def consumer= consumer
|
|
99
113
|
self[:consumer] = consumer
|
|
100
114
|
end
|
|
@@ -235,6 +249,10 @@ module PactBroker
|
|
|
235
249
|
!!fallback_branch
|
|
236
250
|
end
|
|
237
251
|
|
|
252
|
+
def fallback_to_main_branch?
|
|
253
|
+
!!fallback_to_main_branch
|
|
254
|
+
end
|
|
255
|
+
|
|
238
256
|
def main_branch
|
|
239
257
|
self[:main_branch]
|
|
240
258
|
end
|
|
@@ -59,6 +59,10 @@ module PactBroker
|
|
|
59
59
|
any? { | selector | selector.latest_for_tag?(potential_tag) }
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def latest_for_branch? potential_branch = nil
|
|
63
|
+
any? { | selector | selector.latest_for_branch?(potential_branch) }
|
|
64
|
+
end
|
|
65
|
+
|
|
62
66
|
def tag_names_of_selectors_for_all_pacts
|
|
63
67
|
select(&:all_for_tag?).collect(&:tag).uniq
|
|
64
68
|
end
|
|
@@ -18,7 +18,7 @@ module PactBroker
|
|
|
18
18
|
get "/:pacticipant_name/latest-version/:tag/can-i-deploy/to/:environment_tag" do
|
|
19
19
|
# selector and options must be in sync with lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_badge.rb
|
|
20
20
|
selectors = [ PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: params[:pacticipant_name], latest: true, tag: params[:tag]) ]
|
|
21
|
-
options = { latestby: "cvp", limit:
|
|
21
|
+
options = { latestby: "cvp", limit: PactBroker::Matrix::ParseQuery::DEFAULT_LIMIT, tag: params[:to] }
|
|
22
22
|
result = matrix_service.find(selectors, options)
|
|
23
23
|
lines = PactBroker::UI::ViewModels::MatrixLines.new(result, base_url: base_url)
|
|
24
24
|
locals = {
|
|
@@ -17,7 +17,7 @@ module PactBroker
|
|
|
17
17
|
|
|
18
18
|
get "/" do
|
|
19
19
|
selectors = [OpenStruct.new, OpenStruct.new]
|
|
20
|
-
options = { limit:
|
|
20
|
+
options = { limit: PactBroker::Matrix::ParseQuery::DEFAULT_LIMIT, latestby: nil }
|
|
21
21
|
locals = {
|
|
22
22
|
lines: [],
|
|
23
23
|
selectors: create_selector_objects(selectors),
|
|
@@ -50,7 +50,7 @@ module PactBroker
|
|
|
50
50
|
PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: params[:consumer_name]),
|
|
51
51
|
PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: params[:provider_name])
|
|
52
52
|
]
|
|
53
|
-
options = { latestby: "cvpv", limit:
|
|
53
|
+
options = { latestby: "cvpv", limit: PactBroker::Matrix::ParseQuery::DEFAULT_LIMIT }
|
|
54
54
|
lines = matrix_service.find(selectors, options)
|
|
55
55
|
lines = PactBroker::UI::ViewModels::MatrixLines.new(lines, base_url: base_url)
|
|
56
56
|
locals = {
|
|
@@ -99,7 +99,7 @@ module PactBroker
|
|
|
99
99
|
.remove_verifications_for_overridden_consumer_versions
|
|
100
100
|
.for_consumer_name_and_consumer_version_number(consumer_name, consumer_version_number)
|
|
101
101
|
.latest_by_pact_version
|
|
102
|
-
.eager(:provider)
|
|
102
|
+
.eager(:provider, :consumer, :provider_version, :pact_version)
|
|
103
103
|
.all
|
|
104
104
|
.sort_by { | v | v.provider_name.downcase }
|
|
105
105
|
end
|
data/lib/pact_broker/version.rb
CHANGED
data/pact_broker.gemspec
CHANGED
|
@@ -64,7 +64,7 @@ Gem::Specification.new do |gem|
|
|
|
64
64
|
gem.add_runtime_dependency "rack-protection", "~> 4.1"
|
|
65
65
|
gem.add_runtime_dependency "table_print", "~> 1.5"
|
|
66
66
|
gem.add_runtime_dependency "semantic_logger", "~> 5.0"
|
|
67
|
-
gem.add_runtime_dependency "sanitize", "~>
|
|
67
|
+
gem.add_runtime_dependency "sanitize", "~> 7.0"
|
|
68
68
|
gem.add_runtime_dependency "wisper", "~> 2.0"
|
|
69
69
|
gem.add_runtime_dependency "anyway_config", "~> 2.1"
|
|
70
70
|
gem.add_runtime_dependency "request_store", "~> 1.5"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pact_broker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.124.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bethany Skurrie
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2026-
|
|
13
|
+
date: 2026-11-11 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: json
|
|
@@ -228,14 +228,14 @@ dependencies:
|
|
|
228
228
|
requirements:
|
|
229
229
|
- - "~>"
|
|
230
230
|
- !ruby/object:Gem::Version
|
|
231
|
-
version: '
|
|
231
|
+
version: '7.0'
|
|
232
232
|
type: :runtime
|
|
233
233
|
prerelease: false
|
|
234
234
|
version_requirements: !ruby/object:Gem::Requirement
|
|
235
235
|
requirements:
|
|
236
236
|
- - "~>"
|
|
237
237
|
- !ruby/object:Gem::Version
|
|
238
|
-
version: '
|
|
238
|
+
version: '7.0'
|
|
239
239
|
- !ruby/object:Gem::Dependency
|
|
240
240
|
name: wisper
|
|
241
241
|
requirement: !ruby/object:Gem::Requirement
|