pact_broker-client 1.25.0 → 1.27.2

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