pact_broker 2.93.4 → 2.95.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +7 -8
  4. data/db/migrations/20220303_increase_consumer_version_selector_hashes_column_size.rb +14 -0
  5. data/lib/db.rb +1 -0
  6. data/lib/pact_broker/api/authorization/resource_access_policy.rb +1 -5
  7. data/lib/pact_broker/api/decorators/format_date_time.rb +1 -1
  8. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +8 -0
  9. data/lib/pact_broker/api/pact_broker_urls.rb +4 -0
  10. data/lib/pact_broker/api/paths.rb +5 -2
  11. data/lib/pact_broker/api/resources/badge.rb +2 -25
  12. data/lib/pact_broker/api/resources/badge_methods.rb +57 -0
  13. data/lib/pact_broker/api/resources/base_resource.rb +273 -1
  14. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb +67 -0
  15. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +29 -0
  16. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb +48 -0
  17. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_badge.rb +25 -0
  18. data/lib/pact_broker/api/resources/default_base_resource.rb +0 -280
  19. data/lib/pact_broker/api.rb +7 -3
  20. data/lib/pact_broker/configuration.rb +1 -5
  21. data/lib/pact_broker/date_helper.rb +0 -4
  22. data/lib/pact_broker/domain/pact.rb +1 -1
  23. data/lib/pact_broker/domain/version.rb +7 -0
  24. data/lib/pact_broker/index/service.rb +1 -1
  25. data/lib/pact_broker/locale/en.yml +3 -0
  26. data/lib/pact_broker/matrix/quick_row.rb +11 -9
  27. data/lib/pact_broker/matrix/repository.rb +13 -12
  28. data/lib/pact_broker/matrix/unresolved_selector.rb +1 -0
  29. data/lib/pact_broker/pacticipants/find_potential_duplicate_pacticipant_names.rb +1 -4
  30. data/lib/pact_broker/pacts/pact_publication.rb +12 -6
  31. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +29 -5
  32. data/lib/pact_broker/pacts/repository.rb +1 -0
  33. data/lib/pact_broker/pacts/verifiable_pact.rb +5 -1
  34. data/lib/pact_broker/repositories/helpers.rb +4 -0
  35. data/lib/pact_broker/string_refinements.rb +9 -0
  36. data/lib/pact_broker/ui/controllers/base_controller.rb +8 -0
  37. data/lib/pact_broker/ui/controllers/can_i_deploy.rb +1 -1
  38. data/lib/pact_broker/ui/views/dashboard/show.haml +30 -12
  39. data/lib/pact_broker/ui/views/matrix/show.haml +8 -8
  40. data/lib/pact_broker/version.rb +1 -1
  41. data/lib/pact_broker/versions/branch.rb +4 -0
  42. data/lib/pact_broker/versions/branch_service.rb +10 -0
  43. data/lib/pact_broker/versions/repository.rb +8 -0
  44. data/lib/pact_broker/versions/service.rb +4 -0
  45. data/lib/webmachine/describe_routes.rb +24 -2
  46. data/vendor/hal-browser/js/hal/views/embedded_resource.js +1 -1
  47. metadata +9 -6
  48. data/lib/pact_broker/api/resources/can_i_deploy_badge.rb +0 -85
  49. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version.rb +0 -45
  50. data/public/bootstrap.zip +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd8313de32ee72fbd7202e9279130983eeccf2d5bc9c6ae589a5ed9700854cbd
4
- data.tar.gz: 8f7d311c9cbec69ca70f4a2420409e6c4747fcb0d1ae0f9af4d04baaf10aa43a
3
+ metadata.gz: f65cf2bd6eb2b3f0f7fd727e61fc73005817200f8e72bbab574493e4ea9b9ba3
4
+ data.tar.gz: aac1fb501fc5624227e63d424c8982f266fa0a9a2d80b1184844193347ba6724
5
5
  SHA512:
6
- metadata.gz: baaf854e49a5d623279b2481933fb9e033289efba9b4d9350703d9dc9737dac367b6076938f8f309426480f0745d9fe181c8f7f296f8727e17584de498ea631e
7
- data.tar.gz: ea459192ee83bce3c4fe1c307ad8f2ab3c0ed2734f40162cef0ae95033b81d0e3e7bc12d2765af52ae4a06a95ddfed8dfdca7b0c421e923aaa2ba4a172552bcf
6
+ metadata.gz: 8272759e5880069606e56bea3893055df49543b76b237d7511ce8fa21c0414f1a0e593e98ed1f9e67c4b9048b4da1c2d3ef938b0dfea182406cda696111c2bc4
7
+ data.tar.gz: 426a032f0016c8952934fbf630420be42accf192baeaff3bd2651aefa75170d055d8a4ed125640a7b3cad124cf5a461d7c930e5d289f4a6e5bd666366661940a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,43 @@
1
+ <a name="v2.95.1"></a>
2
+ ### v2.95.1 (2022-03-18)
3
+
4
+ #### Bug Fixes
5
+
6
+ * **can-i-deploy**
7
+ * allow new provider to be introduced to existing consumer without can-i-deploy having circular dependency issues ([e43974ca](/../../commit/e43974ca))
8
+
9
+ <a name="v2.95.0"></a>
10
+ ### v2.95.0 (2022-03-16)
11
+
12
+ #### Features
13
+
14
+ * add relation for can-i-deploy branch to environment badge ([6ece8e93](/../../commit/6ece8e93))
15
+ * add badge url for 'can I deploy latest version of branch to envionment' endpoint ([086b8c10](/../../commit/086b8c10))
16
+
17
+ #### Bug Fixes
18
+
19
+ * **pacts for verification**
20
+ * do not de-duplicate pacts with the same content but different consumers ([ae3bb541](/../../commit/ae3bb541))
21
+
22
+ <a name="v2.94.0"></a>
23
+ ### v2.94.0 (2022-02-22)
24
+
25
+ #### Features
26
+
27
+ * add can-i-deploy endpoint for checking if the latest version for a branch can be deployed to a particular environment ([34b145e8](/../../commit/34b145e8))
28
+ * truncate tags, branches and versions in UI when they are super long (#513) ([94bbf915](/../../commit/94bbf915))
29
+
30
+ * **integration dashboard**
31
+ * add copy buttons next to the branch, tag and environment labels ([5b86ac88](/../../commit/5b86ac88))
32
+
33
+ #### Bug Fixes
34
+
35
+ * Improve Matrix request performance (#537) ([123f8629](/../../commit/123f8629))
36
+ * Eagerly load pact publication fields (#536) ([c3f6993b](/../../commit/c3f6993b))
37
+
38
+ * **hal-browser**
39
+ * properties accordion should be collapsed by default (#544) ([19466121](/../../commit/19466121))
40
+
1
41
  <a name="v2.93.4"></a>
2
42
  ### v2.93.4 (2022-02-21)
3
43
 
data/README.md CHANGED
@@ -140,15 +140,14 @@ You can use the [Pact Broker Docker image][docker] or [Terraform on AWS][terrafo
140
140
  #### Rolling your own
141
141
 
142
142
  * Are you sure you don't just want to use the [Pact Broker Docker image][docker]? No Docker at your company yet? Ah well, keep reading.
143
- * Create a PostgreSQL (recommended) or MySQL (not _as_ recommended because of @bethesque's personal prejudices, but still fully supported) database.
144
- * To ensure you're on a supported version of the database that you choose, check the [travis.yml][travisyml] file to see which versions we're currently running our tests against.
145
- * If you're using PostgreSQL (did we mention this was _recommended!_) you'll find the database creation script in the [example/config.ru](https://github.com/pact-foundation/pact_broker/blob/master/example/config.ru).
146
- * Install ruby 2.4 or later and the latest version of bundler (if you've come this far, I'm assuming you know how to do both of these. Did I mention there was a [Docker][docker] image?)
143
+ * Create a PostgreSQL database.
144
+ * To ensure you're on a supported version of the database that you choose, check the [travis.yml][travisyml] file to see which versions we're currently running our tests against.
145
+ * MySQL was supported for the native Ruby application until around 2021, but the official `pactfoundation/pact-broker` Docker image does not support it. New features will not be optimised for MySQL, and some new features may not even be supported on it (eg. the database clean feature).
146
+ * You'll find a sample database creation script in the [example/config.ru](https://github.com/pact-foundation/pact_broker/blob/master/example/config.ru).
147
+ * Install ruby 2.7 or later and the latest version of bundler (if you've come this far, I'm assuming you know how to do both of these. Did I mention there was a [Docker][docker] image?)
147
148
  * Copy the [pact\_broker](https://github.com/DiUS/pact_broker-docker/tree/master/pact_broker) directory from the Pact Broker Docker project. This will have the recommended settings for database connections, logging, basic auth etc. Note that the Docker image uses Phusion Passenger as the web application server in front of the Pact Broker Ruby application, which is the recommended set up.
148
- * Modify the config.ru and Gemfile as desired (eg. choose database driver gem, set your database credentials. Use the "pg" gem if using Postgres and the "mysql2" gem if using MySQL)
149
- * example Sequel configuration for postgres `{adapter: "postgres", database: "pact_broker", username: 'pact_broker', password: 'pact_broker', :encoding => 'utf8'}`
150
- * example Sequel configuration for mysql `{adapter: "mysql2", database: "pact_broker", username: 'pact_broker', password: 'pact_broker', :encoding => 'utf8'}`
151
- `
149
+ * Modify the config.ru and Gemfile as desired (eg. choose database driver gem, set your database credentials. Use the "pg" gem for Postgres)
150
+ * example Sequel configuration for postgres `{ adapter: "postgres", database: "pact_broker", username: 'pact_broker', password: 'pact_broker', :encoding => 'utf8' }`
152
151
  * Please ensure you use `encoding: 'utf8'` in your Sequel options to avoid encoding issues.
153
152
  * For production usage, use a web application server like [Phusion Passenger](https://www.phusionpassenger.com) or [Nginx](http://nginx.org/) to serve the Pact Broker application. You'll need to read up on the documentation for these yourself as it is beyond the scope of this documentation. See the [wiki][reverse-proxy-docs] for instructions on using a reverse proxy with SSL.
154
153
  * Ensure the environment variable `RACK_ENV` is set to `production`.
@@ -0,0 +1,14 @@
1
+ require_relative "migration_helper"
2
+ include PactBroker::MigrationHelper
3
+
4
+ Sequel.migration do
5
+ up do
6
+ if mysql?
7
+ run("ALTER TABLE verifications CHANGE consumer_version_selector_hashes consumer_version_selector_hashes mediumtext")
8
+ end
9
+ end
10
+
11
+ down do
12
+
13
+ end
14
+ end
data/lib/db.rb CHANGED
@@ -38,6 +38,7 @@ module DB
38
38
  con.extension(:connection_validator)
39
39
  con.extension(:pagination)
40
40
  con.extension(:statement_timeout)
41
+ #con.extension(:caller_logging)
41
42
  con.extend_datasets do
42
43
  # rubocop: disable Lint/NestedMethodDefinition
43
44
  def any?
@@ -38,11 +38,7 @@ module PactBroker
38
38
  ]
39
39
 
40
40
  if enable_public_badge_access
41
- rules.concat([
42
- [PUBLIC, READ_METHODS, PACT_BADGE_PATH],
43
- [PUBLIC, READ_METHODS, MATRIX_BADGE_PATH],
44
- [PUBLIC, READ_METHODS, CAN_I_DEPLOY_BADGE_PATH]
45
- ])
41
+ rules.concat(BADGE_PATHS.collect { | badge_path | [PUBLIC, READ_METHODS, badge_path] })
46
42
  end
47
43
 
48
44
  if allow_public_access_to_heartbeat
@@ -8,7 +8,7 @@ module PactBroker
8
8
 
9
9
  def self.call(date_time)
10
10
  if date_time.is_a?(String)
11
- DATE_TIME_CLASS.strptime(date_time).to_time.utc.to_datetime.xmlschema
11
+ Sequel.string_to_datetime(date_time).to_time.utc.to_datetime.xmlschema
12
12
  elsif date_time
13
13
  date_time.to_time.utc.to_datetime.xmlschema if date_time
14
14
  end
@@ -90,6 +90,14 @@ module PactBroker
90
90
  }
91
91
  end
92
92
 
93
+ link :'pb:can-i-deploy-branch-to-environment-badge' do | options |
94
+ {
95
+ title: "Can I Deploy #{represented.name} from branch to environment badge",
96
+ href: templated_can_i_deploy_branch_to_environment_badge_url(represented.name, options[:base_url]),
97
+ templated: true
98
+ }
99
+ end
100
+
93
101
  curies do | options |
94
102
  [{
95
103
  name: :pb,
@@ -255,6 +255,10 @@ module PactBroker
255
255
  templated_can_i_deploy_url(pacticipant_name, base_url) + "/badge"
256
256
  end
257
257
 
258
+ def templated_can_i_deploy_branch_to_environment_badge_url pacticipant_name, base_url = ""
259
+ pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/branches/{branch}/latest-version/can-i-deploy/to-environment/{environment}/badge"
260
+ end
261
+
258
262
  def label_url label, base_url
259
263
  "#{labels_url(label.pacticipant, base_url)}/#{url_encode(label.name)}"
260
264
  end
@@ -3,9 +3,12 @@ module PactBroker
3
3
  module Paths
4
4
  PACT_BADGE_PATH = %r{^/pacts/provider/[^/]+/consumer/.*/badge(?:\.[A-Za-z]+)?$}.freeze
5
5
  MATRIX_BADGE_PATH = %r{^/matrix/provider/[^/]+/latest/[^/]+/consumer/[^/]+/latest/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze
6
- CAN_I_DEPLOY_BADGE_PATH = %r{^/pacticipants/[^/]+/latest-version/[^/]+/can-i-deploy/to/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze
6
+ CAN_I_DEPLOY_TAG_BADGE_PATH = %r{^/pacticipants/[^/]+/latest-version/[^/]+/can-i-deploy/to/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze
7
+ CAN_I_DEPLOY_BRANCH_ENV_BADGE_PATH = %r{^/pacticipants/[^/]+/branches/[^/]+/latest-version/can-i-deploy/to-environment/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze
7
8
  VERIFICATION_RESULTS = %r{^/pacts/provider/[^/]+/consumer/[^/]+/pact-version/[^/]+/verification-results/[^/]+}
8
9
 
10
+ BADGE_PATHS = [PACT_BADGE_PATH, MATRIX_BADGE_PATH, CAN_I_DEPLOY_TAG_BADGE_PATH, CAN_I_DEPLOY_BRANCH_ENV_BADGE_PATH]
11
+
9
12
  extend self
10
13
 
11
14
  def is_verification_results_path?(path)
@@ -14,7 +17,7 @@ module PactBroker
14
17
 
15
18
  def is_badge_path?(path)
16
19
  # Optimise by checking include? first - regexp slow
17
- path.include?("/badge") && (path =~ PACT_BADGE_PATH || path =~ MATRIX_BADGE_PATH || path =~ CAN_I_DEPLOY_BADGE_PATH)
20
+ path.include?("/badge") && BADGE_PATHS.any?{ | regex | path =~ regex }
18
21
  end
19
22
  end
20
23
  end
@@ -1,37 +1,22 @@
1
1
  require "pact_broker/api/resources/base_resource"
2
2
  require "pact_broker/verifications/pseudo_branch_status"
3
3
  require "pact_broker/configuration"
4
+ require "pact_broker/api/resources/badge_methods"
4
5
 
5
6
  module PactBroker
6
7
  module Api
7
8
  module Resources
8
9
  class Badge < BaseResource
9
-
10
- def allowed_methods
11
- ["GET", "OPTIONS"]
12
- end
13
-
14
- def content_types_provided
15
- [["image/svg+xml", :to_svg]]
16
- end
10
+ include BadgeMethods
17
11
 
18
12
  def resource_exists?
19
13
  !badge_service.can_provide_badge_using_redirect?
20
14
  end
21
15
 
22
- # Only called if resource_exists? returns false
23
- def previously_existed?
24
- true
25
- end
26
-
27
16
  def is_authorized?(authorization_header)
28
17
  super || PactBroker.configuration.enable_public_badge_access
29
18
  end
30
19
 
31
- def forbidden?
32
- false
33
- end
34
-
35
20
  def to_svg
36
21
  response.headers["Cache-Control"] = "no-cache"
37
22
  comment + badge_service.pact_verification_badge(pact, label, initials, pseudo_branch_verification_status, tags)
@@ -42,10 +27,6 @@ module PactBroker
42
27
  badge_service.pact_verification_badge_url(pact, label, initials, pseudo_branch_verification_status, tags)
43
28
  end
44
29
 
45
- def policy_name
46
- :'badges::badge'
47
- end
48
-
49
30
  private
50
31
 
51
32
  def pact
@@ -61,10 +42,6 @@ module PactBroker
61
42
  @pseudo_branch_verification_status ||= PactBroker::Verifications::PseudoBranchStatus.new(pact, latest_verification).to_sym
62
43
  end
63
44
 
64
- def label
65
- request.query["label"]
66
- end
67
-
68
45
  def initials
69
46
  request.query["initials"] == "true"
70
47
  end
@@ -0,0 +1,57 @@
1
+ module PactBroker
2
+ module Api
3
+ module Resources
4
+ module BadgeMethods
5
+ def allowed_methods
6
+ ["GET", "OPTIONS"]
7
+ end
8
+
9
+ def content_types_provided
10
+ [["image/svg+xml", :to_svg]]
11
+ end
12
+
13
+ def resource_exists?
14
+ false
15
+ end
16
+
17
+ # Only called if resource_exists? returns false
18
+ def previously_existed?
19
+ true
20
+ end
21
+
22
+ def forbidden?
23
+ false
24
+ end
25
+
26
+ def is_authorized?(authorization_header)
27
+ super || PactBroker.configuration.enable_public_badge_access
28
+ end
29
+
30
+ def policy_name
31
+ :'badges::badge'
32
+ end
33
+
34
+ def moved_temporarily?
35
+ response.headers["Cache-Control"] = "no-cache"
36
+ begin
37
+ badge_url
38
+ rescue StandardError => e
39
+ # Want to render a badge, even if there's an error
40
+ badge_service.error_badge_url("error", ErrorResponseBodyGenerator.display_message(e, "reference: #{PactBroker::Errors.generate_error_reference}"))
41
+ end
42
+ end
43
+
44
+ def badge_url
45
+ raise NotImplementedError
46
+ end
47
+
48
+ private
49
+
50
+ def label
51
+ lab = request.query["label"]
52
+ lab && !lab.empty? ? lab : nil
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,9 +1,281 @@
1
+ # frozen_string_literal: true
1
2
  require "pact_broker/configuration"
3
+ require "webmachine"
4
+ require "pact_broker/services"
5
+ require "pact_broker/api/decorators"
6
+ require "pact_broker/logging"
7
+ require "pact_broker/api/pact_broker_urls"
8
+ require "pact_broker/json"
9
+ require "pact_broker/pacts/pact_params"
10
+ require "pact_broker/api/resources/authentication"
11
+ require "pact_broker/api/resources/authorization"
12
+ require "pact_broker/errors"
2
13
 
3
14
  module PactBroker
4
15
  module Api
5
16
  module Resources
6
- BaseResource = PactBroker.configuration.base_resource_class_factory.call
17
+ class InvalidJsonError < PactBroker::Error ; end
18
+
19
+ class BaseResource < Webmachine::Resource
20
+ include PactBroker::Services
21
+ include PactBroker::Api::PactBrokerUrls
22
+ include PactBroker::Api::Resources::Authentication
23
+ include PactBroker::Api::Resources::Authorization
24
+
25
+ include PactBroker::Logging
26
+
27
+ attr_accessor :user
28
+
29
+ def initialize
30
+ PactBroker.configuration.before_resource.call(self)
31
+ application_context.before_resource&.call(self)
32
+ end
33
+
34
+ def options
35
+ { "Access-Control-Allow-Methods" => allowed_methods.join(", ")}
36
+ end
37
+
38
+ def known_methods
39
+ super + ["PATCH"]
40
+ end
41
+
42
+ def finish_request
43
+ application_context.after_resource&.call(self)
44
+ PactBroker.configuration.after_resource.call(self)
45
+ end
46
+
47
+ def is_authorized?(authorization_header)
48
+ authenticated?(self, authorization_header)
49
+ end
50
+
51
+ def forbidden?
52
+ if application_context.resource_authorizer
53
+ !application_context.resource_authorizer.call(self)
54
+ elsif PactBroker.configuration.authorize
55
+ !PactBroker.configuration.authorize.call(self, {})
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ # The path_info segments aren't URL decoded
62
+ def identifier_from_path
63
+ @identifier_from_path ||= request.path_info.each_with_object({}) do | (key, value), hash|
64
+ if value.is_a?(String)
65
+ hash[key] = URI.decode(value)
66
+ elsif value.is_a?(Symbol) || value.is_a?(Numeric)
67
+ hash[key] = value
68
+ end
69
+ end
70
+ end
71
+
72
+ alias_method :path_info, :identifier_from_path
73
+
74
+ def base_url
75
+ # Have to use something for the base URL here - we can't use an empty string as we can in the UI.
76
+ # Can't work out if cache poisoning is a vulnerability for APIs or not.
77
+ # Using the request base URI as a fallback if the base_url is not configured may be a vulnerability,
78
+ # but the documentation recommends that the
79
+ # base_url should be set in the configuration to mitigate this.
80
+ request.env["pactbroker.base_url"] || request.base_uri.to_s.chomp("/")
81
+ end
82
+
83
+ # See comments for base_url in lib/pact_broker/doc/controllers/app.rb
84
+ def ui_base_url
85
+ request.env["pactbroker.base_url"] || ""
86
+ end
87
+
88
+ def charsets_provided
89
+ [["utf-8", :encode]]
90
+ end
91
+
92
+ # We only use utf-8 so leave encoding as it is
93
+ def encode(body)
94
+ body
95
+ end
96
+
97
+ def resource_url
98
+ request.uri.to_s.gsub(/\?.*/, "").chomp("/")
99
+ end
100
+
101
+ def decorator_context options = {}
102
+ application_context.decorator_context_creator.call(self, options)
103
+ end
104
+
105
+ def decorator_options options = {}
106
+ { user_options: decorator_context(options) }
107
+ end
108
+
109
+ def handle_exception(error)
110
+ error_reference = PactBroker::Errors.generate_error_reference
111
+ application_context.error_logger.call(error, error_reference, request.env)
112
+ if PactBroker::Errors.reportable_error?(error)
113
+ PactBroker::Errors.report(error, error_reference, request.env)
114
+ end
115
+ response.body = application_context.error_response_body_generator.call(error, error_reference, request.env)
116
+ end
117
+
118
+ # rubocop: disable Metrics/CyclomaticComplexity
119
+ def params(options = {})
120
+ return options[:default] if options.key?(:default) && request_body.empty?
121
+
122
+ symbolize_names = !options.key?(:symbolize_names) || options[:symbolize_names]
123
+ if symbolize_names
124
+ @params_with_symbol_keys ||= JSON.parse(request_body, { symbolize_names: true }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
125
+ else
126
+ @params_with_string_keys ||= JSON.parse(request_body, { symbolize_names: false }.merge(PACT_PARSING_OPTIONS)) #Not load! Otherwise it will try to load Ruby classes.
127
+ end
128
+ rescue JSON::JSONError => e
129
+ raise InvalidJsonError.new("Error parsing JSON - #{e.message}")
130
+ end
131
+ # rubocop: enable Metrics/CyclomaticComplexity
132
+
133
+ def params_with_string_keys
134
+ params(symbolize_names: false)
135
+ end
136
+
137
+ def pact_params
138
+ @pact_params ||= PactBroker::Pacts::PactParams.from_request request, identifier_from_path
139
+ end
140
+
141
+ def set_json_error_message message
142
+ response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
143
+ response.body = { error: message }.to_json
144
+ end
145
+
146
+ def set_json_validation_error_messages errors
147
+ response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
148
+ response.body = { errors: errors }.to_json
149
+ end
150
+
151
+ def request_body
152
+ @request_body ||= request.body.to_s
153
+ end
154
+
155
+ def consumer_name
156
+ identifier_from_path[:consumer_name]
157
+ end
158
+
159
+ def consumer_version_number
160
+ identifier_from_path[:consumer_version_number]
161
+ end
162
+
163
+ def pacticipant_version_number
164
+ identifier_from_path[:pacticipant_version_number]
165
+ end
166
+
167
+ def consumer_specified?
168
+ identifier_from_path.key?(:consumer_name)
169
+ end
170
+
171
+ def provider_specified?
172
+ identifier_from_path.key?(:provider_name)
173
+ end
174
+
175
+ def provider_name
176
+ identifier_from_path[:provider_name]
177
+ end
178
+
179
+ def pacticipant_name
180
+ identifier_from_path[:pacticipant_name]
181
+ end
182
+
183
+ def pacticipant_specified?
184
+ identifier_from_path.key?(:pacticipant_name)
185
+ end
186
+
187
+ def invalid_json?
188
+ begin
189
+ params
190
+ false
191
+ rescue StandardError => e
192
+ logger.info "Error parsing JSON #{e} - #{request_body}"
193
+ set_json_error_message "Error parsing JSON - #{e.message}"
194
+ response.headers["Content-Type"] = "application/hal+json;charset=utf-8"
195
+ true
196
+ end
197
+ end
198
+
199
+ def validation_errors? model
200
+ if (errors = model.validate).any?
201
+ set_json_validation_error_messages errors
202
+ true
203
+ else
204
+ false
205
+ end
206
+ end
207
+
208
+ def contract_validation_errors? contract, params
209
+ if (invalid = !contract.validate(params))
210
+ set_json_validation_error_messages contract.errors.messages
211
+ end
212
+ invalid
213
+ end
214
+
215
+ def find_pacticipant name, role
216
+ pacticipant_service.find_pacticipant_by_name(name).tap do | pacticipant |
217
+ set_json_error_message("No #{role} with name '#{name}' found") if pacticipant.nil?
218
+ end
219
+ end
220
+
221
+ def consumer
222
+ @consumer ||= identifier_from_path[:consumer_name] && find_pacticipant(identifier_from_path[:consumer_name], "consumer")
223
+ end
224
+
225
+ def provider
226
+ @provider ||= identifier_from_path[:provider_name] && find_pacticipant(identifier_from_path[:provider_name], "provider")
227
+ end
228
+
229
+ def pacticipant
230
+ @pacticipant ||= identifier_from_path[:pacticipant_name] && find_pacticipant(identifier_from_path[:pacticipant_name], "pacticipant")
231
+ end
232
+
233
+ def pact
234
+ @pact ||= pact_service.find_pact(pact_params)
235
+ end
236
+
237
+ # Not necessarily an existing integration
238
+ def integration
239
+ if consumer_specified? && provider_specified?
240
+ OpenStruct.new(consumer: consumer, provider: provider)
241
+ else
242
+ nil
243
+ end
244
+ end
245
+
246
+ def database_connector
247
+ request.env["pactbroker.database_connector"]
248
+ end
249
+
250
+ def application_context
251
+ request.path_info[:application_context]
252
+ end
253
+
254
+ def decorator_class(name)
255
+ application_context.decorator_configuration.class_for(name)
256
+ end
257
+
258
+ def api_contract_class(name)
259
+ application_context.api_contract_configuration.class_for(name)
260
+ end
261
+
262
+ def schema
263
+ nil
264
+ end
265
+
266
+ def validation_errors_for_schema?(schema_to_use = schema, params_to_validate = params)
267
+ if (errors = schema_to_use.call(params_to_validate)).any?
268
+ set_json_validation_error_messages(errors)
269
+ true
270
+ else
271
+ false
272
+ end
273
+ end
274
+
275
+ def malformed_request_for_json_with_schema?(schema_to_use = schema, params_to_validate = params)
276
+ invalid_json? || validation_errors_for_schema?(schema_to_use, params_to_validate)
277
+ end
278
+ end
7
279
  end
8
280
  end
9
281
  end
@@ -0,0 +1,67 @@
1
+ require "pact_broker/api/resources/matrix"
2
+ require "pact_broker/matrix/can_i_deploy_query_schema"
3
+ require "pact_broker/matrix/parse_can_i_deploy_query"
4
+ require "pact_broker/api/decorators/matrix_decorator"
5
+ require "pact_broker/api/decorators/matrix_text_decorator"
6
+
7
+ module PactBroker
8
+ module Api
9
+ module Resources
10
+ class CanIDeployPacticipantVersionByBranchToEnvironment < BaseResource
11
+ def allowed_methods
12
+ ["GET", "OPTIONS"]
13
+ end
14
+
15
+ def content_types_provided
16
+ [
17
+ ["application/hal+json", :to_json],
18
+ ["text/plain", :to_text]
19
+ ]
20
+ end
21
+
22
+ def resource_exists?
23
+ !!(version && environment)
24
+ end
25
+
26
+ def policy_name
27
+ :'versions::version'
28
+ end
29
+
30
+ private
31
+
32
+ def selectors
33
+ @selectors ||= [
34
+ PactBroker::Matrix::UnresolvedSelector.new(
35
+ pacticipant_name: pacticipant_name,
36
+ latest: true,
37
+ branch: identifier_from_path[:branch_name]
38
+ )
39
+ ]
40
+ end
41
+
42
+ def options
43
+ @options ||= {
44
+ latestby: "cvp",
45
+ environment_name: identifier_from_path[:environment_name]
46
+ }
47
+ end
48
+
49
+ def to_json
50
+ decorator_class(:matrix_decorator).new(results).to_json(decorator_options)
51
+ end
52
+
53
+ def results
54
+ @results ||= matrix_service.can_i_deploy(selectors, options)
55
+ end
56
+
57
+ def version
58
+ @version ||= version_service.find_latest_by_pacticipant_name_and_branch_name(identifier_from_path[:pacticipant_name], identifier_from_path[:branch_name])
59
+ end
60
+
61
+ def environment
62
+ @environment ||= environment_service.find_by_name(identifier_from_path[:environment_name])
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end