pact_broker-client 1.25.0 → 1.27.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/trigger_pact_cli_release.yml +15 -0
  3. data/.github/workflows/trigger_pact_docs_update.yml +21 -0
  4. data/CHANGELOG.md +55 -0
  5. data/Gemfile +6 -1
  6. data/README.md +4 -3
  7. data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +234 -10
  8. data/lib/pact_broker/client/can_i_deploy.rb +6 -0
  9. data/lib/pact_broker/client/cli/broker.rb +33 -0
  10. data/lib/pact_broker/client/cli/custom_thor.rb +36 -14
  11. data/lib/pact_broker/client/git.rb +2 -1
  12. data/lib/pact_broker/client/hal/entity.rb +13 -4
  13. data/lib/pact_broker/client/hal/http_client.rb +6 -0
  14. data/lib/pact_broker/client/hal/link.rb +5 -1
  15. data/lib/pact_broker/client/hal_client_methods.rb +15 -0
  16. data/lib/pact_broker/client/matrix/resource.rb +5 -1
  17. data/lib/pact_broker/client/pacticipants/create.rb +57 -0
  18. data/lib/pact_broker/client/pacts/list_latest_versions.rb +65 -0
  19. data/lib/pact_broker/client/publish_pacts.rb +1 -1
  20. data/lib/pact_broker/client/version.rb +1 -1
  21. data/lib/pact_broker/client/webhooks/create.rb +7 -1
  22. data/lib/pact_broker/client/webhooks/test.rb +16 -0
  23. data/pact-broker-client.gemspec +3 -4
  24. data/spec/integration/can_i_deploy_spec.rb +3 -3
  25. data/spec/integration/create_version_tag_spec.rb +2 -3
  26. data/spec/lib/pact_broker/client/can_i_deploy_spec.rb +22 -4
  27. data/spec/lib/pact_broker/client/cli/broker_run_webhook_commands_spec.rb +2 -0
  28. data/spec/lib/pact_broker/client/cli/custom_thor_spec.rb +6 -0
  29. data/spec/lib/pact_broker/client/hal/entity_spec.rb +1 -0
  30. data/spec/lib/pact_broker/client/pacticipants/create_spec.rb +28 -0
  31. data/spec/lib/pact_broker/client/webhooks/create_spec.rb +22 -0
  32. data/spec/pacts/pact_broker_client-pact_broker.json +273 -10
  33. data/spec/service_providers/pact_broker_client_matrix_spec.rb +10 -10
  34. data/spec/service_providers/pact_helper.rb +29 -0
  35. data/spec/service_providers/pacticipants_create_spec.rb +118 -0
  36. data/spec/service_providers/webhooks_create_spec.rb +46 -17
  37. metadata +25 -30
@@ -87,6 +87,8 @@ module PactBroker
87
87
  if retry_while_unknown?
88
88
  check_if_retry_while_unknown_supported(matrix)
89
89
  if matrix.any_unknown?
90
+ results = matrix.unknown_count == 1 ? "result" : "results"
91
+ $stderr.puts "Waiting for #{matrix.unknown_count} verification #{results} to be published (maximum of #{wait_time} seconds)"
90
92
  matrix = Retry.until_truthy_or_max_times(retry_options) do
91
93
  fetch_matrix
92
94
  end
@@ -120,6 +122,10 @@ module PactBroker
120
122
  options[:retry_while_unknown]
121
123
  end
122
124
 
125
+ def wait_time
126
+ retry_interval * retry_tries
127
+ end
128
+
123
129
  def check_if_retry_while_unknown_supported(matrix)
124
130
  if !matrix.supports_unknown_count?
125
131
  raise PactBroker::Client::Error.new("This version of the Pact Broker does not provide a count of the unknown verification results. Please upgrade your Broker to >= v2.23.4")
@@ -130,6 +130,15 @@ module PactBroker
130
130
  run_webhook_commands webhook_url
131
131
  end
132
132
 
133
+ desc 'test-webhook', 'Test the execution of a webhook'
134
+ method_option :uuid, type: :string, required: true, desc: "Specify the uuid for the webhook"
135
+ shared_authentication_options_for_pact_broker
136
+ def test_webhook
137
+ require 'pact_broker/client/webhooks/test'
138
+ result = PactBroker::Client::Webhooks::Test.call(options, pact_broker_client_options)
139
+ $stdout.puts result.message
140
+ end
141
+
133
142
  ignored_and_hidden_potential_options_from_environment_variables
134
143
  desc 'generate-uuid', 'Generate a UUID for use when calling create-or-update-webhook'
135
144
  def generate_uuid
@@ -138,6 +147,29 @@ module PactBroker
138
147
  puts SecureRandom.uuid
139
148
  end
140
149
 
150
+ desc 'create-or-update-pacticipant', 'Create or update pacticipant by name'
151
+ method_option :name, type: :string, required: true, desc: "Pacticipant name"
152
+ method_option :repository_url, type: :string, required: false, desc: "The repository URL of the pacticipant"
153
+ shared_authentication_options_for_pact_broker
154
+ verbose_option
155
+ def create_or_update_pacticipant(*required_but_ignored)
156
+ raise ::Thor::RequiredArgumentMissingError, "Pacticipant name cannot be blank" if options.name.strip.size == 0
157
+ require 'pact_broker/client/pacticipants/create'
158
+ result = PactBroker::Client::Pacticipants2::Create.call({ name: options.name, repository_url: options.repository_url }, options.broker_base_url, pact_broker_client_options)
159
+ $stdout.puts result.message
160
+ exit(1) unless result.success
161
+ end
162
+
163
+ desc 'list-latest-pact-versions', 'List the latest pact for each integration'
164
+ shared_authentication_options_for_pact_broker
165
+ method_option :output, aliases: "-o", desc: "json or table", default: 'table'
166
+ def list_latest_pact_versions(*required_but_ignored)
167
+ require 'pact_broker/client/pacts/list_latest_versions'
168
+ result = PactBroker::Client::Pacts::ListLatestVersions.call(options.broker_base_url, options.output, pact_broker_client_options)
169
+ $stdout.puts result.message
170
+ exit(1) unless result.success
171
+ end
172
+
141
173
  ignored_and_hidden_potential_options_from_environment_variables
142
174
  desc 'version', "Show the pact_broker-client gem version"
143
175
  def version
@@ -249,6 +281,7 @@ module PactBroker
249
281
 
250
282
  {
251
283
  uuid: options.uuid,
284
+ description: options.description,
252
285
  http_method: options.request,
253
286
  url: webhook_url,
254
287
  headers: headers,
@@ -20,7 +20,7 @@ module PactBroker
20
20
  end
21
21
 
22
22
  def self.add_broker_config_from_environment_variables argv
23
- return argv if argv[0] == 'help'
23
+ return argv if argv[0] == 'help' || argv.empty?
24
24
 
25
25
  new_argv = add_option_from_environment_variable(argv, 'broker-base-url', 'b', 'PACT_BROKER_BASE_URL')
26
26
  new_argv = add_option_from_environment_variable(new_argv, 'broker-username', 'u', 'PACT_BROKER_USERNAME')
@@ -45,20 +45,31 @@ module PactBroker
45
45
  def self.turn_muliple_tag_options_into_array argv
46
46
  new_argv = []
47
47
  opt_name = nil
48
- argv.each_with_index do | arg, i |
49
- if arg.start_with?('-')
50
- opt_name = arg
51
- existing = new_argv.find { | a | a.first == opt_name }
52
- if !existing
53
- new_argv << [arg]
48
+ argv.each_with_index do | word, i |
49
+ if word.start_with?('-')
50
+ if word.include?('=')
51
+ opt_name, opt_value = word.split('=', 2)
52
+
53
+ existing = new_argv.find { | a | a.first == opt_name }
54
+ if existing
55
+ existing << opt_value
56
+ else
57
+ new_argv << [opt_name, opt_value]
58
+ end
59
+ else
60
+ opt_name = word
61
+ existing = new_argv.find { | a | a.first == opt_name }
62
+ if !existing
63
+ new_argv << [word]
64
+ end
54
65
  end
55
66
  else
56
67
  if opt_name
57
68
  existing = new_argv.find { | a | a.first == opt_name }
58
- existing << arg
69
+ existing << word
59
70
  opt_name = nil
60
71
  else
61
- new_argv << [arg]
72
+ new_argv << [word]
62
73
  end
63
74
  end
64
75
  end
@@ -76,17 +87,17 @@ module PactBroker
76
87
  end
77
88
 
78
89
  def self.shared_options_for_webhook_commands
79
- method_option :request, banner: "METHOD", aliases: "-X", desc: "HTTP method", required: true
80
- method_option :header, aliases: "-H", type: :array, desc: "Header"
81
- method_option :data, aliases: "-d", desc: "Data"
82
- method_option :user, aliases: "-u", desc: "Basic auth username and password eg. username:password"
90
+ method_option :request, banner: "METHOD", aliases: "-X", desc: "Webhook HTTP method", required: true
91
+ method_option :header, aliases: "-H", type: :array, desc: "Webhook Header"
92
+ method_option :data, aliases: "-d", desc: "Webhook payload (file or string)"
93
+ method_option :user, aliases: "-u", desc: "Webhook basic auth username and password eg. username:password"
83
94
  method_option :consumer, desc: "Consumer name"
84
95
  method_option :provider, desc: "Provider name"
85
96
  method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
86
97
  method_option :broker_username, desc: "Pact Broker basic auth username"
87
98
  method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
88
99
  method_option :broker_token, aliases: "-k", desc: "Pact Broker bearer token"
89
- method_option :description, desc: "The description of the webhook"
100
+ method_option :description, desc: "Wwebhook description"
90
101
  method_option :contract_content_changed, type: :boolean, desc: "Trigger this webhook when the pact content changes"
91
102
  method_option :contract_published, type: :boolean, desc: "Trigger this webhook when a pact is published"
92
103
  method_option :provider_verification_published, type: :boolean, desc: "Trigger this webhook when a provider verification result is published"
@@ -94,6 +105,17 @@ module PactBroker
94
105
  method_option :provider_verification_succeeded, type: :boolean, desc: "Trigger this webhook when a successful provider verification result is published"
95
106
  method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
96
107
  end
108
+
109
+ def self.shared_authentication_options_for_pact_broker
110
+ method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
111
+ method_option :broker_username, aliases: "-u", desc: "Pact Broker basic auth username"
112
+ method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
113
+ method_option :broker_token, aliases: "-k", desc: "Pact Broker bearer token"
114
+ end
115
+
116
+ def self.verbose_option
117
+ method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
118
+ end
97
119
  end
98
120
  end
99
121
  end
@@ -11,6 +11,7 @@ APPVEYOR_REPO_COMMIT APPVEYOR_REPO_BRANCH https://www.appveyor.com/docs/en
11
11
  CI_COMMIT_REF_NAME https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
12
12
  CI_BRANCH CI_COMMIT_ID https://documentation.codeship.com/pro/builds-and-configuration/environment-variables/
13
13
  bamboo.repository.git.branch https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html
14
+ BITBUCKET_BRANCH https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html
14
15
 
15
16
  =end
16
17
 
@@ -19,7 +20,7 @@ module PactBroker
19
20
  module Client
20
21
  module Git
21
22
  COMMAND = 'git name-rev --name-only HEAD'.freeze
22
- BRANCH_ENV_VAR_NAMES = %w{BUILDKITE_BRANCH CIRCLE_BRANCH TRAVIS_BRANCH GIT_BRANCH GIT_LOCAL_BRANCH APPVEYOR_REPO_BRANCH CI_COMMIT_REF_NAME}.freeze
23
+ BRANCH_ENV_VAR_NAMES = %w{BUILDKITE_BRANCH CIRCLE_BRANCH TRAVIS_BRANCH GIT_BRANCH GIT_LOCAL_BRANCH APPVEYOR_REPO_BRANCH CI_COMMIT_REF_NAME BITBUCKET_BRANCH}.freeze
23
24
 
24
25
  def self.branch
25
26
  find_branch_from_env_vars || branch_from_git_command
@@ -6,11 +6,9 @@ module PactBroker
6
6
  module Client
7
7
  module Hal
8
8
  class RelationNotFoundError < ::PactBroker::Client::Error; end
9
-
10
9
  class ErrorResponseReturned < ::PactBroker::Client::Error; end
11
10
 
12
11
  class Entity
13
-
14
12
  def initialize(href, data, http_client, response = nil)
15
13
  @href = href
16
14
  @data = data
@@ -31,6 +29,10 @@ module PactBroker
31
29
  _link(key).put(*args)
32
30
  end
33
31
 
32
+ def patch(key, *args)
33
+ _link(key).patch(*args)
34
+ end
35
+
34
36
  def can?(key)
35
37
  @links.key? key.to_s
36
38
  end
@@ -57,6 +59,10 @@ module PactBroker
57
59
  true
58
60
  end
59
61
 
62
+ def does_not_exist?
63
+ false
64
+ end
65
+
60
66
  def response
61
67
  @response
62
68
  end
@@ -71,7 +77,7 @@ module PactBroker
71
77
  elsif @links.key?(method_name)
72
78
  Link.new(@links[method_name], @client).run(*args)
73
79
  else
74
- super
80
+ nil
75
81
  end
76
82
  end
77
83
 
@@ -85,7 +91,6 @@ module PactBroker
85
91
  end
86
92
 
87
93
  class ErrorEntity < Entity
88
-
89
94
  def initialize(href, data, http_client, response = nil)
90
95
  @href = href
91
96
  @data = data
@@ -94,6 +99,10 @@ module PactBroker
94
99
  @response = response
95
100
  end
96
101
 
102
+ def does_not_exist?
103
+ response && response.status == 404
104
+ end
105
+
97
106
  def success?
98
107
  false
99
108
  end
@@ -1,6 +1,7 @@
1
1
  require 'pact_broker/client/retry'
2
2
  require 'pact_broker/client/hal/authorization_header_redactor'
3
3
  require 'net/http'
4
+ require 'json'
4
5
 
5
6
  module PactBroker
6
7
  module Client
@@ -32,6 +33,11 @@ module PactBroker
32
33
  perform_request(create_request(uri, 'Post', body, headers), uri)
33
34
  end
34
35
 
36
+ def patch href, body = nil, headers = {}
37
+ uri = URI(href)
38
+ perform_request(create_request(uri, 'Patch', body, headers), uri)
39
+ end
40
+
35
41
  def create_request uri, http_method, body = nil, headers = {}
36
42
  request = Net::HTTP.const_get(http_method).new(uri.request_uri)
37
43
  request['Content-Type'] = "application/json" if ['Post', 'Put', 'Patch'].include?(http_method)
@@ -53,6 +53,10 @@ module PactBroker
53
53
  wrap_response(href, @http_client.post(href, payload ? JSON.dump(payload) : nil, headers))
54
54
  end
55
55
 
56
+ def patch(payload = nil, headers = {})
57
+ wrap_response(href, @http_client.patch(href, payload ? JSON.dump(payload) : nil, headers))
58
+ end
59
+
56
60
  def expand(params)
57
61
  expanded_url = expand_url(params, href)
58
62
  new_attrs = @attrs.merge('href' => expanded_url)
@@ -62,7 +66,7 @@ module PactBroker
62
66
  private
63
67
 
64
68
  def wrap_response(href, http_response)
65
- require 'pact/hal/entity' # avoid circular reference
69
+ require 'pact_broker/client/hal/entity' # avoid circular reference
66
70
  if http_response.success?
67
71
  Entity.new(href, http_response.body, @http_client, http_response)
68
72
  else
@@ -0,0 +1,15 @@
1
+ require 'pact_broker/client/hal'
2
+
3
+ module PactBroker
4
+ module Client
5
+ module HalClientMethods
6
+ def create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
7
+ PactBroker::Client::Hal::EntryPoint.new(pact_broker_base_url, create_http_client(pact_broker_client_options))
8
+ end
9
+
10
+ def create_http_client(pact_broker_client_options)
11
+ PactBroker::Client::Hal::HttpClient.new(pact_broker_client_options.merge(pact_broker_client_options[:basic_auth] || {}))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -11,7 +11,7 @@ module PactBroker
11
11
 
12
12
  def any_unknown?
13
13
  if supports_unknown_count?
14
- self[:summary][:unknown] > 0
14
+ unknown_count > 0
15
15
  else
16
16
  false
17
17
  end
@@ -21,6 +21,10 @@ module PactBroker
21
21
  !!(self[:summary] && Integer === self[:summary][:unknown] )
22
22
  end
23
23
 
24
+ def unknown_count
25
+ supports_unknown_count? ? self[:summary][:unknown] : nil
26
+ end
27
+
24
28
  def reason
25
29
  self[:summary][:reason]
26
30
  end
@@ -0,0 +1,57 @@
1
+ require 'pact_broker/client/hal'
2
+ require 'json'
3
+ require 'pact_broker/client/command_result'
4
+ require 'pact_broker/client/hal_client_methods'
5
+
6
+ module PactBroker
7
+ module Client
8
+ module Pacticipants2
9
+ class Create
10
+
11
+ include HalClientMethods
12
+
13
+ def self.call(params, pact_broker_base_url, pact_broker_client_options)
14
+ new(params, pact_broker_base_url, pact_broker_client_options).call
15
+ end
16
+
17
+ def initialize(params, pact_broker_base_url, pact_broker_client_options)
18
+ @params = params
19
+ @index_entry_point = create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
20
+ @verbose = pact_broker_client_options[:verbose]
21
+ end
22
+
23
+ def call
24
+ pacticipant_entity = index_entity._link('pb:pacticipant').expand('pacticipant' => params[:name]).get
25
+ message = nil
26
+ response_entity = if pacticipant_entity.does_not_exist?
27
+ message = "Pacticipant \"#{params[:name]}\" created"
28
+ index_entity._link!('pb:pacticipants').post(pacticipant_resource_params)
29
+ else
30
+ message = "Pacticipant \"#{params[:name]}\" updated"
31
+ pacticipant_entity._link!('self').patch(pacticipant_resource_params)
32
+ end
33
+
34
+ response_entity.assert_success!
35
+ PactBroker::Client::CommandResult.new(true, message)
36
+ rescue StandardError => e
37
+ $stderr.puts("#{e.class} - #{e}\n#{e.backtrace.join("\n")}") if verbose
38
+ PactBroker::Client::CommandResult.new(false, "#{e.class} - #{e}")
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :index_entry_point, :params, :verbose
44
+
45
+ def index_entity
46
+ @index_entity ||= index_entry_point.get!
47
+ end
48
+
49
+ def pacticipant_resource_params
50
+ p = { name: params[:name] }
51
+ p[:repositoryUrl] = params[:repository_url] if params[:repository_url]
52
+ p
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,65 @@
1
+ require 'pact_broker/client/hal'
2
+ require 'pact_broker/client/command_result'
3
+ require 'pact_broker/client/hal_client_methods'
4
+
5
+ module PactBroker
6
+ module Client
7
+ module Pacts
8
+ class ListLatestVersions
9
+
10
+ include HalClientMethods
11
+
12
+ def self.call(pact_broker_base_url, output, pact_broker_client_options)
13
+ new(pact_broker_base_url, output, pact_broker_client_options).call
14
+ end
15
+
16
+ def initialize(pact_broker_base_url, output, pact_broker_client_options)
17
+ @index_entry_point = create_index_entry_point(pact_broker_base_url, pact_broker_client_options)
18
+ @output = output
19
+ end
20
+
21
+ def call
22
+ message = if output == 'json'
23
+ versions_resource.response.body
24
+ else
25
+ to_text(versions)
26
+ end
27
+ PactBroker::Client::CommandResult.new(true, message)
28
+
29
+ rescue StandardError => e
30
+ PactBroker::Client::CommandResult.new(false, e.message)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :index_entry_point, :output
36
+
37
+ def versions
38
+ versions_resource.pacts.collect do | pact |
39
+ OpenStruct.new(
40
+ consumer_name: pact['_embedded']['consumer']['name'],
41
+ provider_name: pact['_embedded']['provider']['name'],
42
+ consumer_version_number: pact['_embedded']['consumer']['_embedded']['version']['number'],
43
+ created_at: pact['createdAt']
44
+ )
45
+ end
46
+ end
47
+
48
+ def versions_resource
49
+ index_entry_point.get!._link('pb:latest-pact-versions').get!
50
+ end
51
+
52
+ def to_text(pacts)
53
+ require 'table_print'
54
+ options = [
55
+ { consumer_name: {display_name: 'consumer'} },
56
+ { consumer_version_number: {display_name: 'consumer_version'} },
57
+ { provider_name: {display_name: 'provider'} },
58
+ { created_at: {} }
59
+ ]
60
+ TablePrint::Printer.new(pacts, options).table_print
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -85,7 +85,7 @@ module PactBroker
85
85
  Retry.while_error do
86
86
  pacts = pact_broker_client.pacticipants.versions.pacts
87
87
  if pacts.version_published?(consumer: pact.consumer_name, provider: pact.provider_name, consumer_version: consumer_version)
88
- $stdout.puts ::Term::ANSIColor.yellow("The given version of pact is already published. Overwriting...")
88
+ $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.)")
89
89
  end
90
90
 
91
91
  latest_pact_url = pacts.publish(pact_hash: pact, consumer_version: consumer_version)