pact_broker-client 1.32.0 → 1.37.0

Sign up to get free protection for your applications and to get access to all the features.
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