pact_broker-client 1.13.1 → 1.14.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/.gitignore +0 -1
  3. data/CHANGELOG.md +17 -0
  4. data/README.md +28 -17
  5. data/doc/markdown/Pact Broker Client - Pact Broker.md +582 -0
  6. data/doc/markdown/README.md +3 -0
  7. data/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +1174 -0
  8. data/doc/pacts/markdown/README.md +3 -0
  9. data/example/scripts/README.md +13 -0
  10. data/example/scripts/deploy-consumer.sh +26 -0
  11. data/example/scripts/pact.json +67 -0
  12. data/example/scripts/publish-pact.sh +3 -0
  13. data/example/scripts/publish-verification.sh +3 -0
  14. data/example/scripts/verification.json +4 -0
  15. data/lib/pact_broker/client/base_client.rb +17 -1
  16. data/lib/pact_broker/client/cli/broker.rb +27 -0
  17. data/lib/pact_broker/client/cli/can_i_deploy_long_desc.txt +13 -3
  18. data/lib/pact_broker/client/error.rb +2 -0
  19. data/lib/pact_broker/client/matrix.rb +0 -2
  20. data/lib/pact_broker/client/version.rb +1 -1
  21. data/lib/pact_broker/client/versions.rb +15 -5
  22. data/lib/pact_broker/client/versions/describe.rb +60 -0
  23. data/lib/pact_broker/client/versions/formatter.rb +20 -0
  24. data/lib/pact_broker/client/versions/json_formatter.rb +13 -0
  25. data/lib/pact_broker/client/versions/text_formatter.rb +31 -0
  26. data/spec/integration/can_i_deploy_spec.rb +2 -2
  27. data/spec/integration/create_version_tag_spec.rb +1 -1
  28. data/spec/lib/pact_broker/client/base_client_spec.rb +28 -0
  29. data/spec/lib/pact_broker/client/versions/describe_spec.rb +65 -0
  30. data/spec/pacts/pact_broker_client-pact_broker.json +118 -0
  31. data/spec/service_providers/pact_broker_client_versions_spec.rb +114 -0
  32. data/spec/service_providers/pact_helper.rb +1 -0
  33. metadata +19 -3
@@ -0,0 +1,3 @@
1
+ ### Pacts for Pact Broker Client
2
+
3
+ * [Pact Broker](Pact Broker Client - Pact Broker.md)
@@ -0,0 +1,13 @@
1
+ # Example scripts
2
+
3
+ These are designed to be run from the top level of the pact_broker-client repository.
4
+
5
+ You will need to set your own values for `PACT_BROKER_BASE_URL`, `PACT_BROKER_USERNAME`, and `PACT_BROKER_PASSWORD`
6
+
7
+ eg.
8
+
9
+ export PACT_BROKER_BASE_URL="http://..."
10
+ export PACT_BROKER_USERNAME="username"
11
+ export PACT_BROKER_PASSWORD="password"
12
+ example/scripts/publish-pact.sh
13
+ example/scripts/publish-verification.sh
@@ -0,0 +1,26 @@
1
+ version="1"
2
+ stage="development"
3
+ application="A"
4
+
5
+ export PACT_BROKER_CLIENT="bundle exec bin/pact-broker"
6
+ export PACT_BROKER_BASE_URL="http://localhost:9292"
7
+
8
+ can_i_deploy_output=$(${PACT_BROKER_CLIENT} can-i-deploy --pacticipant ${application} --version ${version})
9
+ can_i_deploy_exit_code=$?
10
+
11
+ set -e
12
+
13
+ echo "${can_i_deploy_output}"
14
+
15
+ if [[ "${can_i_deploy_exit_code}" == "0" ]]; then
16
+ existing_tags=$(${PACT_BROKER_CLIENT} describe-version --pacticipant ${application} --version ${version} --output json | jq "[._embedded.tags[].name]" | jq 'join("\n")' --raw-output)
17
+ if [ $(echo "${existing_tags}" | grep "${stage}") ]; then
18
+ echo "Version ${version} of ${application} has already been deployed to ${stage}"
19
+ else
20
+ echo "Deploying version ${version} of ${application} to ${stage}"
21
+ # do deployment here
22
+ ${PACT_BROKER_CLIENT} create-version-tag --pacticipant ${application} --version ${version} --tag ${stage}
23
+ fi
24
+ else
25
+ echo "Cannot currently deploy version ${version} of ${application} to ${stage}"
26
+ fi
@@ -0,0 +1,67 @@
1
+ {
2
+ "provider": {
3
+ "name": "Animal Service"
4
+ },
5
+ "consumer": {
6
+ "name": "Zoo App"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a request for an alligator",
11
+ "provider_state": "there is an alligator named Mary",
12
+ "request": {
13
+ "method": "get",
14
+ "path": "/alligators/Mary",
15
+ "headers": {
16
+ "Accept": "application/json"
17
+ }
18
+ },
19
+ "response": {
20
+ "status": 200,
21
+ "headers": {
22
+ "Content-Type": "application/json;charset=utf-8"
23
+ },
24
+ "body": {
25
+ "name": "Mary"
26
+ }
27
+ }
28
+ },
29
+ {
30
+ "description": "a request for an alligator",
31
+ "provider_state": "there is not an alligator named Mary",
32
+ "request": {
33
+ "method": "get",
34
+ "path": "/alligators/Mary",
35
+ "headers": {
36
+ "Accept": "application/json"
37
+ }
38
+ },
39
+ "response": {
40
+ "status": 404
41
+ }
42
+ },
43
+ {
44
+ "description": "a request for an alligator",
45
+ "provider_state": "an error occurs retrieving an alligator",
46
+ "request": {
47
+ "method": "get",
48
+ "path": "/alligators/Mary",
49
+ "headers": {
50
+ "Accept": "application/json"
51
+ }
52
+ },
53
+ "response": {
54
+ "status": 500,
55
+ "headers": {
56
+ "Content-Type": "application/json;charset=utf-8"
57
+ },
58
+ "body": {
59
+ "error": "Argh!!!"
60
+ }
61
+ }
62
+ }
63
+ ],
64
+ "metadata": {
65
+ "pactSpecificationVersion": "1.0.0"
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ # assumes you've set PACT_BROKER_BASE_URL, PACT_BROKER_USERNAME and PACT_BROKER_PASSWORD already
2
+
3
+ bundle exec bin/pact-broker publish $(dirname "$0")/pact.json --consumer-app-version=1.0.0 --tag master
@@ -0,0 +1,3 @@
1
+ response_body=$(curl -u ${PACT_BROKER_USERNAME}:${PACT_BROKER_PASSWORD} ${PACT_BROKER_BASE_URL}/pacts/provider/Animal%20Service/consumer/Zoo%20App/latest)
2
+ verification_url=$(echo "${response_body}" | ruby -e "require 'json'; puts JSON.parse(ARGF.read)['_links']['pb:publish-verification-results']['href']")
3
+ curl -XPOST -u ${PACT_BROKER_USERNAME}:${PACT_BROKER_PASSWORD} -H 'Content-Type: application/json' -d@$(dirname "$0")/verification.json ${verification_url}
@@ -0,0 +1,4 @@
1
+ {
2
+ "success": true,
3
+ "providerApplicationVersion": "2.0.0"
4
+ }
@@ -41,6 +41,7 @@ module PactBroker
41
41
  @client_options = options[:client_options] || {}
42
42
  @verbose = @client_options[:verbose]
43
43
  self.class.base_uri base_url
44
+ self.class.debug_output($stderr) if verbose?
44
45
  self.class.basic_auth(client_options[:basic_auth][:username], client_options[:basic_auth][:password]) if client_options[:basic_auth]
45
46
  end
46
47
 
@@ -62,7 +63,7 @@ module PactBroker
62
63
 
63
64
  def handle_response response
64
65
  if response.success?
65
- yield
66
+ yield response
66
67
  elsif response.code == 404
67
68
  nil
68
69
  elsif response.code == 401
@@ -97,6 +98,21 @@ module PactBroker
97
98
  self.class.get(url, *args)
98
99
  end
99
100
 
101
+ def url_for_relation relation_name, params
102
+ handle_response(get("/", headers: default_get_headers)) do | response |
103
+ relation = (JSON.parse(response.body)['_links'] || {})[relation_name]
104
+ if relation
105
+ url = relation['href']
106
+ params.each do | (key, value) |
107
+ url = url.gsub("{#{key}}", value)
108
+ end
109
+ url
110
+ else
111
+ raise PactBroker::Client::RelationNotFound.new("Could not find relation #{relation_name} in index resource. Try upgrading your Pact Broker as the feature you require may not exist in your version.")
112
+ end
113
+ end
114
+ end
115
+
100
116
  def verbose?
101
117
  @verbose
102
118
  end
@@ -7,6 +7,7 @@ require 'pact_broker/client/publish_pacts'
7
7
  require 'rake/file_list'
8
8
  require 'thor/error'
9
9
  require 'pact_broker/client/create_tag'
10
+ require 'pact_broker/client/versions/describe'
10
11
 
11
12
  module PactBroker
12
13
  module Client
@@ -72,6 +73,32 @@ module PactBroker
72
73
  pact_broker_client_options)
73
74
  end
74
75
 
76
+ method_option :pacticipant, required: true, aliases: "-a", desc: "The name of the pacticipant that the version belongs to."
77
+ method_option :version, required: false, aliases: "-e", desc: "The pacticipant version number."
78
+ method_option :latest, required: false, aliases: "-l", banner: '[TAG]', desc: "Describe the latest pacticipant version. Optionally specify a TAG to describe the latest version with the specified tag."
79
+ method_option :broker_base_url, required: true, aliases: "-b", desc: "The base URL of the Pact Broker"
80
+ method_option :broker_username, aliases: "-u", desc: "Pact Broker basic auth username"
81
+ method_option :broker_password, aliases: "-p", desc: "Pact Broker basic auth password"
82
+ method_option :output, aliases: "-o", desc: "json or table or id", default: 'table'
83
+ method_option :verbose, aliases: "-v", type: :boolean, default: false, required: false, desc: "Verbose output. Default: false"
84
+
85
+ desc 'describe-version', 'Describes a pacticipant version. If no version or tag is specified, the latest version is described.'
86
+ def describe_version
87
+ latest = !options.latest.nil? || (options.latest.nil? && options.version.nil?)
88
+ params = {
89
+ pacticipant: options.pacticipant,
90
+ version: options.version,
91
+ latest: latest,
92
+ tag: options.latest != "latest" ? options.latest : nil
93
+ }
94
+ opts = {
95
+ output: options.output
96
+ }
97
+ result = PactBroker::Client::Versions::Describe.call(params, opts, options.broker_base_url, pact_broker_client_options)
98
+ $stdout.puts result.message
99
+ exit(1) unless result.success
100
+ end
101
+
75
102
  desc 'version', "Show the pact_broker-client gem version"
76
103
  def version
77
104
  $stdout.puts PactBroker::Client::VERSION
@@ -1,20 +1,30 @@
1
1
  Returns exit code 0 or 1, indicating whether or not the specified pacticipant versions are compatible. Prints out the relevant pact/verification details.
2
2
 
3
- WARNING! This feature is in beta release, and backwards compatibility is NOT guaranteed. You will need the latest version of the Pact Broker for this feature to work.
4
-
5
3
  The environment variables PACT_BROKER_BASE_URL_BASE_URL, PACT_BROKER_BASE_URL_USERNAME and PACT_BROKER_BASE_URL_PASSWORD may be used instead of their respective command line options.
6
4
 
7
5
  SCENARIOS
8
6
 
7
+ # If every build goes straight to production
8
+
9
9
  Check the status of the pacts for a pacticipant version. Note that this only checks that the most recent verification for each pact is successful. It doesn't provide any assurance that the pact has been verified by the *production* version of the provider, however, it is sufficient if you are doing true continuous deployment.
10
10
 
11
11
  $ pact-broker can-i-deploy --pacticipant PACTICIPANT --version VERSION --broker-base-url BROKER_BASE_URL
12
12
 
13
+ # If every build does NOT go straight to production
14
+
15
+ ## Recommended approach
16
+
13
17
  If all applications within the pact network are not being deployed continuously (ie. if there is a gap between pact verification and actual deployment) then the following strategy is recommended. Each application version should be tagged in the broker with the name of the stage (eg. test, staging, production) as it is deployed (see the pact-broker create-version-tag CLI). This enables you to use the following very simple command to check if the application version you are about to deploy is compatible with every other application version already deployed in that environment.
14
18
 
15
19
  $ pact-broker can-i-deploy --pacticipant PACTICIPANT --version VERSION --to TAG --broker-base-url BROKER_BASE_URL
16
20
 
17
- Check the status of the pacts for the latest pacticipant version:
21
+ ## Other approaches
22
+
23
+ If you do not/cannot tag every application at deployment, you have two options. You can either use the very first form of this command which just checks that the *latest* verification is successful (not recommended as it's the production version that you really care about) or you will need to determine the production versions of each collaborating application from some other source (eg. git) and explictly reference each one using one using the format `--pacticipant PACTICIPANT1 --version VERSION1 --pacticipant PACTICIPANT2 --version VERSION2 ...`
24
+
25
+ # Other commands
26
+
27
+ Check the status of the pacts for the latest pacticipant version. This form is not recommended for use in your CI as it is possible that the version you are about to deploy is not the the version that the Broker considers the latest. It's best to specify the version explictly.
18
28
 
19
29
  $ pact-broker can-i-deploy --pacticipant PACTICIPANT --latest --broker-base-url BROKER_BASE_URL
20
30
 
@@ -1,5 +1,7 @@
1
1
  module PactBroker
2
2
  module Client
3
3
  class Error < StandardError; end
4
+
5
+ class RelationNotFound < Error; end
4
6
  end
5
7
  end
@@ -10,8 +10,6 @@ module PactBroker
10
10
  latestby: latestby
11
11
  }.merge(query_options(options))
12
12
  response = self.class.get("/matrix", query: query, headers: default_get_headers)
13
- $stdout.puts("DEBUG: Response headers #{response.headers}") if verbose?
14
- $stdout.puts("DEBUG: Response body #{response}") if verbose?
15
13
  response = handle_response(response) do
16
14
  JSON.parse(response.body, symbolize_names: true)
17
15
  end
@@ -1,5 +1,5 @@
1
1
  module PactBroker
2
2
  module Client
3
- VERSION = '1.13.1'
3
+ VERSION = '1.14.0'
4
4
  end
5
5
  end
@@ -5,13 +5,23 @@ module PactBroker
5
5
  module Client
6
6
  class Versions < BaseClient
7
7
 
8
-
9
- def latest options
10
- query = options[:tag] ? {tag: options[:tag]} : {}
11
- response = self.class.get("#{versions_base_url(options)}/latest", query: query, headers: default_get_headers)
8
+ def find options
9
+ response = get("#{version_base_url(options)}", headers: default_get_headers)
12
10
 
13
11
  handle_response(response) do
14
- string_keys_to_symbols(response.to_hash)
12
+ JSON.parse(response.body, symbolize_names: true)
13
+ end
14
+ end
15
+
16
+ def latest options
17
+ puts options
18
+ url = if options[:tag]
19
+ url_for_relation('pb:latest-tagged-version', pacticipant: options.fetch(:pacticipant), tag: options.fetch(:tag))
20
+ else
21
+ url_for_relation('pb:latest-version', pacticipant: options.fetch(:pacticipant))
22
+ end
23
+ handle_response(get(url, headers: default_get_headers)) do | response |
24
+ JSON.parse(response.body, symbolize_names: true)
15
25
  end
16
26
  end
17
27
 
@@ -0,0 +1,60 @@
1
+ require 'pact_broker/client/pact_broker_client'
2
+ require 'pact_broker/client/versions/formatter'
3
+
4
+ module PactBroker
5
+ module Client
6
+ class Versions
7
+ class Describe
8
+
9
+ class Result
10
+ attr_reader :success, :message
11
+
12
+ def initialize success, message = nil
13
+ @success = success
14
+ @message = message
15
+ end
16
+ end
17
+
18
+ def self.call params, options, pact_broker_base_url, pact_broker_client_options
19
+ new(params, options, pact_broker_base_url, pact_broker_client_options).call
20
+ end
21
+
22
+ def initialize params, options, pact_broker_base_url, pact_broker_client_options
23
+ @params = params
24
+ @options = options
25
+ @pact_broker_base_url = pact_broker_base_url
26
+ @pact_broker_client_options = pact_broker_client_options
27
+ end
28
+
29
+ def call
30
+ version_hash = if params[:version]
31
+ versions_client.find params
32
+ else
33
+ pact_broker_client.pacticipants.versions.latest(params)
34
+ end
35
+ if version_hash
36
+ Result.new(true, format_version(version_hash))
37
+ else
38
+ Result.new(false, "Pacticipant version not found")
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def format_version(version_hash)
45
+ Formatter.call(version_hash, options[:output])
46
+ end
47
+
48
+ attr_reader :params, :options, :pact_broker_base_url, :pact_broker_client_options
49
+
50
+ def versions_client
51
+ pact_broker_client.pacticipants.versions
52
+ end
53
+
54
+ def pact_broker_client
55
+ @pact_broker_client ||= PactBroker::Client::PactBrokerClient.new(base_url: pact_broker_base_url, client_options: pact_broker_client_options)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ require 'pact_broker/client/versions/json_formatter'
2
+ require 'pact_broker/client/versions/text_formatter'
3
+
4
+ module PactBroker
5
+ module Client
6
+ class Versions
7
+ class Formatter
8
+ def self.call(matrix_lines, format)
9
+ formatter = case format
10
+ when 'json' then JsonFormatter
11
+ when 'table' then TextFormatter
12
+ else
13
+ raise PactBroker::Client::Error.new("Invalid output option '#{format}. Must be one of 'table' or 'json'.")
14
+ end
15
+ formatter.call(matrix_lines)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'table_print'
2
+
3
+ module PactBroker
4
+ module Client
5
+ class Versions
6
+ class JsonFormatter
7
+ def self.call(matrix)
8
+ JSON.pretty_generate(matrix)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ require 'table_print'
2
+
3
+ module PactBroker
4
+ module Client
5
+ class Versions
6
+ class TextFormatter
7
+
8
+ Line = Struct.new(:number, :tags)
9
+
10
+ OPTIONS = [
11
+ { number: {} },
12
+ { tags: {} }
13
+ ]
14
+
15
+ def self.call(version_hash)
16
+ tags = (lookup(version_hash, [], :_embedded, :tags) || []).collect{ | t| t[:name] }.join(" ")
17
+ data = Line.new(version_hash[:number], tags)
18
+
19
+ printer = TablePrint::Printer.new([data], OPTIONS)
20
+ printer.table_print
21
+ end
22
+
23
+ def self.lookup line, default, *keys
24
+ keys.reduce(line) { | line, key | line[key] }
25
+ rescue NoMethodError
26
+ default
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,7 +5,7 @@ describe "pact-broker can-i-deploy" do
5
5
  end
6
6
 
7
7
  context "when the pacticipants can be deployed" do
8
- subject { `bundle exec bin/pact-broker can-i-deploy -v --pacticipant Foo --version 1.2.3 --pacticipant Bar --version 4.5.6 --broker-base-url http://localhost:5000` }
8
+ subject { `bundle exec bin/pact-broker can-i-deploy --pacticipant Foo --version 1.2.3 --pacticipant Bar --version 4.5.6 --broker-base-url http://localhost:5000` }
9
9
 
10
10
  it "returns a success exit code" do
11
11
  subject
@@ -18,7 +18,7 @@ describe "pact-broker can-i-deploy" do
18
18
  end
19
19
 
20
20
  context "when the pacticipants can't be deployed" do
21
- subject { `bundle exec bin/pact-broker can-i-deploy -v --pacticipant Wiffle --version 1.2.3 --pacticipant Meep --version 4.5.6 --broker-base-url http://localhost:5000` }
21
+ subject { `bundle exec bin/pact-broker can-i-deploy --pacticipant Wiffle --version 1.2.3 --pacticipant Meep --version 4.5.6 --broker-base-url http://localhost:5000` }
22
22
 
23
23
  it "returns an error exit code" do
24
24
  subject