pact_broker-client 1.36.0 → 1.38.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release_gem.yml +1 -0
  3. data/CHANGELOG.md +32 -0
  4. data/Rakefile +2 -0
  5. data/bin/pact-broker +6 -0
  6. data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +180 -0
  7. data/lib/pact_broker/client.rb +1 -1
  8. data/lib/pact_broker/client/backports.rb +13 -0
  9. data/lib/pact_broker/client/cli/broker.rb +85 -30
  10. data/lib/pact_broker/client/cli/create_or_update_webhook_long_desc.txt +3 -1
  11. data/lib/pact_broker/client/cli/create_webhook_long_desc.txt +2 -0
  12. data/lib/pact_broker/client/cli/custom_thor.rb +11 -17
  13. data/lib/pact_broker/client/git.rb +3 -0
  14. data/lib/pact_broker/client/hal/entity.rb +44 -3
  15. data/lib/pact_broker/client/hal/http_client.rb +38 -2
  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/text_formatter.rb +2 -0
  20. data/lib/pact_broker/client/publish_pacts.rb +2 -1
  21. data/lib/pact_broker/client/tasks/publication_task.rb +2 -0
  22. data/lib/pact_broker/client/version.rb +1 -1
  23. data/lib/pact_broker/client/versions/record_deployment.rb +109 -0
  24. data/lib/pact_broker/client/versions/record_undeployment.rb +125 -0
  25. data/pact-broker-client.gemspec +1 -1
  26. data/script/record-deployment.sh +4 -0
  27. data/script/trigger-release.sh +1 -1
  28. data/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb +38 -4
  29. data/spec/lib/pact_broker/client/cli/broker_publish_spec.rb +1 -1
  30. data/spec/lib/pact_broker/client/cli/custom_thor_spec.rb +1 -7
  31. data/spec/lib/pact_broker/client/hal/entity_spec.rb +4 -3
  32. data/spec/lib/pact_broker/client/hal/http_client_spec.rb +64 -7
  33. data/spec/lib/pact_broker/client/versions/describe_spec.rb +0 -1
  34. data/spec/lib/pact_broker/client/versions/record_deployment_spec.rb +82 -0
  35. data/spec/pacts/pact_broker_client-pact_broker.json +181 -0
  36. data/spec/service_providers/record_deployment_spec.rb +219 -0
  37. data/spec/spec_helper.rb +2 -0
  38. metadata +17 -7
@@ -1 +1,3 @@
1
- Create a curl command that executes the request that you want your webhook to execute, then replace "curl" with "pact-broker create-or-update-webhook" and add the consumer, provider, event types and broker details. Note that the URL must be the first parameter when executing create-or-update-webhook and a uuid must also be provided. You can generate a valid UUID by using the `generate-uuid` command.
1
+ Create a curl command that executes the request that you want your webhook to execute, then replace "curl" with "pact-broker create-or-update-webhook" and add the consumer, provider, event types and broker details. Note that the URL must be the first parameter when executing create-or-update-webhook and a uuid must also be provided. You can generate a valid UUID by using the `generate-uuid` command.
2
+
3
+ Note that the -u option from the curl command clashes with the -u option from the pact-broker CLI. When used in this command, the -u will be used as a curl option. Please use the --broker-username or environment variable for the Pact Broker username.
@@ -1 +1,3 @@
1
1
  Create a curl command that executes the request that you want your webhook to execute, then replace "curl" with "pact-broker create-webhook" and add the consumer, provider, event types and broker details. Note that the URL must be the first parameter when executing create-webhook.
2
+
3
+ Note that the -u option from the curl command clashes with the -u option from the pact-broker CLI. When used in this command, the -u will be used as a curl option. Please use the --broker-username or environment variable for the Pact Broker username.
@@ -22,10 +22,7 @@ module PactBroker
22
22
  def self.add_broker_config_from_environment_variables argv
23
23
  return argv if argv[0] == 'help' || argv.empty?
24
24
 
25
- new_argv = add_option_from_environment_variable(argv, 'broker-base-url', 'b', 'PACT_BROKER_BASE_URL')
26
- new_argv = add_option_from_environment_variable(new_argv, 'broker-username', 'u', 'PACT_BROKER_USERNAME')
27
- new_argv = add_option_from_environment_variable(new_argv, 'broker-token', 'k', 'PACT_BROKER_TOKEN')
28
- add_option_from_environment_variable(new_argv, 'broker-password', 'p', 'PACT_BROKER_PASSWORD')
25
+ add_option_from_environment_variable(argv, 'broker-base-url', 'b', 'PACT_BROKER_BASE_URL')
29
26
  end
30
27
 
31
28
  def self.add_option_from_environment_variable argv, long_name, short_name, environment_variable_name
@@ -86,6 +83,14 @@ module PactBroker
86
83
  method_option :broker_token, hide: true
87
84
  end
88
85
 
86
+ def self.shared_authentication_options
87
+ method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
88
+ method_option :broker_username, aliases: "-u", desc: "Pact Broker basic auth username"
89
+ method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
90
+ method_option :broker_token, aliases: "-k", desc: "Pact Broker bearer token"
91
+ method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
92
+ end
93
+
89
94
  def self.shared_options_for_webhook_commands
90
95
  method_option :request, banner: "METHOD", aliases: "-X", desc: "Webhook HTTP method", required: true
91
96
  method_option :header, aliases: "-H", type: :array, desc: "Webhook Header"
@@ -93,24 +98,13 @@ module PactBroker
93
98
  method_option :user, aliases: "-u", desc: "Webhook basic auth username and password eg. username:password"
94
99
  method_option :consumer, desc: "Consumer name"
95
100
  method_option :provider, desc: "Provider name"
96
- method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
97
- method_option :broker_username, desc: "Pact Broker basic auth username"
98
- method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
99
- method_option :broker_token, aliases: "-k", desc: "Pact Broker bearer token"
100
101
  method_option :description, desc: "Wwebhook description"
101
102
  method_option :contract_content_changed, type: :boolean, desc: "Trigger this webhook when the pact content changes"
102
103
  method_option :contract_published, type: :boolean, desc: "Trigger this webhook when a pact is published"
103
104
  method_option :provider_verification_published, type: :boolean, desc: "Trigger this webhook when a provider verification result is published"
104
105
  method_option :provider_verification_failed, type: :boolean, desc: "Trigger this webhook when a failed provider verification result is published"
105
106
  method_option :provider_verification_succeeded, type: :boolean, desc: "Trigger this webhook when a successful provider verification result is published"
106
- method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
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"
107
+ shared_authentication_options
114
108
  end
115
109
 
116
110
  def self.verbose_option
@@ -120,4 +114,4 @@ module PactBroker
120
114
  end
121
115
  end
122
116
  end
123
- end
117
+ end
@@ -1,4 +1,5 @@
1
1
  require 'pact_broker/client/error'
2
+ require 'pact_broker/client/hash_refinements'
2
3
 
3
4
  =begin
4
5
 
@@ -21,6 +22,8 @@ BITBUCKET_BRANCH BITBUCKET_COMMIT https://confluence.atlassian.com/bitbucket/var
21
22
  module PactBroker
22
23
  module Client
23
24
  module Git
25
+ using PactBroker::Client::HashRefinements
26
+
24
27
  COMMAND = 'git rev-parse --abbrev-ref HEAD'.freeze
25
28
  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
26
29
  COMMIT_ENV_VAR_NAMES = %w{BUILDKITE_COMMIT CIRCLE_SHA1 TRAVIS_COMMIT GIT_COMMIT APPVEYOR_REPO_COMMIT CI_COMMIT_ID BITBUCKET_COMMIT}
@@ -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
@@ -7,6 +7,7 @@ module PactBroker
7
7
  module Client
8
8
  module Hal
9
9
  class HttpClient
10
+ RETRYABLE_ERRORS = [Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EHOSTUNREACH, Net::ReadTimeout]
10
11
  attr_accessor :username, :password, :verbose, :token
11
12
 
12
13
  def initialize options
@@ -17,7 +18,7 @@ module PactBroker
17
18
  end
18
19
 
19
20
  def get href, params = {}, headers = {}
20
- query = params.collect{ |(key, value)| "#{CGI::escape(key)}=#{CGI::escape(value)}" }.join("&")
21
+ query = params.collect{ |(key, value)| "#{CGI::escape(key.to_s)}=#{CGI::escape(value)}" }.join("&")
21
22
  uri = URI(href)
22
23
  uri.query = query
23
24
  perform_request(create_request(uri, 'Get', nil, headers), uri)
@@ -53,7 +54,7 @@ module PactBroker
53
54
  end
54
55
 
55
56
  def perform_request request, uri
56
- response = Retry.until_truthy_or_max_times do
57
+ response = until_truthy_or_max_times(times: 5, sleep: 5, condition: ->(resp) { resp.code.to_i < 500 }) do
57
58
  http = Net::HTTP.new(uri.host, uri.port, :ENV)
58
59
  http.set_debug_output(output_stream) if verbose
59
60
  http.use_ssl = (uri.scheme == 'https')
@@ -69,6 +70,37 @@ module PactBroker
69
70
  Response.new(response)
70
71
  end
71
72
 
73
+ def until_truthy_or_max_times options = {}
74
+ max_tries = options.fetch(:times, 3)
75
+ tries = 0
76
+ sleep_interval = options.fetch(:sleep, 5)
77
+ sleep(sleep_interval) if options[:sleep_first]
78
+ while true
79
+ begin
80
+ result = yield
81
+ return result if max_tries < 2
82
+ if options[:condition]
83
+ condition_result = options[:condition].call(result)
84
+ return result if condition_result
85
+ else
86
+ return result if result
87
+ end
88
+ tries += 1
89
+ return result if max_tries == tries
90
+ sleep sleep_interval
91
+ rescue *RETRYABLE_ERRORS => e
92
+ tries += 1
93
+ $stderr.puts "ERROR: Error making request - #{e.class} #{e.message} #{e.backtrace.find{|l| l.include?('pact_broker-client')}}, attempt #{tries} of #{max_tries}"
94
+ raise e if max_tries == tries
95
+ sleep sleep_interval
96
+ end
97
+ end
98
+ end
99
+
100
+ def sleep seconds
101
+ Kernel.sleep seconds
102
+ end
103
+
72
104
  def output_stream
73
105
  AuthorizationHeaderRedactor.new($stdout)
74
106
  end
@@ -83,6 +115,10 @@ module PactBroker
83
115
  end
84
116
  end
85
117
 
118
+ def headers
119
+ __getobj__().to_hash
120
+ end
121
+
86
122
  def raw_body
87
123
  __getobj__().body
88
124
  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
@@ -1,10 +1,12 @@
1
1
  require 'table_print'
2
2
  require 'dig_rb'
3
+ require 'pact_broker/client/hash_refinements'
3
4
 
4
5
  module PactBroker
5
6
  module Client
6
7
  class Matrix
7
8
  class TextFormatter
9
+ using PactBroker::Client::HashRefinements
8
10
 
9
11
  Line = Struct.new(:consumer, :consumer_version, :provider, :provider_version, :success, :ref)
10
12
 
@@ -5,11 +5,12 @@ require 'pact_broker/client/pact_file'
5
5
  require 'pact_broker/client/pact_hash'
6
6
  require 'pact_broker/client/merge_pacts'
7
7
  require 'pact_broker/client/hal_client_methods'
8
+ require 'pact_broker/client/hash_refinements'
8
9
 
9
10
  module PactBroker
10
11
  module Client
11
12
  class PublishPacts
12
-
13
+ using PactBroker::Client::HashRefinements
13
14
  include HalClientMethods
14
15
 
15
16
  def self.call(pact_broker_base_url, pact_file_paths, consumer_version_params, pact_broker_client_options={})
@@ -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,6 +18,7 @@ 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
22
24
  attr_reader :auto_detect_version_properties, :branch, :build_url
@@ -1,5 +1,5 @@
1
1
  module PactBroker
2
2
  module Client
3
- VERSION = '1.36.0'
3
+ VERSION = '1.38.2'
4
4
  end
5
5
  end
@@ -0,0 +1,109 @@
1
+ require 'pact_broker/client/hal_client_methods'
2
+ require 'pact_broker/client/error'
3
+ require 'pact_broker/client/command_result'
4
+
5
+ module PactBroker
6
+ module Client
7
+ class Versions
8
+ class RecordDeployment
9
+ include PactBroker::Client::HalClientMethods
10
+
11
+ NOT_SUPPORTED_MESSAGE = "This version of the Pact Broker does not support recording deployments. Please upgrade to version 2.80.0 or later."
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
+ @pact_broker_base_url = pact_broker_base_url
19
+ @pacticipant_name = params.fetch(:pacticipant_name)
20
+ @version_number = params.fetch(:version_number)
21
+ @environment_name = params.fetch(:environment_name)
22
+ @replaced_previous_deployed_version = params.fetch(:replaced_previous_deployed_version)
23
+ @output = params.fetch(:output)
24
+ @pact_broker_client_options = pact_broker_client_options
25
+ end
26
+
27
+ def call
28
+ check_if_command_supported
29
+ record_deployment
30
+
31
+ PactBroker::Client::CommandResult.new(true, result_message)
32
+ rescue PactBroker::Client::Error => e
33
+ PactBroker::Client::CommandResult.new(false, e.message)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :pact_broker_base_url, :pact_broker_client_options
39
+ attr_reader :pacticipant_name, :version_number, :environment_name, :replaced_previous_deployed_version, :output
40
+ attr_reader :deployed_version_resource
41
+
42
+ def check_environment_exists
43
+ index_resource
44
+ ._link!("pb:environments")
45
+ .get!
46
+ ._links("pb:environments")
47
+ .find!(environment_name, "No environment found with name '#{environment_name}'")
48
+ end
49
+
50
+ def record_deployment
51
+ @deployed_version_resource =
52
+ get_record_deployment_relation
53
+ .post(record_deployment_request_body)
54
+ .assert_success!
55
+ end
56
+
57
+ def get_record_deployment_relation
58
+ record_deployment_links = get_pacticipant_version._links!("pb:record-deployment")
59
+ link_for_environment = record_deployment_links.find(environment_name)
60
+ if link_for_environment
61
+ link_for_environment
62
+ else
63
+ check_environment_exists
64
+ # Force the exception to be raised
65
+ record_deployment_links.find!(environment_name, "Environment '#{environment_name}' is not an available option for recording a deployment of #{pacticipant_name}.")
66
+ end
67
+ end
68
+
69
+ def get_pacticipant_version
70
+ index_resource
71
+ ._link!("pb:pacticipant-version")
72
+ .expand(pacticipant: pacticipant_name, version: version_number)
73
+ .get
74
+ .assert_success!(404 => "#{pacticipant_name} version #{version_number} not found")
75
+ end
76
+
77
+ def record_deployment_request_body
78
+ { replacedPreviousDeployedVersion: replaced_previous_deployed_version }
79
+ end
80
+
81
+ def result_message
82
+ if output == "text"
83
+ message = "Recorded deployment of #{pacticipant_name} version #{version_number} to #{environment_name} in #{pact_broker_name}."
84
+ suffix = replaced_previous_deployed_version ? " Marked previous deployed version as undeployed." : ""
85
+ message + suffix
86
+ elsif output == "json"
87
+ deployed_version_resource.response.raw_body
88
+ else
89
+ ""
90
+ end
91
+ end
92
+
93
+ def pact_broker_name
94
+ is_pactflow? ? "Pactflow" : "the Pact Broker"
95
+ end
96
+
97
+ def is_pactflow?
98
+ deployed_version_resource.response.headers.keys.any?{ | header_name | header_name.downcase.include?("pactflow") }
99
+ end
100
+
101
+ def check_if_command_supported
102
+ unless index_resource.can?("pb:environments")
103
+ raise PactBroker::Client::Error.new(NOT_SUPPORTED_MESSAGE)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end