pact_broker-client 1.32.0 → 1.37.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release_gem.yml +1 -0
  3. data/.github/workflows/test.yml +23 -0
  4. data/CHANGELOG.md +48 -0
  5. data/README.md +15 -3
  6. data/Rakefile +2 -0
  7. data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +331 -8
  8. data/lib/pact_broker/client.rb +1 -1
  9. data/lib/pact_broker/client/backports.rb +13 -0
  10. data/lib/pact_broker/client/cli/broker.rb +73 -28
  11. data/lib/pact_broker/client/cli/can_i_deploy_long_desc.txt +18 -9
  12. data/lib/pact_broker/client/cli/custom_thor.rb +9 -12
  13. data/lib/pact_broker/client/git.rb +43 -22
  14. data/lib/pact_broker/client/hal/entity.rb +27 -3
  15. data/lib/pact_broker/client/hal/http_client.rb +4 -0
  16. data/lib/pact_broker/client/hal/links.rb +39 -0
  17. data/lib/pact_broker/client/hal_client_methods.rb +11 -0
  18. data/lib/pact_broker/client/hash_refinements.rb +19 -0
  19. data/lib/pact_broker/client/matrix.rb +2 -1
  20. data/lib/pact_broker/client/matrix/text_formatter.rb +46 -11
  21. data/lib/pact_broker/client/publish_pacts.rb +93 -14
  22. data/lib/pact_broker/client/tasks/publication_task.rb +37 -6
  23. data/lib/pact_broker/client/version.rb +1 -1
  24. data/lib/pact_broker/client/versions/record_deployment.rb +109 -0
  25. data/pact-broker-client.gemspec +2 -0
  26. data/script/publish-pact.sh +7 -1
  27. data/script/trigger-release.sh +1 -1
  28. data/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb +13 -2
  29. data/spec/lib/pact_broker/client/cli/broker_publish_spec.rb +108 -12
  30. data/spec/lib/pact_broker/client/git_spec.rb +39 -2
  31. data/spec/lib/pact_broker/client/hal/entity_spec.rb +4 -3
  32. data/spec/lib/pact_broker/client/matrix/text_formatter_spec.rb +29 -4
  33. data/spec/lib/pact_broker/client/publish_pacts_spec.rb +119 -7
  34. data/spec/lib/pact_broker/client/tasks/publication_task_spec.rb +88 -10
  35. data/spec/lib/pact_broker/client/versions/describe_spec.rb +0 -1
  36. data/spec/lib/pact_broker/client/versions/record_deployment_spec.rb +82 -0
  37. data/spec/pacts/pact_broker_client-pact_broker.json +335 -8
  38. data/spec/service_providers/pact_broker_client_create_version_spec.rb +89 -0
  39. data/spec/service_providers/pact_broker_client_matrix_spec.rb +4 -0
  40. data/spec/service_providers/pact_broker_client_versions_spec.rb +1 -2
  41. data/spec/service_providers/record_deployment_spec.rb +219 -0
  42. data/spec/support/matrix.json +6 -1
  43. data/spec/support/matrix.txt +3 -3
  44. data/spec/support/matrix_error.txt +3 -3
  45. data/spec/support/matrix_with_results.txt +10 -0
  46. data/tasks/pact.rake +2 -0
  47. metadata +44 -4
  48. data/.travis.yml +0 -11
@@ -83,6 +83,10 @@ module PactBroker
83
83
  end
84
84
  end
85
85
 
86
+ def headers
87
+ __getobj__().to_hash
88
+ end
89
+
86
90
  def raw_body
87
91
  __getobj__().body
88
92
  end
@@ -0,0 +1,39 @@
1
+ require 'uri'
2
+ require 'delegate'
3
+
4
+ module PactBroker
5
+ module Client
6
+ module Hal
7
+ class Links
8
+ def initialize(href, key, links)
9
+ @href = href
10
+ @key = key
11
+ @links = links
12
+ end
13
+
14
+ def names
15
+ @names ||= links.collect(&:name).compact.uniq
16
+ end
17
+
18
+ def find!(name, not_found_message = nil)
19
+ link = find(name)
20
+ if link
21
+ link
22
+ else
23
+ message = not_found_message || "Could not find relation '#{key}' with name '#{name}' in resource at #{href}."
24
+ available_options = names.any? ? names.join(", ") : "<none found>"
25
+ raise RelationNotFoundError.new(message.chomp(".") + ". Available options: #{available_options}")
26
+ end
27
+ end
28
+
29
+ def find(name)
30
+ links.find{ | link | link.name == name }
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :links, :key, :href
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,4 +1,5 @@
1
1
  require 'pact_broker/client/hal'
2
+ require 'pact_broker/client/retry'
2
3
 
3
4
  module PactBroker
4
5
  module Client
@@ -10,6 +11,16 @@ module PactBroker
10
11
  def create_http_client(pact_broker_client_options)
11
12
  PactBroker::Client::Hal::HttpClient.new(pact_broker_client_options.merge(pact_broker_client_options[:basic_auth] || {}))
12
13
  end
14
+
15
+ def index_entry_point
16
+ @index_entry_point ||= create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
17
+ end
18
+
19
+ def index_resource
20
+ @index_resource ||= Retry.while_error do
21
+ index_entry_point.get!
22
+ end
23
+ end
13
24
  end
14
25
  end
15
26
  end
@@ -0,0 +1,19 @@
1
+ module PactBroker
2
+ module Client
3
+ module HashRefinements
4
+ refine Hash do
5
+ def compact
6
+ h = {}
7
+ each do |key, value|
8
+ h[key] = value unless value == nil
9
+ end
10
+ h
11
+ end unless Hash.method_defined? :compact
12
+
13
+ def compact!
14
+ reject! {|_key, value| value == nil}
15
+ end unless Hash.method_defined? :compact!
16
+ end
17
+ end
18
+ end
19
+ end
@@ -53,10 +53,11 @@ module PactBroker
53
53
  opts[:success] = [*options[:success]]
54
54
  end
55
55
  opts[:limit] = options[:limit] if options[:limit]
56
+ opts[:environment] = options[:to_environment] if options[:to_environment]
56
57
  if options[:to_tag]
57
58
  opts[:latest] = 'true'
58
59
  opts[:tag] = options[:to_tag]
59
- elsif selectors.size == 1
60
+ elsif selectors.size == 1 && !options[:to_environment]
60
61
  opts[:latest] = 'true'
61
62
  end
62
63
  opts
@@ -1,41 +1,76 @@
1
1
  require 'table_print'
2
+ require 'dig_rb'
3
+ require 'pact_broker/client/hash_refinements'
2
4
 
3
5
  module PactBroker
4
6
  module Client
5
7
  class Matrix
6
8
  class TextFormatter
9
+ using PactBroker::Client::HashRefinements
7
10
 
8
- Line = Struct.new(:consumer, :consumer_version, :provider, :provider_version, :success)
11
+ Line = Struct.new(:consumer, :consumer_version, :provider, :provider_version, :success, :ref)
9
12
 
10
13
  OPTIONS = [
11
14
  { consumer: {} },
12
15
  { consumer_version: {display_name: 'C.VERSION'} },
13
16
  { provider: {} },
14
17
  { provider_version: {display_name: 'P.VERSION'} },
15
- { success: {display_name: 'SUCCESS?'} }
18
+ { success: {display_name: 'SUCCESS?'} },
19
+ { ref: { display_name: 'RESULT#' }}
16
20
  ]
17
21
 
18
22
  def self.call(matrix)
19
23
  matrix_rows = matrix[:matrix]
20
24
  return "" if matrix_rows.size == 0
21
- data = matrix_rows.collect do | line |
25
+ verification_result_number = 0
26
+ data = matrix_rows.each_with_index.collect do | line |
27
+ has_verification_result_url = lookup(line, nil, :verificationResult, :_links, :self, :href)
28
+ if has_verification_result_url
29
+ verification_result_number += 1
30
+ end
22
31
  Line.new(
23
- lookup(line, :consumer, :name),
24
- lookup(line, :consumer, :version, :number),
25
- lookup(line, :provider, :name),
26
- lookup(line, :provider, :version, :number),
27
- lookup(line, :verificationResult, :success).to_s
32
+ lookup(line, "???", :consumer, :name),
33
+ lookup(line, "???", :consumer, :version, :number),
34
+ lookup(line, "???", :provider, :name) ,
35
+ lookup(line, "???", :provider, :version, :number),
36
+ (lookup(line, "???", :verificationResult, :success)).to_s,
37
+ has_verification_result_url ? verification_result_number : ""
28
38
  )
29
39
  end
30
40
 
31
41
  printer = TablePrint::Printer.new(data, OPTIONS)
32
- printer.table_print
42
+ printer.table_print + verification_result_urls_text(matrix)
33
43
  end
34
44
 
35
- def self.lookup line, *keys
45
+ def self.lookup line, default, *keys
36
46
  keys.reduce(line) { | line, key | line[key] }
37
47
  rescue NoMethodError
38
- "???"
48
+ default
49
+ end
50
+
51
+ def self.verification_results_urls_and_successes(matrix)
52
+ (matrix[:matrix] || []).collect do | row |
53
+ url = row.dig(:verificationResult, :_links, :self, :href)
54
+ if url
55
+ success = row.dig(:verificationResult, :success)
56
+ [url, success]
57
+ else
58
+ nil
59
+ end
60
+ end.compact
61
+ end
62
+
63
+ def self.verification_result_urls_text(matrix)
64
+ text = self.verification_results_urls_and_successes(matrix).each_with_index.collect do |(url, success), i|
65
+ status = success ? 'success' : 'failure'
66
+ "#{i+1}. #{url} (#{status})"
67
+ end.join("\n")
68
+
69
+ if text.size > 0
70
+ "\n\nVERIFICATION RESULTS\n--------------------\n#{text}"
71
+ else
72
+ text
73
+ end
39
74
  end
40
75
  end
41
76
  end
@@ -4,39 +4,64 @@ require 'pact_broker/client/retry'
4
4
  require 'pact_broker/client/pact_file'
5
5
  require 'pact_broker/client/pact_hash'
6
6
  require 'pact_broker/client/merge_pacts'
7
+ require 'pact_broker/client/hal_client_methods'
8
+ require 'pact_broker/client/hash_refinements'
7
9
 
8
10
  module PactBroker
9
11
  module Client
10
12
  class PublishPacts
13
+ using PactBroker::Client::HashRefinements
14
+ include HalClientMethods
11
15
 
12
- def self.call(pact_broker_base_url, pact_file_paths, consumer_version, tags, pact_broker_client_options={})
13
- new(pact_broker_base_url, pact_file_paths, consumer_version, tags, pact_broker_client_options).call
16
+ def self.call(pact_broker_base_url, pact_file_paths, consumer_version_params, pact_broker_client_options={})
17
+ new(pact_broker_base_url, pact_file_paths, consumer_version_params, pact_broker_client_options).call
14
18
  end
15
19
 
16
- def initialize pact_broker_base_url, pact_file_paths, consumer_version, tags, pact_broker_client_options={}
20
+ def initialize pact_broker_base_url, pact_file_paths, consumer_version_params, pact_broker_client_options={}
17
21
  @pact_broker_base_url = pact_broker_base_url
18
22
  @pact_file_paths = pact_file_paths
19
- @consumer_version = consumer_version
20
- @tags = tags
23
+ @consumer_version_number = consumer_version_params[:number].respond_to?(:strip) ? consumer_version_params[:number].strip : consumer_version_params[:number]
24
+ @branch = consumer_version_params[:branch]
25
+ @build_url = consumer_version_params[:build_url]
26
+ @tags = consumer_version_params[:tags] ? consumer_version_params[:tags].collect{ |tag| tag.respond_to?(:strip) ? tag.strip : tag } : []
27
+ @version_required = consumer_version_params[:version_required]
21
28
  @pact_broker_client_options = pact_broker_client_options
22
29
  end
23
30
 
24
31
  def call
25
32
  validate
26
33
  $stdout.puts("")
27
- result = apply_tags && publish_pacts
34
+ result = create_consumer_versions && apply_tags && publish_pacts
28
35
  $stdout.puts("")
29
36
  result
30
37
  end
31
38
 
32
39
  private
33
40
 
34
- attr_reader :pact_broker_base_url, :pact_file_paths, :consumer_version, :tags, :pact_broker_client_options
41
+ attr_reader :pact_broker_base_url, :pact_file_paths, :consumer_version_number, :branch, :tags, :build_url, :pact_broker_client_options, :version_required
35
42
 
36
43
  def pact_broker_client
37
44
  @pact_broker_client ||= PactBroker::Client::PactBrokerClient.new(base_url: pact_broker_base_url, client_options: pact_broker_client_options)
38
45
  end
39
46
 
47
+ def index_entry_point
48
+ @index_entry_point ||= create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
49
+ end
50
+
51
+ def index_resource
52
+ @index_resource ||= Retry.while_error do
53
+ index_entry_point.get!
54
+ end
55
+ end
56
+
57
+ def can_create_version_with_branch?
58
+ @can_create_version_with_branch ||= index_resource.can?('pb:pacticipant-version')
59
+ end
60
+
61
+ def merge_on_server?
62
+ pact_broker_client_options[:write] == :merge
63
+ end
64
+
40
65
  def publish_pacts
41
66
  pact_files.group_by(&:pact_name).collect do | pact_name, pact_files |
42
67
  $stdout.puts "Merging #{pact_files.collect(&:path).join(", ")}" if pact_files.size > 1
@@ -66,8 +91,58 @@ module PactBroker
66
91
  end
67
92
  end
68
93
 
94
+ def create_consumer_versions
95
+ if create_versions?
96
+ consumer_names.collect do | consumer_name |
97
+ create_version(index_resource, consumer_name)
98
+ end
99
+ true
100
+ else
101
+ true
102
+ end
103
+ end
104
+
105
+ def create_versions?
106
+ if version_required
107
+ if can_create_version_with_branch?
108
+ true
109
+ else
110
+ raise PactBroker::Client::Error.new("This version of the Pact Broker does not support versions with branches or build URLs. Please upgrade your broker to 2.76.2 or later.")
111
+ end
112
+ elsif (branch || build_url) && can_create_version_with_branch?
113
+ true
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ def create_version(index_resource, consumer_name)
120
+ Retry.while_error do
121
+ version_resource = index_resource._link('pb:pacticipant-version').expand(version: consumer_version_number, pacticipant: consumer_name).put(version_body).assert_success!
122
+ message = if version_resource.response.status == 200
123
+ "Replaced version #{consumer_version_number} of #{consumer_name}"
124
+ else
125
+ "Created version #{consumer_version_number} of #{consumer_name}"
126
+ end
127
+
128
+ message = message + " (branch #{branch})" if branch
129
+ $stdout.puts message
130
+ if version_resource.response.status == 200
131
+ $stdout.puts ::Term::ANSIColor.yellow("Replacing the version resource is not recommended under normal circumstances and may indicate that you have not configured your Pact pipeline correctly (unless you are just re-running a build for a particular commit). For more information see https://docs.pact.io/versioning")
132
+ end
133
+ true
134
+ end
135
+ end
136
+
137
+ def version_body
138
+ {
139
+ branch: branch,
140
+ buildUrl: build_url
141
+ }.compact
142
+ end
143
+
69
144
  def apply_tags
70
- return true if tags.nil? || tags.empty?
145
+ return true if tags.empty?
71
146
  tags.all? do | tag |
72
147
  tag_consumer_version tag
73
148
  end
@@ -77,8 +152,8 @@ module PactBroker
77
152
  versions = pact_broker_client.pacticipants.versions
78
153
  Retry.while_error do
79
154
  consumer_names.collect do | consumer_name |
80
- versions.tag(pacticipant: consumer_name, version: consumer_version, tag: tag)
81
- $stdout.puts "Tagged version #{consumer_version} of #{consumer_name} as #{tag.inspect}"
155
+ versions.tag(pacticipant: consumer_name, version: consumer_version_number, tag: tag)
156
+ $stdout.puts "Tagged version #{consumer_version_number} of #{consumer_name} as #{tag.inspect}"
82
157
  true
83
158
  end
84
159
  end
@@ -90,18 +165,22 @@ module PactBroker
90
165
  def publish_pact_contents(pact)
91
166
  Retry.while_error do
92
167
  pacts = pact_broker_client.pacticipants.versions.pacts
93
- if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version)
94
- $stdout.puts ::Term::ANSIColor.yellow("A pact for this consumer version is already published. Overwriting. (Note: Overwriting pacts is not recommended as it can lead to race conditions. Best practice is to provide a unique consumer version number for each publication.)")
168
+ if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version_number)
169
+ if merge_on_server?
170
+ $stdout.puts "A pact for this consumer version is already published. Merging contents."
171
+ else
172
+ $stdout.puts ::Term::ANSIColor.yellow("A pact for this consumer version is already published. Overwriting. (Note: Overwriting pacts is not recommended as it can lead to race conditions. Best practice is to provide a unique consumer version number for each publication. For more information, see https://docs.pact.io/versioning)")
173
+ end
95
174
  end
96
175
 
97
- latest_pact_url = pacts.publish(pact_hash: pact, consumer_version: consumer_version)
176
+ latest_pact_url = pacts.publish(pact_hash: pact, consumer_version: consumer_version_number)
98
177
  $stdout.puts "The latest version of this pact can be accessed at the following URL:\n#{latest_pact_url}"
99
178
  true
100
179
  end
101
180
  end
102
181
 
103
182
  def validate
104
- raise PactBroker::Client::Error.new("Please specify the consumer_version") unless (consumer_version && consumer_version.to_s.strip.size > 0)
183
+ raise PactBroker::Client::Error.new("Please specify the consumer_version_number") unless (consumer_version_number && consumer_version_number.to_s.strip.size > 0)
105
184
  raise PactBroker::Client::Error.new("Please specify the pact_broker_base_url") unless (pact_broker_base_url && pact_broker_base_url.to_s.strip.size > 0)
106
185
  raise PactBroker::Client::Error.new("No pact files found") unless (pact_file_paths && pact_file_paths.any?)
107
186
  end
@@ -1,5 +1,6 @@
1
1
  require 'rake/tasklib'
2
2
  require 'pact_broker/client/git'
3
+ require 'pact_broker/client/hash_refinements'
3
4
 
4
5
  =begin
5
6
  require pact_broker/client/tasks
@@ -17,31 +18,52 @@ end
17
18
  module PactBroker
18
19
  module Client
19
20
  class PublicationTask < ::Rake::TaskLib
21
+ using PactBroker::Client::HashRefinements
20
22
 
21
23
  attr_accessor :pattern, :pact_broker_base_url, :consumer_version, :tag, :write_method, :tag_with_git_branch, :pact_broker_basic_auth, :pact_broker_token
24
+ attr_reader :auto_detect_version_properties, :branch, :build_url
22
25
  alias_method :tags=, :tag=
23
26
  alias_method :tags, :tag
24
27
 
25
28
  def initialize name = nil, &block
26
29
  @name = name
30
+ @auto_detect_version_properties = nil
31
+ @version_required = false
27
32
  @pattern = 'spec/pacts/*.json'
28
33
  @pact_broker_base_url = 'http://pact-broker'
29
34
  rake_task &block
30
35
  end
31
36
 
37
+ def auto_detect_version_properties= auto_detect_version_properties
38
+ @version_required = version_required || auto_detect_version_properties
39
+ @auto_detect_version_properties = auto_detect_version_properties
40
+ end
41
+
42
+ def branch= branch
43
+ @version_required = version_required || !!branch
44
+ @branch = branch
45
+ end
46
+
47
+ def build_url= build_url
48
+ @version_required = version_required || !!build_url
49
+ @build_url = build_url
50
+ end
51
+
32
52
  private
33
53
 
54
+ attr_reader :version_required
55
+
34
56
  def rake_task &block
35
57
  namespace :pact do
36
58
  desc "Publish pacts to pact broker"
37
59
  task task_name do
38
60
  block.call(self)
39
61
  require 'pact_broker/client/publish_pacts'
40
- pact_broker_client_options = {}
41
- .merge( pact_broker_basic_auth ? { basic_auth: pact_broker_basic_auth } : {} )
42
- .merge( write_method ? { write: write_method } : {} )
43
- .merge( pact_broker_token ? { token: pact_broker_token } : {} )
44
- success = PactBroker::Client::PublishPacts.new(pact_broker_base_url, FileList[pattern], consumer_version, all_tags, pact_broker_client_options).call
62
+ pact_broker_client_options = { write: write_method, token: pact_broker_token }
63
+ pact_broker_client_options[:basic_auth] = pact_broker_basic_auth if pact_broker_basic_auth && pact_broker_basic_auth.any?
64
+ pact_broker_client_options.compact!
65
+ consumer_version_params = { number: consumer_version, branch: the_branch, build_url: build_url, tags: all_tags, version_required: version_required }.compact
66
+ success = PactBroker::Client::PublishPacts.new(pact_broker_base_url, FileList[pattern], consumer_version_params, pact_broker_client_options).call
45
67
  raise "One or more pacts failed to be published" unless success
46
68
  end
47
69
  end
@@ -53,9 +75,18 @@ module PactBroker
53
75
 
54
76
  def all_tags
55
77
  t = [*tags]
56
- t << PactBroker::Client::Git.branch if tag_with_git_branch
78
+ t << PactBroker::Client::Git.branch(raise_error: true) if tag_with_git_branch
57
79
  t.compact.uniq
58
80
  end
81
+
82
+ def the_branch
83
+ if branch.nil? && auto_detect_version_properties != false
84
+ PactBroker::Client::Git.branch(raise_error: auto_detect_version_properties == true)
85
+ else
86
+ branch
87
+ end
88
+ end
89
+
59
90
  end
60
91
  end
61
92
  end