pact_broker-client 1.34.0 → 1.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) 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 +39 -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 +112 -34
  11. data/lib/pact_broker/client/cli/can_i_deploy_long_desc.txt +18 -9
  12. data/lib/pact_broker/client/cli/create_or_update_webhook_long_desc.txt +3 -1
  13. data/lib/pact_broker/client/cli/create_webhook_long_desc.txt +2 -0
  14. data/lib/pact_broker/client/cli/custom_thor.rb +11 -17
  15. data/lib/pact_broker/client/git.rb +43 -22
  16. data/lib/pact_broker/client/hal/entity.rb +44 -3
  17. data/lib/pact_broker/client/hal/http_client.rb +5 -1
  18. data/lib/pact_broker/client/hal/links.rb +39 -0
  19. data/lib/pact_broker/client/hal_client_methods.rb +11 -0
  20. data/lib/pact_broker/client/hash_refinements.rb +19 -0
  21. data/lib/pact_broker/client/matrix.rb +2 -1
  22. data/lib/pact_broker/client/matrix/text_formatter.rb +46 -11
  23. data/lib/pact_broker/client/publish_pacts.rb +85 -14
  24. data/lib/pact_broker/client/tasks/publication_task.rb +37 -6
  25. data/lib/pact_broker/client/version.rb +1 -1
  26. data/lib/pact_broker/client/versions/record_deployment.rb +109 -0
  27. data/lib/pact_broker/client/versions/record_undeployment.rb +125 -0
  28. data/pact-broker-client.gemspec +2 -1
  29. data/script/publish-pact.sh +7 -1
  30. data/script/record-deployment.sh +4 -0
  31. data/script/trigger-release.sh +1 -1
  32. data/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb +51 -6
  33. data/spec/lib/pact_broker/client/cli/broker_publish_spec.rb +108 -12
  34. data/spec/lib/pact_broker/client/cli/custom_thor_spec.rb +1 -7
  35. data/spec/lib/pact_broker/client/git_spec.rb +39 -2
  36. data/spec/lib/pact_broker/client/hal/entity_spec.rb +4 -3
  37. data/spec/lib/pact_broker/client/matrix/text_formatter_spec.rb +29 -4
  38. data/spec/lib/pact_broker/client/publish_pacts_spec.rb +99 -6
  39. data/spec/lib/pact_broker/client/tasks/publication_task_spec.rb +88 -10
  40. data/spec/lib/pact_broker/client/versions/describe_spec.rb +0 -1
  41. data/spec/lib/pact_broker/client/versions/record_deployment_spec.rb +82 -0
  42. data/spec/pacts/pact_broker_client-pact_broker.json +335 -8
  43. data/spec/service_providers/pact_broker_client_create_version_spec.rb +89 -0
  44. data/spec/service_providers/pact_broker_client_matrix_spec.rb +4 -0
  45. data/spec/service_providers/pact_broker_client_versions_spec.rb +1 -2
  46. data/spec/service_providers/record_deployment_spec.rb +219 -0
  47. data/spec/spec_helper.rb +2 -0
  48. data/spec/support/matrix.json +6 -1
  49. data/spec/support/matrix.txt +3 -3
  50. data/spec/support/matrix_error.txt +3 -3
  51. data/spec/support/matrix_with_results.txt +10 -0
  52. data/tasks/pact.rake +2 -0
  53. metadata +36 -8
  54. data/.travis.yml +0 -11
@@ -1,6 +1,8 @@
1
1
  require 'erb'
2
2
  require 'delegate'
3
+ require 'pact_broker/client/error'
3
4
  require 'pact_broker/client/hal/link'
5
+ require 'pact_broker/client/hal/links'
4
6
 
5
7
  module PactBroker
6
8
  module Client
@@ -51,10 +53,43 @@ module PactBroker
51
53
  end
52
54
  end
53
55
 
56
+ def _links(key)
57
+ if @links[key] && @links[key].is_a?(Array)
58
+ link_collection = @links[key].collect do | hash |
59
+ Link.new(hash, @client)
60
+ end
61
+ Links.new(@href, key, link_collection)
62
+ elsif @links[key].is_a?(Hash)
63
+ Links.new(@href, key, [Link.new(@links[key], @client)])
64
+ else
65
+ nil
66
+ end
67
+ end
68
+
54
69
  def _link!(key)
55
70
  _link(key) or raise RelationNotFoundError.new("Could not find relation '#{key}' in resource at #{@href}")
56
71
  end
57
72
 
73
+ def _links!(key)
74
+ _links(key) or raise RelationNotFoundError.new("Could not find relation '#{key}' in resource at #{@href}")
75
+ end
76
+
77
+ def embedded_entity
78
+ embedded_ent = yield @data["_embedded"]
79
+ Entity.new(embedded_ent["_links"]["self"]["href"], embedded_ent, @client, response)
80
+ end
81
+
82
+ def embedded_entities(key = nil)
83
+ embedded_ents = if key
84
+ @data["_embedded"][key]
85
+ else
86
+ yield @data["_embedded"]
87
+ end
88
+ embedded_ents.collect do | embedded_ent |
89
+ Entity.new(embedded_ent["_links"]["self"]["href"], embedded_ent, @client, response)
90
+ end
91
+ end
92
+
58
93
  def success?
59
94
  true
60
95
  end
@@ -85,7 +120,7 @@ module PactBroker
85
120
  @data.key?(method_name) || @links.key?(method_name)
86
121
  end
87
122
 
88
- def assert_success!
123
+ def assert_success!(_ignored = nil)
89
124
  self
90
125
  end
91
126
  end
@@ -107,8 +142,14 @@ module PactBroker
107
142
  false
108
143
  end
109
144
 
110
- def assert_success!
111
- raise ErrorResponseReturned.new("Error retrieving #{@href} status=#{response ? response.code: nil} #{response ? response.raw_body : ''}")
145
+ def assert_success!(messages = {})
146
+ default_message = "Error retrieving #{@href} status=#{response ? response.status: nil} #{response ? response.raw_body : ''}".strip
147
+ message = if response && messages[response.status]
148
+ (messages[response.status] || "") + " (#{default_message})"
149
+ else
150
+ default_message
151
+ end
152
+ raise ErrorResponseReturned.new(message)
112
153
  end
113
154
  end
114
155
  end
@@ -17,7 +17,7 @@ module PactBroker
17
17
  end
18
18
 
19
19
  def get href, params = {}, headers = {}
20
- query = params.collect{ |(key, value)| "#{CGI::escape(key)}=#{CGI::escape(value)}" }.join("&")
20
+ query = params.collect{ |(key, value)| "#{CGI::escape(key.to_s)}=#{CGI::escape(value)}" }.join("&")
21
21
  uri = URI(href)
22
22
  uri.query = query
23
23
  perform_request(create_request(uri, 'Get', nil, headers), uri)
@@ -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,60 @@ 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.respond_to?(:strip) ? consumer_version.strip : consumer_version
20
- @tags = tags ? tags.collect{ |tag| tag.respond_to?(:strip) ? tag.strip : tag } : 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
+
40
61
  def merge_on_server?
41
62
  pact_broker_client_options[:write] == :merge
42
63
  end
@@ -70,8 +91,58 @@ module PactBroker
70
91
  end
71
92
  end
72
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
+
73
144
  def apply_tags
74
- return true if tags.nil? || tags.empty?
145
+ return true if tags.empty?
75
146
  tags.all? do | tag |
76
147
  tag_consumer_version tag
77
148
  end
@@ -81,8 +152,8 @@ module PactBroker
81
152
  versions = pact_broker_client.pacticipants.versions
82
153
  Retry.while_error do
83
154
  consumer_names.collect do | consumer_name |
84
- versions.tag(pacticipant: consumer_name, version: consumer_version, tag: tag)
85
- $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}"
86
157
  true
87
158
  end
88
159
  end
@@ -94,22 +165,22 @@ module PactBroker
94
165
  def publish_pact_contents(pact)
95
166
  Retry.while_error do
96
167
  pacts = pact_broker_client.pacticipants.versions.pacts
97
- if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version)
168
+ if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version_number)
98
169
  if merge_on_server?
99
170
  $stdout.puts "A pact for this consumer version is already published. Merging contents."
100
171
  else
101
- $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.)")
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)")
102
173
  end
103
174
  end
104
175
 
105
- 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)
106
177
  $stdout.puts "The latest version of this pact can be accessed at the following URL:\n#{latest_pact_url}"
107
178
  true
108
179
  end
109
180
  end
110
181
 
111
182
  def validate
112
- 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)
113
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)
114
185
  raise PactBroker::Client::Error.new("No pact files found") unless (pact_file_paths && pact_file_paths.any?)
115
186
  end