pact_broker-client 1.38.3 → 1.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +18 -0
  4. data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +100 -0
  5. data/example/scripts/publish-pact.sh +1 -1
  6. data/lib/pact_broker/client/cli/broker.rb +23 -7
  7. data/lib/pact_broker/client/cli/record_deployment_long_desc.txt +55 -0
  8. data/lib/pact_broker/client/hal/entity.rb +16 -0
  9. data/lib/pact_broker/client/hal/http_client.rb +6 -2
  10. data/lib/pact_broker/client/hal/link.rb +12 -0
  11. data/lib/pact_broker/client/hal/links.rb +15 -0
  12. data/lib/pact_broker/client/hal_client_methods.rb +8 -0
  13. data/lib/pact_broker/client/pacts.rb +0 -1
  14. data/lib/pact_broker/client/publish_pacts.rb +90 -128
  15. data/lib/pact_broker/client/publish_pacts_the_old_way.rb +194 -0
  16. data/lib/pact_broker/client/tasks/publication_task.rb +3 -3
  17. data/lib/pact_broker/client/version.rb +1 -1
  18. data/lib/pact_broker/client/versions/record_deployment.rb +4 -4
  19. data/lib/pact_broker/client/versions/record_undeployment.rb +45 -68
  20. data/script/publish-pact.sh +17 -4
  21. data/script/record-deployment.sh +1 -3
  22. data/script/record-undeployment.sh +4 -0
  23. data/spec/fixtures/foo-bar.json +31 -0
  24. data/spec/lib/pact_broker/client/cli/broker_publish_spec.rb +36 -7
  25. data/spec/lib/pact_broker/client/pacticipants/create_spec.rb +3 -0
  26. data/spec/lib/pact_broker/client/{publish_pacts_spec.rb → publish_pacts_the_old_way_spec.rb} +10 -9
  27. data/spec/lib/pact_broker/client/tasks/publication_task_spec.rb +18 -12
  28. data/spec/lib/pact_broker/client/versions/record_deployment_spec.rb +4 -4
  29. data/spec/pacts/pact_broker_client-pact_broker.json +106 -0
  30. data/spec/service_providers/pact_broker_client_create_version_spec.rb +4 -4
  31. data/spec/service_providers/publish_pacts_spec.rb +115 -0
  32. data/spec/service_providers/record_deployment_spec.rb +5 -5
  33. metadata +12 -5
@@ -21,6 +21,14 @@ module PactBroker
21
21
  index_entry_point.get!
22
22
  end
23
23
  end
24
+
25
+ def is_pactflow?
26
+ index_resource.response.headers.keys.any?{ | header_name | header_name.downcase.include?("pactflow") }
27
+ end
28
+
29
+ def pact_broker_name
30
+ is_pactflow? ? "Pactflow" : "the Pact Broker"
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -4,7 +4,6 @@ require 'pact_broker/client/pact_hash'
4
4
  module PactBroker
5
5
  module Client
6
6
  class Pacts < BaseClient
7
-
8
7
  def publish options
9
8
  consumer_version = options[:consumer_version]
10
9
  pact_hash = options[:pact_hash]
@@ -1,11 +1,7 @@
1
1
  require 'term/ansicolor'
2
- require 'pact_broker/client'
3
- require 'pact_broker/client/retry'
4
- require 'pact_broker/client/pact_file'
5
- require 'pact_broker/client/pact_hash'
6
- require 'pact_broker/client/merge_pacts'
7
2
  require 'pact_broker/client/hal_client_methods'
8
- require 'pact_broker/client/hash_refinements'
3
+ require 'base64'
4
+ require 'pact_broker/client/publish_pacts_the_old_way'
9
5
 
10
6
  module PactBroker
11
7
  module Client
@@ -13,170 +9,132 @@ module PactBroker
13
9
  using PactBroker::Client::HashRefinements
14
10
  include HalClientMethods
15
11
 
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
12
+ def self.call(pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options={})
13
+ new(pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options).call
18
14
  end
19
15
 
20
- def initialize pact_broker_base_url, pact_file_paths, consumer_version_params, pact_broker_client_options={}
16
+ def initialize pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options={}
21
17
  @pact_broker_base_url = pact_broker_base_url
22
18
  @pact_file_paths = pact_file_paths
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]
19
+ @consumer_version_params = consumer_version_params
20
+ @consumer_version_number = strip(consumer_version_params[:number])
21
+ @branch = strip(consumer_version_params[:branch])
22
+ @build_url = strip(consumer_version_params[:build_url])
23
+ @tags = consumer_version_params[:tags] ? consumer_version_params[:tags].collect{ |tag| strip(tag) } : []
24
+ @options = options
28
25
  @pact_broker_client_options = pact_broker_client_options
29
26
  end
30
27
 
31
28
  def call
32
29
  validate
33
- $stdout.puts("")
34
- result = create_consumer_versions && apply_tags && publish_pacts
35
- $stdout.puts("")
36
- result
30
+ if index_resource.can?("pb:publish-contracts")
31
+ publish_pacts
32
+ PactBroker::Client::CommandResult.new(success?, message)
33
+ else
34
+ PublishPactsTheOldWay.call(pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options)
35
+ end
37
36
  end
38
37
 
39
38
  private
40
39
 
41
- attr_reader :pact_broker_base_url, :pact_file_paths, :consumer_version_number, :branch, :tags, :build_url, :pact_broker_client_options, :version_required
42
-
43
- def pact_broker_client
44
- @pact_broker_client ||= PactBroker::Client::PactBrokerClient.new(base_url: pact_broker_base_url, client_options: pact_broker_client_options)
45
- end
46
-
47
- def index_entry_point
48
- @index_entry_point ||= create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
49
- end
40
+ attr_reader :pact_broker_base_url, :pact_file_paths, :consumer_version_params, :consumer_version_number, :branch, :tags, :build_url, :options, :pact_broker_client_options, :response_entities
50
41
 
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
42
+ def request_body_for(consumer_name)
43
+ {
44
+ pacticipantName: consumer_name,
45
+ pacticipantVersionNumber: consumer_version_number,
46
+ tags: tags,
47
+ branch: branch,
48
+ buildUrl: build_url,
49
+ contracts: contracts_for(consumer_name)
50
+ }.compact
63
51
  end
64
52
 
65
53
  def publish_pacts
66
- pact_files.group_by(&:pact_name).collect do | pact_name, pact_files |
67
- $stdout.puts "Merging #{pact_files.collect(&:path).join(", ")}" if pact_files.size > 1
68
- publish_pact(PactHash[merge_contents(pact_files)])
69
- end.all?
70
- end
71
-
72
- def merge_contents(pact_files)
73
- MergePacts.call(pact_files.collect(&:pact_hash))
74
- end
75
-
76
- def pact_files
77
- @pact_files ||= pact_file_paths.collect{ |pact_file_path| PactFile.new(pact_file_path) }
78
- end
79
-
80
- def consumer_names
81
- pact_files.collect(&:consumer_name).uniq
54
+ @response_entities = consumer_names.collect do | consumer_name |
55
+ index_resource._link("pb:publish-contracts").post(request_body_for(consumer_name))
56
+ end
82
57
  end
83
58
 
84
- def publish_pact pact
85
- begin
86
- $stdout.puts "Publishing #{pact.pact_name} to pact broker at #{pact_broker_base_url}"
87
- publish_pact_contents pact
88
- rescue => e
89
- $stderr.puts "Failed to publish #{pact.pact_name} due to error: #{e.class} - #{e}"
90
- false
91
- end
59
+ def success?
60
+ response_entities.all?(&:success?)
92
61
  end
93
62
 
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
63
+ def message
64
+ if options[:output] == "json"
65
+ response_entities.collect(&:response).collect(&:body).to_a.to_json
100
66
  else
101
- true
67
+ text_message
102
68
  end
103
69
  end
104
70
 
105
- def create_versions?
106
- if version_required
107
- if can_create_version_with_branch?
108
- true
71
+ def text_message
72
+ response_entities.flat_map do | response_entity |
73
+ if response_entity.success?
74
+ if response_entity.logs
75
+ response_entity.logs.collect do | log |
76
+ colorized_message(log)
77
+ end
78
+ else
79
+ "Successfully published pacts"
80
+ end
109
81
  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.")
82
+ ::Term::ANSIColor.red(response_entity.response.body.to_s)
111
83
  end
112
- elsif (branch || build_url) && can_create_version_with_branch?
113
- true
84
+ end.join("\n")
85
+ end
86
+
87
+ def colorized_message(log)
88
+ color = color_for_level(log["level"])
89
+ if color
90
+ ::Term::ANSIColor.send(color, log["message"])
114
91
  else
115
- false
92
+ log["message"]
116
93
  end
117
94
  end
118
95
 
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
96
+ def color_for_level(level)
97
+ case level
98
+ when "warn" then :yellow
99
+ when "error" then :red
100
+ when "info" then :green
101
+ else nil
102
+ end
103
+ end
127
104
 
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
105
+ def contracts_for(consumer_name)
106
+ pact_files_for(consumer_name).group_by(&:pact_name).values.collect do | pact_files |
107
+ $stderr.puts "Merging #{pact_files.collect(&:path).join(", ")}" if pact_files.size > 1
108
+ pact_hash = PactHash[merge_contents(pact_files)]
109
+ {
110
+ consumerName: pact_hash.consumer_name,
111
+ providerName: pact_hash.provider_name,
112
+ specification: "pact",
113
+ contentType: "application/json",
114
+ content: Base64.strict_encode64(pact_hash.to_json),
115
+ writeMode: write_mode
116
+ }
134
117
  end
135
118
  end
136
119
 
137
- def version_body
138
- {
139
- branch: branch,
140
- buildUrl: build_url
141
- }.compact
120
+ def merge_contents(pact_files)
121
+ MergePacts.call(pact_files.collect(&:pact_hash))
142
122
  end
143
123
 
144
- def apply_tags
145
- return true if tags.empty?
146
- tags.all? do | tag |
147
- tag_consumer_version tag
148
- end
124
+ def pact_files
125
+ @pact_files ||= pact_file_paths.collect{ |pact_file_path| PactFile.new(pact_file_path) }
149
126
  end
150
127
 
151
- def tag_consumer_version tag
152
- versions = pact_broker_client.pacticipants.versions
153
- Retry.while_error do
154
- consumer_names.collect do | consumer_name |
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}"
157
- true
158
- end
159
- end
160
- rescue => e
161
- $stderr.puts "Failed to tag version due to error: #{e.class} - #{e}"
162
- false
128
+ def pact_files_for(consumer_name)
129
+ pact_files.select{ | pact_file | pact_file.consumer_name == consumer_name }
163
130
  end
164
131
 
165
- def publish_pact_contents(pact)
166
- Retry.while_error do
167
- pacts = pact_broker_client.pacticipants.versions.pacts
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
174
- end
132
+ def consumer_names
133
+ pact_files.collect(&:consumer_name).uniq
134
+ end
175
135
 
176
- latest_pact_url = pacts.publish(pact_hash: pact, consumer_version: consumer_version_number)
177
- $stdout.puts "The latest version of this pact can be accessed at the following URL:\n#{latest_pact_url}"
178
- true
179
- end
136
+ def write_mode
137
+ options[:merge] ? "merge" : "overwrite"
180
138
  end
181
139
 
182
140
  def validate
@@ -184,6 +142,10 @@ module PactBroker
184
142
  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)
185
143
  raise PactBroker::Client::Error.new("No pact files found") unless (pact_file_paths && pact_file_paths.any?)
186
144
  end
145
+
146
+ def strip(maybe_string)
147
+ maybe_string.respond_to?(:strip) ? maybe_string.strip : maybe_string
148
+ end
187
149
  end
188
150
  end
189
151
  end
@@ -0,0 +1,194 @@
1
+ require 'term/ansicolor'
2
+ require 'pact_broker/client'
3
+ require 'pact_broker/client/retry'
4
+ require 'pact_broker/client/pact_file'
5
+ require 'pact_broker/client/pact_hash'
6
+ require 'pact_broker/client/merge_pacts'
7
+ require 'pact_broker/client/hal_client_methods'
8
+ require 'pact_broker/client/hash_refinements'
9
+ require 'pact_broker/client/command_result'
10
+
11
+ module PactBroker
12
+ module Client
13
+ class PublishPactsTheOldWay
14
+ using PactBroker::Client::HashRefinements
15
+ include HalClientMethods
16
+
17
+ def self.call(pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options={})
18
+ new(pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options).call
19
+ end
20
+
21
+ def initialize pact_broker_base_url, pact_file_paths, consumer_version_params, options, pact_broker_client_options={}
22
+ @pact_broker_base_url = pact_broker_base_url
23
+ @pact_file_paths = pact_file_paths
24
+ @consumer_version_number = consumer_version_params[:number].respond_to?(:strip) ? consumer_version_params[:number].strip : consumer_version_params[:number]
25
+ @branch = consumer_version_params[:branch]
26
+ @build_url = consumer_version_params[:build_url]
27
+ @tags = consumer_version_params[:tags] ? consumer_version_params[:tags].collect{ |tag| tag.respond_to?(:strip) ? tag.strip : tag } : []
28
+ @version_required = consumer_version_params[:version_required]
29
+ @pact_broker_client_options = pact_broker_client_options
30
+ end
31
+
32
+ def call
33
+ validate
34
+ $stdout.puts("")
35
+ result = create_consumer_versions && apply_tags && publish_pacts
36
+ $stdout.puts("")
37
+ if result
38
+ PactBroker::Client::CommandResult.new(true)
39
+ else
40
+ PactBroker::Client::CommandResult.new(false, "One or more pacts failed to be published")
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :pact_broker_base_url, :pact_file_paths, :consumer_version_number, :branch, :tags, :build_url, :pact_broker_client_options, :version_required
47
+
48
+ def pact_broker_client
49
+ @pact_broker_client ||= PactBroker::Client::PactBrokerClient.new(base_url: pact_broker_base_url, client_options: pact_broker_client_options)
50
+ end
51
+
52
+ def index_entry_point
53
+ @index_entry_point ||= create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
54
+ end
55
+
56
+ def index_resource
57
+ @index_resource ||= Retry.while_error do
58
+ index_entry_point.get!
59
+ end
60
+ end
61
+
62
+ def can_create_version_with_branch?
63
+ @can_create_version_with_branch ||= index_resource.can?('pb:pacticipant-version')
64
+ end
65
+
66
+ def merge_on_server?
67
+ pact_broker_client_options[:write] == :merge
68
+ end
69
+
70
+ def publish_pacts
71
+ pact_files.group_by(&:pact_name).collect do | pact_name, pact_files |
72
+ $stdout.puts "Merging #{pact_files.collect(&:path).join(", ")}" if pact_files.size > 1
73
+ publish_pact(PactHash[merge_contents(pact_files)])
74
+ end.all?
75
+ end
76
+
77
+ def merge_contents(pact_files)
78
+ MergePacts.call(pact_files.collect(&:pact_hash))
79
+ end
80
+
81
+ def pact_files
82
+ @pact_files ||= pact_file_paths.collect{ |pact_file_path| PactFile.new(pact_file_path) }
83
+ end
84
+
85
+ def consumer_names
86
+ pact_files.collect(&:consumer_name).uniq
87
+ end
88
+
89
+ def publish_pact pact
90
+ begin
91
+ $stdout.puts "Publishing #{pact.pact_name} to pact broker at #{pact_broker_base_url}"
92
+ publish_pact_contents pact
93
+ rescue => e
94
+ $stderr.puts "Failed to publish #{pact.pact_name} due to error: #{e.class} - #{e}"
95
+ false
96
+ end
97
+ end
98
+
99
+ def create_consumer_versions
100
+ if create_versions?
101
+ consumer_names.collect do | consumer_name |
102
+ create_version(index_resource, consumer_name)
103
+ end
104
+ true
105
+ else
106
+ true
107
+ end
108
+ end
109
+
110
+ def create_versions?
111
+ if version_required
112
+ if can_create_version_with_branch?
113
+ true
114
+ else
115
+ 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.")
116
+ end
117
+ elsif (branch || build_url) && can_create_version_with_branch?
118
+ true
119
+ else
120
+ false
121
+ end
122
+ end
123
+
124
+ def create_version(index_resource, consumer_name)
125
+ Retry.while_error do
126
+ version_resource = index_resource._link('pb:pacticipant-version').expand(version: consumer_version_number, pacticipant: consumer_name).put(version_body).assert_success!
127
+ message = if version_resource.response.status == 200
128
+ "Replaced version #{consumer_version_number} of #{consumer_name}"
129
+ else
130
+ "Created version #{consumer_version_number} of #{consumer_name}"
131
+ end
132
+
133
+ message = message + " (branch #{branch})" if branch
134
+ $stdout.puts message
135
+ if version_resource.response.status == 200
136
+ $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")
137
+ end
138
+ true
139
+ end
140
+ end
141
+
142
+ def version_body
143
+ {
144
+ branch: branch,
145
+ buildUrl: build_url
146
+ }.compact
147
+ end
148
+
149
+ def apply_tags
150
+ return true if tags.empty?
151
+ tags.all? do | tag |
152
+ tag_consumer_version tag
153
+ end
154
+ end
155
+
156
+ def tag_consumer_version tag
157
+ versions = pact_broker_client.pacticipants.versions
158
+ Retry.while_error do
159
+ consumer_names.collect do | consumer_name |
160
+ versions.tag(pacticipant: consumer_name, version: consumer_version_number, tag: tag)
161
+ $stdout.puts "Tagged version #{consumer_version_number} of #{consumer_name} as #{tag.inspect}"
162
+ true
163
+ end
164
+ end
165
+ rescue => e
166
+ $stderr.puts "Failed to tag version due to error: #{e.class} - #{e}"
167
+ false
168
+ end
169
+
170
+ def publish_pact_contents(pact)
171
+ Retry.while_error do
172
+ pacts = pact_broker_client.pacticipants.versions.pacts
173
+ if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version_number)
174
+ if merge_on_server?
175
+ $stdout.puts "A pact for this consumer version is already published. Merging contents."
176
+ else
177
+ $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)")
178
+ end
179
+ end
180
+
181
+ latest_pact_url = pacts.publish(pact_hash: pact, consumer_version: consumer_version_number)
182
+ $stdout.puts "The latest version of this pact can be accessed at the following URL:\n#{latest_pact_url}"
183
+ true
184
+ end
185
+ end
186
+
187
+ def validate
188
+ raise PactBroker::Client::Error.new("Please specify the consumer_version_number") unless (consumer_version_number && consumer_version_number.to_s.strip.size > 0)
189
+ 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)
190
+ raise PactBroker::Client::Error.new("No pact files found") unless (pact_file_paths && pact_file_paths.any?)
191
+ end
192
+ end
193
+ end
194
+ end