pact_broker 2.100.0 → 2.101.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile +1 -0
  4. data/docs/api/PACTICIPANTS.md +290 -0
  5. data/docs/api/WEBHOOKS.md +40 -40
  6. data/lib/db.rb +1 -1
  7. data/lib/pact_broker/api/decorators/triggered_webhook_decorator.rb +1 -2
  8. data/lib/pact_broker/api/resources/all_webhooks.rb +1 -4
  9. data/lib/pact_broker/api/resources/base_resource.rb +51 -5
  10. data/lib/pact_broker/api/resources/branch_version.rb +10 -1
  11. data/lib/pact_broker/api/resources/clean.rb +11 -9
  12. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +0 -4
  13. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +0 -4
  14. data/lib/pact_broker/api/resources/deployed_version.rb +12 -14
  15. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +4 -0
  16. data/lib/pact_broker/api/resources/environment.rb +5 -5
  17. data/lib/pact_broker/api/resources/environments.rb +1 -5
  18. data/lib/pact_broker/api/resources/label.rb +4 -0
  19. data/lib/pact_broker/api/resources/pact.rb +10 -5
  20. data/lib/pact_broker/api/resources/pact_webhooks.rb +1 -4
  21. data/lib/pact_broker/api/resources/pacticipant.rb +11 -5
  22. data/lib/pact_broker/api/resources/pacticipant_webhooks.rb +1 -4
  23. data/lib/pact_broker/api/resources/pacticipants.rb +1 -4
  24. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +19 -11
  25. data/lib/pact_broker/api/resources/publish_contracts.rb +11 -15
  26. data/lib/pact_broker/api/resources/released_version.rb +12 -6
  27. data/lib/pact_broker/api/resources/released_versions_for_version_and_environment.rb +10 -6
  28. data/lib/pact_broker/api/resources/tag.rb +7 -3
  29. data/lib/pact_broker/api/resources/verifications.rb +7 -9
  30. data/lib/pact_broker/api/resources/version.rb +8 -8
  31. data/lib/pact_broker/api/resources/webhook.rb +5 -4
  32. data/lib/pact_broker/api/resources/webhook_execution.rb +4 -6
  33. data/lib/pact_broker/doc/views/index/pacticipant-branch-version.markdown +13 -2
  34. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +1 -1
  35. data/lib/pact_broker/domain/pacticipant.rb +1 -0
  36. data/lib/pact_broker/locale/en.yml +1 -0
  37. data/lib/pact_broker/pacticipants/repository.rb +5 -4
  38. data/lib/pact_broker/pacts/generate_sha.rb +1 -0
  39. data/lib/pact_broker/pacts/verifiable_pact_messages.rb +1 -1
  40. data/lib/pact_broker/test/test_data_builder.rb +20 -0
  41. data/lib/pact_broker/version.rb +1 -1
  42. data/lib/pact_broker/versions/branch_service.rb +7 -0
  43. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  44. data/lib/rack/pact_broker/cascade.rb +87 -0
  45. data/lib/webmachine/describe_routes.rb +43 -9
  46. metadata +5 -4
  47. data/lib/pact_broker/api/resources/default_base_resource.rb +0 -0
@@ -22,15 +22,16 @@ module PactBroker
22
22
  ["GET", "PUT", "DELETE", "OPTIONS"]
23
23
  end
24
24
 
25
+ def put_can_create?
26
+ true
27
+ end
28
+
25
29
  def resource_exists?
26
30
  !!webhook
27
31
  end
28
32
 
29
33
  def malformed_request?
30
- if request.put?
31
- return invalid_json? || webhook_validation_errors?(parsed_webhook, uuid)
32
- end
33
- false
34
+ super || (request.put? && webhook_validation_errors?(parsed_webhook, uuid))
34
35
  end
35
36
 
36
37
  def from_json
@@ -13,10 +13,6 @@ module PactBroker
13
13
  include WebhookResourceMethods
14
14
  include WebhookExecutionMethods
15
15
 
16
- def content_types_accepted
17
- [["application/json"]]
18
- end
19
-
20
16
  def content_types_provided
21
17
  [["application/hal+json"]]
22
18
  end
@@ -37,14 +33,16 @@ module PactBroker
37
33
  end
38
34
 
39
35
  def malformed_request?
40
- if request.post?
36
+ if super
37
+ true
38
+ elsif request.post?
41
39
  if uuid
42
40
  false
43
41
  else
44
42
  webhook_validation_errors?(webhook)
45
43
  end
46
44
  else
47
- super
45
+ false
48
46
  end
49
47
  end
50
48
 
@@ -1,14 +1,25 @@
1
1
  # Pacticipant branch version
2
2
 
3
- Allowed methods: `GET`, `PUT`
3
+ Allowed methods: `GET`, `PUT`, `DELETE`
4
4
 
5
5
  Path: `/pacticipants/{pacticipant}/branches/{branch}/versions/{version}`
6
6
 
7
7
  Get or add/create a pacticipant version for a branch.
8
8
 
9
- ## Example
9
+ ## Create
10
+
11
+ ### Example
10
12
 
11
13
  Add a version to a branch. The pacticipant and branch are automatically created if they do not exist.
12
14
 
13
15
  curl -XPUT http://broker/pacticipants/Bar/branches/main/versions/1e70030c6579915e5ff56b107a0fd25cf5df7464 \
14
16
  -H "Content-Type: application/json" -d "{}"
17
+
18
+
19
+ ## Delete
20
+
21
+ Removes a pacticipant version from a branch. Does not delete the actual pacticipant version.
22
+
23
+ Send a `DELETE` request to the branch version resource.
24
+
25
+ curl -XDELETE http://broker/pacticipants/Bar/branches/main/versions/1e70030c6579915e5ff56b107a0fd25cf5df7464
@@ -48,7 +48,7 @@ Example: This data structure represents the way a user might specify "I want to
48
48
 
49
49
  `consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
50
50
 
51
- `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use the `fallbackBranch` in preference now.*
51
+ `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use two separate selectors - one with the main branch name and one with the feature branch name.*
52
52
 
53
53
  `providerVersionBranch`: the repository branch name for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not.
54
54
 
@@ -44,6 +44,7 @@ module PactBroker
44
44
  using PactBroker::StringRefinements
45
45
 
46
46
  plugin :insert_ignore, identifying_columns: [:name]
47
+ plugin :upsert, identifying_columns: [:name]
47
48
  plugin :timestamps, update_on_create: true
48
49
 
49
50
  set_primary_key :id
@@ -107,6 +107,7 @@ en:
107
107
  To disable this check, set `check_for_potential_duplicate_pacticipant_names` to false in the configuration.
108
108
  new_line_in_url_path: URL path cannot contain a new line character.
109
109
  tab_in_url_path: URL path cannot contain a tab character.
110
+ non_utf_8_char_in_request_body: "Request body has a non UTF-8 character at char %{char_number} and cannot be parsed as JSON. Fragment preceding invalid character is: '%{fragment}'"
110
111
 
111
112
  "400":
112
113
  title: 400 Malformed Request
@@ -59,22 +59,23 @@ module PactBroker
59
59
  repository_name: params[:repository_name],
60
60
  repository_namespace: params[:repository_namespace],
61
61
  main_branch: params[:main_branch]
62
- ).insert_ignore
62
+ ).insert_ignore.refresh
63
63
  end
64
64
 
65
65
  def update(pacticipant_name, pacticipant)
66
66
  pacticipant.name = pacticipant_name
67
- pacticipant.save
67
+ pacticipant.save.refresh
68
68
  end
69
69
 
70
- def replace(_pacticipant_name, open_struct_pacticipant)
70
+ def replace(pacticipant_name, open_struct_pacticipant)
71
71
  PactBroker::Domain::Pacticipant.new(
72
+ name: pacticipant_name,
72
73
  display_name: open_struct_pacticipant.display_name,
73
74
  repository_url: open_struct_pacticipant.repository_url,
74
75
  repository_name: open_struct_pacticipant.repository_name,
75
76
  repository_namespace: open_struct_pacticipant.repository_namespace,
76
77
  main_branch: open_struct_pacticipant.main_branch
77
- ).save
78
+ ).upsert
78
79
  end
79
80
 
80
81
  def delete(pacticipant)
@@ -7,6 +7,7 @@ require "pact_broker/pacts/content"
7
7
  module PactBroker
8
8
  module Pacts
9
9
  class GenerateSha
10
+ # @param [String] json_content
10
11
  def self.call json_content, _options = {}
11
12
  content_for_sha = if PactBroker.configuration.base_equality_only_on_content_that_affects_verification_results
12
13
  extract_verifiable_content_for_sha(json_content)
@@ -31,7 +31,7 @@ module PactBroker
31
31
  version_text = head_consumer_tags.size == 1 || branches.size == 1 ? "version" : "versions"
32
32
  if wip?
33
33
  # WIP pacts will always have tags, because it is part of the definition of being a WIP pact
34
- "The pact at #{pact_version_url} is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest #{version_text} of #{consumer_name} #{joined_head_consumer_tags_and_branches} and it has not yet been successfully verified by #{pending_provider_branch_or_tags_description("a")} when the pact's application version was explicitly specified in the consumer version selectors). #{READ_MORE_WIP}".tap { |it| puts it }
34
+ "The pact at #{pact_version_url} is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest #{version_text} of #{consumer_name} #{joined_head_consumer_tags_and_branches} and it has not yet been successfully verified by #{pending_provider_branch_or_tags_description("a")} when the pact's application version was explicitly specified in the consumer version selectors). #{READ_MORE_WIP}"
35
35
  else
36
36
  criteria_or_criterion = selectors.size > 1 ? "criteria" : "criterion"
37
37
  version_or_versions = pluralize("the consumer version", selectors.size)
@@ -572,6 +572,26 @@ module PactBroker
572
572
  }.to_json
573
573
  end
574
574
 
575
+ def fixed_json_content(consumer_name, provider_name, differentiator)
576
+ {
577
+ "consumer" => {
578
+ "name" => consumer_name
579
+ },
580
+ "provider" => {
581
+ "name" => provider_name
582
+ },
583
+ "interactions" => [{
584
+ "request" => {
585
+ "method" => "GET",
586
+ "path" => "/things/#{differentiator}"
587
+ },
588
+ "response" => {
589
+ "status" => 200
590
+ }
591
+ }],
592
+ }.to_json
593
+ end
594
+
575
595
  private
576
596
 
577
597
  def create_pacticipant_version(version_number, pacticipant, params = {})
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = "2.100.0"
2
+ VERSION = "2.101.0"
3
3
  end
@@ -1,12 +1,19 @@
1
1
  require "pact_broker/logging"
2
2
  require "pact_broker/repositories"
3
3
  require "pact_broker/messages"
4
+ require "forwardable"
4
5
 
5
6
  module PactBroker
6
7
  module Versions
7
8
  class BranchService
8
9
  extend PactBroker::Repositories
9
10
 
11
+ class << self
12
+ extend Forwardable
13
+ delegate [:delete_branch_version] => :branch_version_repository
14
+ end
15
+
16
+
10
17
  def self.find_branch_version(pacticipant_name:, branch_name:, version_number:, **)
11
18
  BranchVersion.where(
12
19
  version: PactBroker::Domain::Version.where_pacticipant_name_and_version_number(pacticipant_name, version_number),
@@ -16,6 +16,23 @@ module PactBroker
16
16
  branch_version
17
17
  end
18
18
 
19
+ # Deletes a branch version - that is, removes a version from a branch.
20
+ # Updates the branch head if the deleted branch version was the latest for the branch.
21
+ #
22
+ # @param [PactBroker::Versions::BranchVersion] the branch version to delete
23
+ def delete_branch_version(branch_version)
24
+ latest = branch_version.latest?
25
+ branch = branch_version.latest? ? branch_version.branch : nil
26
+ deleted = branch_version.delete
27
+ if latest
28
+ new_head_branch_version = BranchVersion.find_latest_for_branch(branch)
29
+ if new_head_branch_version
30
+ PactBroker::Versions::BranchHead.new(branch: branch, branch_version: new_head_branch_version).upsert
31
+ end
32
+ end
33
+ deleted
34
+ end
35
+
19
36
  private
20
37
 
21
38
  def find_or_create_branch(pacticipant, branch_name)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a modified version of the Rack::Cascade class from https://github.com/rack/rack/blob/2833813/lib/rack/cascade.rb
4
+ # that short circuts the cascade if a response body is provided for a 404/403 response.
5
+ # This is to allow the UI and API to be in a cascade, but allows the UI
6
+ # to return a 404 page when the route matches, but there is no domain object found.
7
+ # If we don't do this, then the 404 from the UI causes an API response to be returned.
8
+
9
+
10
+ # This does not work. Do not use it. Have not yet worked out why.
11
+
12
+ module Rack
13
+ # Rack::Cascade tries a request on several apps, and returns the
14
+ # first response that is not 404 or 405 (or in a list of configured
15
+ # status codes). If all applications tried return one of the configured
16
+ # status codes, return the last response.
17
+
18
+ module PactBroker
19
+ class Cascade
20
+ # deprecated, no longer used
21
+ NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
22
+
23
+ # An array of applications to try in order.
24
+ attr_reader :apps
25
+
26
+ # Set the apps to send requests to, and what statuses result in
27
+ # cascading. Arguments:
28
+ #
29
+ # apps: An enumerable of rack applications.
30
+ # cascade_for: The statuses to use cascading for. If a response is received
31
+ # from an app, the next app is tried.
32
+ def initialize(apps, cascade_for = [404, 405])
33
+ @apps = []
34
+ apps.each { |app| add app }
35
+
36
+ @cascade_for = {}
37
+ [*cascade_for].each { |status| @cascade_for[status] = true }
38
+ end
39
+
40
+ # Call each app in order. If the responses uses a status that requires
41
+ # cascading, try the next app. If all responses require cascading,
42
+ # return the response from the last app.
43
+ def call(env)
44
+ return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
45
+ result = nil
46
+ last_body = nil
47
+
48
+ @apps.each_with_index do |app, i|
49
+ # The SPEC says that the body must be closed after it has been iterated
50
+ # by the server, or if it is replaced by a middleware action. Cascade
51
+ # replaces the body each time a cascade happens. It is assumed that nil
52
+ # does not respond to close, otherwise the previous application body
53
+ # will be closed. The final application body will not be closed, as it
54
+ # will be passed to the server as a result.
55
+ last_body.close if last_body.respond_to? :close
56
+ result = app.call(env)
57
+
58
+ puts result.last
59
+
60
+ # If it is a 404/403 AND the response body is empty, then try the next app
61
+ if @cascade_for.include?(result[0].to_i) && result[2].respond_to?(:empty?) && result[2].empty?
62
+ last_body = result[2]
63
+ else
64
+ puts "returned from #{i} of #{@apps.size}"
65
+ # otherwise, return the result
66
+ return result
67
+ end
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ # Append an app to the list of apps to cascade. This app will
74
+ # be tried last.
75
+ def add(app)
76
+ @apps << app
77
+ end
78
+
79
+ # Whether the given app is one of the apps to cascade to.
80
+ def include?(app)
81
+ @apps.include?(app)
82
+ end
83
+
84
+ alias_method :<<, :add
85
+ end
86
+ end
87
+ end
@@ -5,6 +5,7 @@ module Webmachine
5
5
 
6
6
  Route = Struct.new(
7
7
  :path,
8
+ :path_spec,
8
9
  :resource_class,
9
10
  :resource_name,
10
11
  :resource_class_location,
@@ -23,6 +24,38 @@ module Webmachine
23
24
  def path_include?(component)
24
25
  path.include?(component)
25
26
  end
27
+
28
+ def route_param_names
29
+ path_spec.select { | component | component.is_a?(Symbol) }
30
+ end
31
+
32
+ # Creates a Webmachine Resource for the given route for use in tests.
33
+ # @param [Hash] env the rack env from which to build the request
34
+ # @param [PactBroker::ApplicationContext] application_context the application context
35
+ # @param [Hash] path_param_values concrete parameter values from which to construct the path
36
+ # @return [Webmachine::Resource] the webmachine resource for the request
37
+ def build_resource(env, application_context, path_param_values)
38
+ path = "/" + path_spec.collect{ | part | part.is_a?(Symbol) ? (path_param_values[part] || "missing-param") : part }.join("/")
39
+
40
+ path_params = route_param_names.each_with_object({}){ | name, new_params | new_params[name] = path_param_values[name] }
41
+ path_info = {
42
+ application_context: application_context,
43
+ resource_name: resource_name
44
+ }.merge(path_params)
45
+
46
+ rack_req = ::Rack::Request.new({ "REQUEST_METHOD" => "GET", "rack.input" => StringIO.new("") }.merge(env) )
47
+ dummy_request = Webmachine::Adapters::Rack::RackRequest.new(
48
+ rack_req.env["REQUEST_METHOD"],
49
+ path,
50
+ Webmachine::Headers.from_cgi({"HTTP_HOST" => "example.org"}.merge(env)),
51
+ Webmachine::Adapters::Rack::RequestBody.new(rack_req),
52
+ {},
53
+ {},
54
+ rack_req.env
55
+ )
56
+ dummy_request.path_info = path_info
57
+ resource_class.new(dummy_request, Webmachine::Response.new)
58
+ end
26
59
  end
27
60
 
28
61
  def self.call(webmachine_applications, search_term: nil)
@@ -36,24 +69,25 @@ module Webmachine
36
69
  end
37
70
 
38
71
  def self.paths_to_resource_class_mappings(webmachine_application)
39
- webmachine_application.routes.collect do | route |
40
- resource_path_absolute = Pathname.new(source_location_for(route.resource))
72
+ webmachine_application.routes.collect do | webmachine_route |
73
+ resource_path_absolute = Pathname.new(source_location_for(webmachine_route.resource))
41
74
  Route.new({
42
- path: "/" + route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
43
- resource_class: route.resource,
44
- resource_name: route.instance_variable_get(:@bindings)[:resource_name],
75
+ path: "/" + webmachine_route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
76
+ path_spec: webmachine_route.path_spec,
77
+ resource_class: webmachine_route.resource,
78
+ resource_name: webmachine_route.instance_variable_get(:@bindings)[:resource_name],
45
79
  resource_class_location: resource_path_absolute.relative_path_from(Pathname.pwd).to_s
46
- }.merge(info_from_resource_instance(route)))
47
- end
80
+ }.merge(info_from_resource_instance(webmachine_route)))
81
+ end.reject{ | route | route.resource_class == Webmachine::Trace::TraceResource }
48
82
  end
49
83
 
50
- def self.info_from_resource_instance(route)
84
+ def self.info_from_resource_instance(webmachine_route)
51
85
  with_no_logging do
52
86
  path_info = { application_context: OpenStruct.new, pacticipant_name: "foo", pacticipant_version_number: "1", resource_name: "foo" }
53
87
  path_info.default = "1"
54
88
  dummy_request = Webmachine::Adapters::Rack::RackRequest.new("GET", "/", Webmachine::Headers["host" => "example.org"], nil, {}, {}, { "REQUEST_METHOD" => "GET" })
55
89
  dummy_request.path_info = path_info
56
- dummy_resource = route.resource.new(dummy_request, Webmachine::Response.new)
90
+ dummy_resource = webmachine_route.resource.new(dummy_request, Webmachine::Response.new)
57
91
  if dummy_resource
58
92
  {
59
93
  allowed_methods: dummy_resource.allowed_methods,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact_broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.100.0
4
+ version: 2.101.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bethany Skurrie
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-05-20 00:00:00.000000000 Z
13
+ date: 2022-06-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httparty
@@ -586,6 +586,7 @@ files:
586
586
  - db/migrations/20220303_increase_consumer_version_selector_hashes_column_size.rb
587
587
  - db/migrations/migration_helper.rb
588
588
  - docs/CONFIGURATION.md
589
+ - docs/api/PACTICIPANTS.md
589
590
  - docs/api/WEBHOOKS.md
590
591
  - lib/db.rb
591
592
  - lib/pact/doc/README.md
@@ -705,7 +706,6 @@ files:
705
706
  - lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb
706
707
  - lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb
707
708
  - lib/pact_broker/api/resources/dashboard.rb
708
- - lib/pact_broker/api/resources/default_base_resource.rb
709
709
  - lib/pact_broker/api/resources/deployed_version.rb
710
710
  - lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb
711
711
  - lib/pact_broker/api/resources/environment.rb
@@ -1076,6 +1076,7 @@ files:
1076
1076
  - lib/rack/hal_browser/redirect.rb
1077
1077
  - lib/rack/pact_broker/add_pact_broker_version_header.rb
1078
1078
  - lib/rack/pact_broker/add_vary_header.rb
1079
+ - lib/rack/pact_broker/cascade.rb
1079
1080
  - lib/rack/pact_broker/configurable_make_it_later.rb
1080
1081
  - lib/rack/pact_broker/convert_404_to_hal.rb
1081
1082
  - lib/rack/pact_broker/convert_file_extension_to_accept_header.rb
@@ -1222,7 +1223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1222
1223
  - !ruby/object:Gem::Version
1223
1224
  version: '0'
1224
1225
  requirements: []
1225
- rubygems_version: 3.3.14
1226
+ rubygems_version: 3.3.15
1226
1227
  signing_key:
1227
1228
  specification_version: 4
1228
1229
  summary: See description