Skip to content

Commit 125c272

Browse files
committed
feat(matrix): allow provider to be deployed to an environment without the consumer having to be deployed there already
Closes: pact-foundation/pact_broker-client#48
1 parent 2e2a203 commit 125c272

8 files changed

+343
-84
lines changed

lib/pact_broker/matrix/deployment_status_summary.rb

+15-16
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,24 @@ def counts
1717
{
1818
success: rows.count{ |row| row.success },
1919
failed: rows.count { |row| row.success == false },
20-
unknown: integrations_without_a_row.count + rows.count { |row| row.success.nil? }
20+
unknown: required_integrations_without_a_row.count + rows.count { |row| row.success.nil? }
2121
}
2222
end
2323

2424
def deployable?
25-
return nil if rows.empty?
25+
# return nil if rows.empty?
2626
return nil if rows.any?{ |row| row.success.nil? }
27-
return nil if integrations_without_a_row.any?
27+
return nil if required_integrations_without_a_row.any?
2828
rows.all?{ |row| row.success }
2929
end
3030

3131
def reasons
3232
@reasons ||= begin
3333
reasons = []
34-
if rows.empty?
35-
reasons << "No results matched the given query"
36-
else
37-
reasons.concat(missing_reasons)
38-
reasons.concat(failure_messages)
39-
reasons.concat(unverified_messages)
40-
reasons.concat(success_messages)
41-
end
34+
reasons.concat(missing_reasons)
35+
reasons.concat(failure_messages)
36+
reasons.concat(unverified_messages)
37+
reasons.concat(success_messages)
4238
reasons
4339
end
4440
end
@@ -60,16 +56,19 @@ def failure_messages
6056
end
6157

6258
def success_messages
63-
if rows.all?{ |row| row.success } && integrations_without_a_row.empty?
59+
if rows.all?{ |row| row.success } && required_integrations_without_a_row.empty?
6460
["All verification results are published and successful"]
6561
else
6662
[]
6763
end
6864
end
6965

70-
def integrations_without_a_row
71-
@integrations_without_a_row ||= begin
72-
integrations.select do | relationship |
66+
# For deployment, the consumer requires the provider,
67+
# but the provider does not require the consumer
68+
# This method tells us which providers are missing.
69+
def required_integrations_without_a_row
70+
@required_integrations_without_a_row ||= begin
71+
integrations.select(&:required?).select do | relationship |
7372
!rows.find do | row |
7473
row.consumer_id == relationship.consumer_id && row.provider_id == relationship.provider_id
7574
end
@@ -78,7 +77,7 @@ def integrations_without_a_row
7877
end
7978

8079
def missing_reasons
81-
integrations_without_a_row.collect do | missing_relationship|
80+
required_integrations_without_a_row.collect do | missing_relationship|
8281
consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})"
8382
provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})"
8483
"There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}"

lib/pact_broker/matrix/integration.rb

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
1-
#
2-
# Represents the integration relationship between a consumer and a provider
3-
#
1+
2+
# Represents the integration relationship between a consumer and a provider in the context
3+
# of a matrix or can-i-deploy query.
4+
# If the required flag is set, then one of the pacticipants (consumers) specified in the HTTP query
5+
# requires the provider. It would not be required if a provider was specified, and it had an
6+
# integration with a consumer.
7+
48
module PactBroker
59
module Matrix
610
class Integration
711

812
attr_reader :consumer_name, :consumer_id, :provider_name, :provider_id
913

10-
def initialize consumer_id, consumer_name, provider_id, provider_name
14+
def initialize consumer_id, consumer_name, provider_id, provider_name, required
1115
@consumer_id = consumer_id
1216
@consumer_name = consumer_name
1317
@provider_id = provider_id
1418
@provider_name = provider_name
19+
@required = required
1520
end
1621

1722
def self.from_hash hash
1823
new(
1924
hash.fetch(:consumer_id),
2025
hash.fetch(:consumer_name),
2126
hash.fetch(:provider_id),
22-
hash.fetch(:provider_name)
27+
hash.fetch(:provider_name),
28+
hash.fetch(:required)
2329
)
2430
end
2531

32+
def required?
33+
@required
34+
end
35+
2636
def == other
2737
consumer_id == other.consumer_id && provider_id == other.provider_id
2838
end
2939

3040
def <=> other
3141
comparison = consumer_name <=> other.consumer_name
3242
return comparison if comparison != 0
33-
provider_name <=> other.provider_name
43+
comparison =provider_name <=> other.provider_name
3444
end
3545

3646
def to_hash
@@ -49,6 +59,22 @@ def pacticipant_names
4959
def to_s
5060
"Relationship between #{consumer_name} (id=#{consumer_id}) and #{provider_name} (id=#{provider_id})"
5161
end
62+
63+
def involves_consumer_with_id?(consumer_id)
64+
self.consumer_id == consumer_id
65+
end
66+
67+
def involves_consumer_with_names?(consumer_names)
68+
consumer_names.include?(self.consumer_name)
69+
end
70+
71+
def involves_provider_with_name?(provider_name)
72+
self.provider_name == provider_name
73+
end
74+
75+
def involves_consumer_with_name?(consumer_name)
76+
self.consumer_name == consumer_name
77+
end
5278
end
5379
end
5480
end

lib/pact_broker/matrix/repository.rb

+85-49
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'pact_broker/matrix/query_results'
66
require 'pact_broker/matrix/integration'
77
require 'pact_broker/matrix/query_results_with_deployment_status_summary'
8+
require 'pact_broker/matrix/resolved_selector'
89

910
module PactBroker
1011
module Matrix
@@ -48,18 +49,18 @@ def find_ids_for_pacticipant_names params
4849
end
4950

5051
# Return the latest matrix row (pact/verification) for each consumer_version_number/provider_version_number
51-
def find selectors, options = {}
52-
resolved_selectors = resolve_selectors(selectors, options)
52+
def find specified_selectors, options = {}
53+
resolved_selectors = resolve_selectors(specified_selectors, options)
5354
lines = query_matrix(resolved_selectors, options)
54-
lines = apply_latestby(options, selectors, lines)
55+
lines = apply_latestby(options, specified_selectors, lines)
5556

5657
# This needs to be done after the latestby, so can't be done in the db unless
5758
# the latestby logic is moved to the db
5859
if options.key?(:success)
5960
lines = lines.select{ |l| options[:success].include?(l.success) }
6061
end
6162

62-
QueryResults.new(lines.sort, selectors, options, resolved_selectors)
63+
QueryResults.new(lines.sort, specified_selectors, options, resolved_selectors)
6364
end
6465

6566
def find_for_consumer_and_provider pacticipant_1_name, pacticipant_2_name
@@ -73,13 +74,19 @@ def find_compatible_pacticipant_versions selectors
7374
end
7475

7576
def find_integrations(pacticipant_names)
76-
selectors = pacticipant_names.collect{ | pacticipant_name | add_ids(pacticipant_name: pacticipant_name) }
77+
selectors = pacticipant_names.collect{ | pacticipant_name | add_ids_to_selector(pacticipant_name: pacticipant_name) }
7778
Row
7879
.select(:consumer_name, :consumer_id, :provider_name, :provider_id)
7980
.matching_selectors(selectors)
8081
.distinct
8182
.all
82-
.collect{ |row | Integration.from_hash(row.to_hash) }.uniq
83+
.collect do |row |
84+
row.to_hash
85+
end
86+
.uniq
87+
.collect do | hash |
88+
Integration.from_hash(hash.merge(required: pacticipant_names.include?(hash[:consumer_name])))
89+
end
8390
end
8491

8592
private
@@ -127,85 +134,102 @@ def view_for(options)
127134
Row
128135
end
129136

130-
def resolve_selectors(selectors, options)
131-
resolved_selectors = look_up_version_numbers(selectors, options)
137+
def resolve_selectors(specified_selectors, options)
138+
resolved_specified_selectors = resolve_versions_and_add_ids(specified_selectors, options)
132139
if options[:latest] || options[:tag]
133-
apply_latest_and_tag_to_inferred_selectors(resolved_selectors, options)
140+
add_inferred_selectors(resolved_specified_selectors, options)
134141
else
135-
resolved_selectors
142+
resolved_specified_selectors
136143
end
137144
end
138145

139146
# Find the version number for selectors with the latest and/or tag specified
140-
def look_up_version_numbers(selectors, options)
147+
def resolve_versions_and_add_ids(selectors, options, required = true)
141148
selectors.collect do | selector |
142-
if selector[:tag] && selector[:latest]
143-
version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag])
144-
raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") unless version
145-
# validation in resource should ensure we always have a version
146-
{
147-
pacticipant_name: selector[:pacticipant_name],
148-
pacticipant_version_number: version.number
149-
}
150-
elsif selector[:latest]
151-
version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name])
152-
raise Error.new("No version of #{selector[:pacticipant_name]} found") unless version
153-
{
154-
pacticipant_name: selector[:pacticipant_name],
155-
pacticipant_version_number: version.number
156-
}
157-
elsif selector[:tag]
158-
# validation in resource should ensure we always have at least one version
159-
versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag])
160-
raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") unless versions.any?
149+
pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name])
150+
151+
versions = find_versions_for_selector(selector, required)
152+
153+
if versions
161154
versions.collect do | version |
162-
{
163-
pacticipant_name: selector[:pacticipant_name],
164-
pacticipant_version_number: version.number
165-
}
155+
if version
156+
selector_for_version(pacticipant, version)
157+
else
158+
selector_for_non_existing_version(pacticipant)
159+
end
166160
end
167161
else
168-
selector.dup
162+
selector_without_version(pacticipant)
169163
end
170-
end.flatten.compact.collect do | selector |
171-
add_ids(selector)
164+
end.flatten
165+
end
166+
167+
def find_versions_for_selector(selector, required)
168+
if selector[:tag] && selector[:latest]
169+
version = version_repository.find_by_pacticipant_name_and_latest_tag(selector[:pacticipant_name], selector[:tag])
170+
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && !version
171+
[version]
172+
elsif selector[:latest]
173+
version = version_repository.find_latest_by_pacticpant_name(selector[:pacticipant_name])
174+
# raise Error.new("No version of #{selector[:pacticipant_name]} found") if required && !version
175+
[version]
176+
elsif selector[:tag]
177+
versions = version_repository.find_by_pacticipant_name_and_tag(selector[:pacticipant_name], selector[:tag])
178+
# raise Error.new("No version of #{selector[:pacticipant_name]} found with tag #{selector[:tag]}") if required && versions.empty?
179+
versions.any? ? versions : [nil]
180+
elsif selector[:pacticipant_version_number]
181+
version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number])
182+
# raise Error.new("No version #{selector[:pacticipant_version_number]} of #{selector[:pacticipant_name]} found") if required && !version
183+
[version]
184+
else
185+
nil
172186
end
173187
end
174188

175-
def add_ids(selector)
189+
def add_ids_to_selector(selector)
176190
if selector[:pacticipant_name]
177191
pacticipant = PactBroker::Domain::Pacticipant.find(name: selector[:pacticipant_name])
178192
selector[:pacticipant_id] = pacticipant ? pacticipant.id : nil
179193
end
180194

181-
if selector[:pacticipant_name] && selector[:pacticipant_version_number]
195+
if selector[:pacticipant_name] && selector[:pacticipant_version_number] && !selector[:pacticipant_version_id]
182196
version = version_repository.find_by_pacticipant_name_and_number(selector[:pacticipant_name], selector[:pacticipant_version_number])
183197
selector[:pacticipant_version_id] = version ? version.id : nil
184198
end
185199

186-
if selector[:pacticipant_version_number].nil?
187-
selector[:pacticipant_version_id] = nil
200+
if !selector.key?(:pacticipant_version_id)
201+
selector[:pacticipant_version_id] = nil
188202
end
189203
selector
190204
end
191205

192206
# eg. when checking to see if Foo version 2 can be deployed to prod,
193207
# need to look up all the 'partner' pacticipants, and determine their latest prod versions
194-
def apply_latest_and_tag_to_inferred_selectors(selectors, options)
195-
all_pacticipant_names = all_pacticipant_names_in_specified_matrix(selectors)
196-
specified_names = selectors.collect{ |s| s[:pacticipant_name] }
197-
inferred_names = all_pacticipant_names - specified_names
208+
def add_inferred_selectors(resolved_specified_selectors, options)
209+
integrations = find_integrations(resolved_specified_selectors.collect{|s| s[:pacticipant_name]})
210+
all_pacticipant_names = integrations.collect(&:pacticipant_names).flatten.uniq
211+
specified_names = resolved_specified_selectors.collect{ |s| s[:pacticipant_name] }
212+
inferred_pacticipant_names = all_pacticipant_names - specified_names
213+
# Inferred providers are required for a consumer to be deployed
214+
required_inferred_pacticipant_names = inferred_pacticipant_names.select{ | n | integrations.any?{ |i| i.involves_provider_with_name?(n) } }
215+
# Inferred consumers are NOT required for a provider to be deployed
216+
optional_inferred_pacticipant_names = inferred_pacticipant_names - required_inferred_pacticipant_names
217+
218+
resolved_specified_selectors +
219+
build_inferred_selectors(required_inferred_pacticipant_names, options, true) +
220+
build_inferred_selectors(optional_inferred_pacticipant_names, options, false)
221+
end
198222

199-
inferred_selectors = inferred_names.collect do | pacticipant_name |
223+
def build_inferred_selectors(inferred_pacticipant_names, options, required)
224+
selectors = inferred_pacticipant_names.collect do | pacticipant_name |
200225
selector = {
201-
pacticipant_name: pacticipant_name,
226+
pacticipant_name: pacticipant_name
202227
}
203228
selector[:tag] = options[:tag] if options[:tag]
204229
selector[:latest] = options[:latest] if options[:latest]
205230
selector
206231
end
207-
208-
selectors + look_up_version_numbers(inferred_selectors, options)
232+
resolve_versions_and_add_ids(selectors, options, required)
209233
end
210234

211235
def all_pacticipant_names_in_specified_matrix(selectors)
@@ -214,6 +238,18 @@ def all_pacticipant_names_in_specified_matrix(selectors)
214238
.flatten
215239
.uniq
216240
end
241+
242+
def selector_for_non_existing_version(pacticipant)
243+
ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant)
244+
end
245+
246+
def selector_for_version(pacticipant, version)
247+
ResolvedSelector.for_pacticipant_and_version(pacticipant, version)
248+
end
249+
250+
def selector_without_version(pacticipant)
251+
ResolvedSelector.for_pacticipant(pacticipant)
252+
end
217253
end
218254
end
219255
end

0 commit comments

Comments
 (0)