pact_broker 2.120.0 → 2.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +552 -482
  3. data/Gemfile +0 -6
  4. data/README.md +0 -1
  5. data/docs/CONFIGURATION.md +29 -2
  6. data/docs/matrix-optimisation/README.md +85 -0
  7. data/lib/pact/doc/interaction_view_model.rb +2 -1
  8. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +3 -1
  9. data/lib/pact/doc.rb +5 -0
  10. data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +2 -0
  11. data/lib/pact_broker/api/decorators/provider_states_decorator.rb +1 -1
  12. data/lib/pact_broker/api/decorators/version_decorator.rb +7 -2
  13. data/lib/pact_broker/api/pact_broker_urls.rb +4 -0
  14. data/lib/pact_broker/api/resources/deployed_or_released_versions.rb +78 -0
  15. data/lib/pact_broker/api/resources/environment_versions.rb +89 -0
  16. data/lib/pact_broker/api/resources/tag_versions.rb +1 -1
  17. data/lib/pact_broker/api.rb +2 -0
  18. data/lib/pact_broker/app.rb +0 -1
  19. data/lib/pact_broker/config/runtime_configuration.rb +19 -0
  20. data/lib/pact_broker/configuration.rb +15 -2
  21. data/lib/pact_broker/dataset.rb +4 -23
  22. data/lib/pact_broker/db/clean_incremental.rb +4 -8
  23. data/lib/pact_broker/db.rb +0 -7
  24. data/lib/pact_broker/domain/version.rb +6 -2
  25. data/lib/pact_broker/initializers/database_connection.rb +5 -0
  26. data/lib/pact_broker/integrations/repository.rb +1 -1
  27. data/lib/pact_broker/integrations/service.rb +1 -3
  28. data/lib/pact_broker/logging/open_telemetry_appender_setup.rb +73 -0
  29. data/lib/pact_broker/logging/trace_aware_json_formatter.rb +38 -0
  30. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +29 -11
  31. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +23 -12
  32. data/lib/pact_broker/matrix/selector_resolver.rb +30 -2
  33. data/lib/pact_broker/pacticipants/repository.rb +4 -1
  34. data/lib/pact_broker/pacts/service.rb +2 -2
  35. data/lib/pact_broker/tags/service.rb +4 -5
  36. data/lib/pact_broker/verifications/repository.rb +1 -1
  37. data/lib/pact_broker/version.rb +1 -1
  38. data/lib/pact_broker/versions/branch_version_repository.rb +3 -3
  39. data/lib/pact_broker/versions/repository.rb +4 -13
  40. data/lib/pact_broker/webhooks/render.rb +1 -1
  41. data/lib/sequel/plugins/upsert.rb +1 -8
  42. data/pact_broker.gemspec +2 -2
  43. metadata +15 -6
data/Gemfile CHANGED
@@ -5,8 +5,6 @@ gemspec
5
5
 
6
6
  gem "rake", "~> 13.3"
7
7
  gem "sqlite3", ">=2.0.0"
8
- gem "conventional-changelog", "~>1.3"
9
- gem "bump", "~> 0.5"
10
8
  gem "padrino-core", ">= 0.16.0.pre3", require: false
11
9
  gem "rackup", "~> 2.2"
12
10
  gem "thor", "~> 1.4" # thor is secondary dependency but bumping here to avoid CVEs
@@ -49,10 +47,6 @@ group :pg, optional: true do
49
47
  gem "pg", "~>1.6"
50
48
  end
51
49
 
52
- group :mysql, optional: true do
53
- gem "mysql2", "~>0.5"
54
- end
55
-
56
50
  if ENV["X_PACT_DEVELOPMENT"] == "true"
57
51
  gem "pact-support", path: "../pact-support"
58
52
  end
data/README.md CHANGED
@@ -151,7 +151,6 @@ You can use the [Pact Broker Docker image][docker] or [Terraform on AWS][terrafo
151
151
  * 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.
152
152
  * Create a PostgreSQL database.
153
153
  * To ensure you're on a supported version of the database that you choose, check the [.github/workflows/test.yml](.github/workflows/test.yml) file to see which versions we're currently running our tests against.
154
- * 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).
155
154
  * 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).
156
155
  * Install ruby 2.7 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?)
157
156
  * 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 if you are not using a containerized solution.
@@ -34,6 +34,25 @@ The application log format. Can be any value supported by Semantic Logger.
34
34
  **Allowed values:** `default`, `json`, `color`<br/>
35
35
  **More information:** https://github.com/rocketjob/semantic_logger/tree/master/lib/semantic_logger/formatters<br/>
36
36
 
37
+ ### log_otel_enabled
38
+
39
+ Controls whether logs are also emitted to the OpenTelemetry logs pipeline.
40
+
41
+ `auto` (the default) adds the OpenTelemetry appender only when the
42
+ `opentelemetry-logs-sdk` gem is installed and an OpenTelemetry logger provider
43
+ is configured. `true` forces the appender on and raises an error if the gem is
44
+ not available. `false` disables it entirely.
45
+
46
+ The `opentelemetry-logs-sdk` gem is an optional, runtime-detected dependency and
47
+ is not bundled with the Pact Broker. When `log_format` is set to `json`, active
48
+ trace and span IDs are also embedded in the JSON log output.
49
+
50
+ **Supported versions:** From v2.120.0<br/>
51
+ **Environment variable name:** `PACT_BROKER_LOG_OTEL_ENABLED`<br/>
52
+ **YAML configuration key name:** `log_otel_enabled`<br/>
53
+ **Default:** `auto`<br/>
54
+ **Allowed values:** `auto`, `true`, `false`<br/>
55
+
37
56
  ### log_dir
38
57
 
39
58
  The log file directory
@@ -248,8 +267,8 @@ The number of seconds after which an SQL query will be aborted. Only supported f
248
267
 
249
268
  **Environment variable name:** `PACT_BROKER_DATABASE_STATEMENT_TIMEOUT`<br/>
250
269
  **YAML configuration key name:** `database_statement_timeout`<br/>
251
- **Default:** `15000`<br/>
252
- **Allowed values:** A positive integer in milliseconds<br/>
270
+ **Default:** `15`<br/>
271
+ **Allowed values:** A positive integer or float.<br/>
253
272
  **More information:** https://www.postgresql.org/docs/9.3/runtime-config-client.html<br/>
254
273
 
255
274
  ### metrics_sql_statement_timeout
@@ -533,6 +552,14 @@ The URL of the shields.io server used to generate the README badges.
533
552
  **Default:** `https://img.shields.io`<br/>
534
553
  **More information:** https://shields.io<br/>
535
554
 
555
+ ### badge_default_cache_setting
556
+
557
+ Cache Control header value for the badge, this sets the cache-control header for the badge image, for more information on header formats see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/cache-control
558
+
559
+ **Environment variable name:** `PACT_BROKER_BADGE_DEFAULT_CACHE_SETTING`<br/>
560
+ **YAML configuration key name:** `badge_default_cache_setting`<br/>
561
+ **Default:** `max-age=30`<br/>
562
+
536
563
  ### badge_provider_mode
537
564
 
538
565
  The method by which the badges are generated. When set to `redirect`, a request to the Pact Broker for a badge will be sent a redirect response
@@ -0,0 +1,85 @@
1
+ # Matrix query baselines
2
+
3
+ Tooling to capture a query-profile baseline (per-request query count, exact SQL,
4
+ and `EXPLAIN` plans) for the `/matrix` and `/can-i-deploy` endpoints, so query
5
+ cost can be compared before and after changes to the query engine.
6
+
7
+ The generated baseline files (`baseline-*.md`) are **git-ignored** — generate them
8
+ locally and diff two runs; they are not committed.
9
+
10
+ ## Generate a baseline (Postgres)
11
+
12
+ Baselines are generated against **Postgres** (the production backend). Start a
13
+ throwaway Postgres with the dev compose file:
14
+
15
+ docker compose -f docker-compose-dev-postgres.yml up -d postgres
16
+
17
+ The Postgres version is controlled by `POSTGRES_VERSION` (default `15`),
18
+ so a baseline can be captured against a specific major version:
19
+
20
+ POSTGRES_VERSION=14 docker compose -f docker-compose-dev-postgres.yml up -d postgres
21
+
22
+ The `pg` driver lives in an optional bundle group, so it must be installed and
23
+ enabled explicitly — without `BUNDLE_WITH=pg` the run fails with
24
+ `LoadError: cannot load such file -- pg`:
25
+
26
+ BUNDLE_WITH=pg bundle install
27
+
28
+ Point the suite at the database and run the generator:
29
+
30
+ BUNDLE_WITH=pg \
31
+ PACT_BROKER_TEST_DATABASE_URL="postgres://postgres:postgres@localhost:5432/postgres" \
32
+ bundle exec rspec spec/benchmarks/matrix_baseline_spec.rb --tag matrix_baseline
33
+
34
+ This writes `docs/matrix-optimisation/baseline-postgres.md`. Tear the container down
35
+ with `docker compose -f docker-compose-dev-postgres.yml down -v` when done.
36
+
37
+ > Postgres 18 and later relocated the data directory, which is incompatible with
38
+ > the `/var/lib/postgresql/data` volume mount in `docker-compose-dev-postgres.yml`;
39
+ > the container exits on startup. Use a tag of `17` or lower until that mount is
40
+ > updated.
41
+
42
+ The generator is tagged `matrix_baseline` and excluded from the default test run;
43
+ it only executes when invoked with `--tag matrix_baseline`. Against a non-Postgres
44
+ database it skips with a message.
45
+
46
+ ## What's captured
47
+
48
+ For each request shape (see `spec/support/matrix_baseline/shapes.rb`), the file
49
+ records, inside collapsible sections:
50
+
51
+ - the number of SQL statements the request issued (main query + eager loads);
52
+ - the exact SQL of each statement;
53
+ - its `EXPLAIN` plan.
54
+
55
+ Plans are captured with `EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)`. That keeps the
56
+ measures of work done — estimated cost, actual row counts, buffer hit/read
57
+ counts, sort and hash memory usage — and drops the measures of how fast this
58
+ particular machine did it. Wall-clock figures are excluded deliberately: they
59
+ vary with unrelated load and would swamp the diff.
60
+
61
+ ## Comparing runs
62
+
63
+ **Two runs against unchanged code produce a byte-identical file.** Any diff at
64
+ all is a real difference in what the query engine did. Regenerate and diff two
65
+ `baseline-postgres.md` files to compare:
66
+
67
+ - **query count** per shape (the `N queries` in each section heading);
68
+ - **plan shape** — sequential vs index scans, sorts, join types, UNION branch count;
69
+ - **estimated cost, actual rows, buffers and memory usage** per plan node.
70
+
71
+ Determinism depends on the generator resetting the database itself — it
72
+ truncates with `RESTART IDENTITY` so surrogate keys in the captured SQL are
73
+ stable, and runs `VACUUM ANALYZE` so plans are chosen against real statistics
74
+ on an unbloated heap. That is why it is tagged `no_db_clean` and will wipe the
75
+ database it is pointed at. **Point it at a throwaway Postgres, never at a
76
+ database whose contents you want to keep.**
77
+
78
+ ## Limitations
79
+
80
+ - The seed is a few hundred rows per table. Postgres will often choose a
81
+ sequential scan at this size regardless of index quality, so a plan-shape
82
+ change here does not automatically imply the same change in production. Query
83
+ counts and join structure transfer; scan choice on small tables may not.
84
+ - The success filter and can-i-deploy's `--ignore` are applied in Ruby after
85
+ the query, so their shapes exercise no extra SQL beyond selector resolution.
@@ -1,6 +1,7 @@
1
1
  require "pact/shared/active_support_support"
2
2
  require "pact/reification"
3
3
  require "cgi"
4
+ require "pact/doc"
4
5
 
5
6
  module Pact
6
7
  module Doc
@@ -149,7 +150,7 @@ module Pact
149
150
 
150
151
  def markdown_escape string
151
152
  return nil unless string
152
- string.gsub("*","\\*").gsub("_","\\_")
153
+ string.gsub(Pact::Doc::MARKDOWN_SPECIAL_CHARS_REGEXP) { |char| "\\#{char}" }
153
154
  end
154
155
  end
155
156
  end
@@ -1,6 +1,7 @@
1
1
  require "pact/doc/markdown/interaction_renderer"
2
2
  require "pact/doc/sort_interactions"
3
3
  require "rack/utils"
4
+ require "pact/doc"
4
5
 
5
6
  module Pact
6
7
  module Doc
@@ -60,7 +61,8 @@ module Pact
60
61
  end
61
62
 
62
63
  def markdown_escape string
63
- string.gsub("*","\\*").gsub("_","\\_")
64
+ return nil unless string
65
+ string.gsub(Pact::Doc::MARKDOWN_SPECIAL_CHARS_REGEXP) { |char| "\\#{char}" }
64
66
  end
65
67
 
66
68
  def h(text)
data/lib/pact/doc.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Pact
2
+ module Doc
3
+ MARKDOWN_SPECIAL_CHARS_REGEXP = /[\\`*_\[\](){}#+\-.!|]/
4
+ end
5
+ end
@@ -10,6 +10,8 @@ module PactBroker
10
10
  required(:name_in_pact).maybe(:string)
11
11
  end
12
12
 
13
+ rule(:name).validate(:not_blank_if_present)
14
+
13
15
  rule(:name, :name_in_pact) do
14
16
  if name_in_pact_does_not_match_name_in_url_path?(values)
15
17
  key.failure(validation_message("pact_name_in_path_mismatch_name_in_pact", name_in_pact: values[:name_in_pact], name_in_path: values[:name]))
@@ -26,7 +26,7 @@ module PactBroker
26
26
  end
27
27
 
28
28
  consumers_map.map do |(name, params), consumers|
29
- { name: name, params: params, consumers: consumers.uniq }.transform_keys(&:to_sym)
29
+ { name: name, params: params, consumers: consumers.uniq.sort }
30
30
  end
31
31
  .sort_by { |provider_state| provider_state[:name] }
32
32
  }, :extend => PactBroker::Api::Decorators::ProviderStateDecorator
@@ -1,6 +1,7 @@
1
1
  require_relative "base_decorator"
2
2
  require_relative "embedded_tag_decorator"
3
3
  require_relative "embedded_branch_version_decorator"
4
+ require_relative "format_date_time"
4
5
 
5
6
  module PactBroker
6
7
  module Api
@@ -85,7 +86,9 @@ module PactBroker
85
86
  title: "Version deployed to #{deployed_version.environment.display_name}",
86
87
  name: deployed_version.environment.display_name,
87
88
  href: deployed_version_url(deployed_version, context.fetch(:base_url)),
88
- currently_deployed: deployed_version.currently_deployed
89
+ currently_deployed: deployed_version.currently_deployed,
90
+ deployed_at: FormatDateTime.call(deployed_version.created_at),
91
+ undeployed_at: FormatDateTime.call(deployed_version.undeployed_at)
89
92
  }.tap do |hash|
90
93
  hash[:application_instance] = deployed_version.application_instance unless deployed_version.application_instance.nil?
91
94
  end
@@ -104,7 +107,9 @@ module PactBroker
104
107
  title: "Version released to #{released_version.environment.display_name}",
105
108
  name: released_version.environment.display_name,
106
109
  href: released_version_url(released_version, context.fetch(:base_url)),
107
- currently_supported: released_version.currently_supported
110
+ currently_supported: released_version.currently_supported,
111
+ released_at: FormatDateTime.call(released_version.created_at),
112
+ support_ended_at: FormatDateTime.call(released_version.support_ended_at)
108
113
  }
109
114
  end
110
115
  end
@@ -370,6 +370,10 @@ module PactBroker
370
370
  "#{environments_url(base_url)}/#{environment.uuid}"
371
371
  end
372
372
 
373
+ def environment_versions_url(pacticipant, environment, base_url = "")
374
+ "#{pacticipant_url(base_url, pacticipant)}/environments/#{environment.uuid}/versions"
375
+ end
376
+
373
377
  def deployed_versions_for_version_and_environment_url(version, environment, base_url = "")
374
378
  "#{version_url(base_url, version)}/deployed-versions/environment/#{environment.uuid}"
375
379
  end
@@ -0,0 +1,78 @@
1
+ require "pact_broker/api/resources/base_resource"
2
+ require "pact_broker/api/decorators/versions_decorator"
3
+ require "pact_broker/api/resources/pagination_methods"
4
+
5
+ module PactBroker
6
+ module Api
7
+ module Resources
8
+ class DeployedOrReleasedVersions < BaseResource
9
+ include PaginationMethods
10
+
11
+ def content_types_provided
12
+ [["application/hal+json", :to_json]]
13
+ end
14
+
15
+ def allowed_methods
16
+ ["GET", "OPTIONS"]
17
+ end
18
+
19
+ def malformed_request?
20
+ super || request.get? && validation_errors_for_schema?(schema, request.query)
21
+ end
22
+
23
+ def resource_exists?
24
+ !!pacticipant
25
+ end
26
+
27
+ def to_json
28
+ decorator_class(:versions_decorator).new(versions).to_json(
29
+ **decorator_options(
30
+ identifier_from_path.merge(
31
+ resource_title: "Versions of #{pacticipant_name} that have been deployed or released",
32
+ deployed_versions: deployed_versions,
33
+ released_versions: released_versions
34
+ )
35
+ )
36
+ )
37
+ end
38
+
39
+ def policy_name
40
+ :'versions::versions'
41
+ end
42
+
43
+ private
44
+
45
+ def versions
46
+ @versions ||= version_service.find_by_ids_in_reverse_order(
47
+ version_ids,
48
+ pagination_options,
49
+ decorator_class(:versions_decorator).eager_load_associations
50
+ )
51
+ end
52
+
53
+ def version_ids
54
+ PactBroker::Deployments::DeployedVersion
55
+ .for_pacticipant_name(pacticipant_name)
56
+ .select(:version_id)
57
+ .union(
58
+ PactBroker::Deployments::ReleasedVersion
59
+ .for_pacticipant_name(pacticipant_name)
60
+ .select(:version_id)
61
+ )
62
+ end
63
+
64
+ def deployed_versions
65
+ @deployed_versions ||= deployed_version_service.find_deployed_versions_for_versions(versions)
66
+ end
67
+
68
+ def released_versions
69
+ @released_versions ||= released_version_service.find_released_versions_for_versions(versions)
70
+ end
71
+
72
+ def schema
73
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema if request.get?
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,89 @@
1
+ require "pact_broker/api/resources/base_resource"
2
+ require "pact_broker/api/decorators/versions_decorator"
3
+ require "pact_broker/api/resources/pagination_methods"
4
+
5
+ module PactBroker
6
+ module Api
7
+ module Resources
8
+ class EnvironmentVersions < BaseResource
9
+ include PaginationMethods
10
+
11
+ def content_types_provided
12
+ [["application/hal+json", :to_json]]
13
+ end
14
+
15
+ def allowed_methods
16
+ ["GET", "OPTIONS"]
17
+ end
18
+
19
+ def malformed_request?
20
+ super || request.get? && validation_errors_for_schema?(schema, request.query)
21
+ end
22
+
23
+ def resource_exists?
24
+ !!environment && !!pacticipant
25
+ end
26
+
27
+ def to_json
28
+ decorator_class(:versions_decorator).new(versions).to_json(
29
+ **decorator_options(
30
+ identifier_from_path.merge(
31
+ resource_title: resource_title,
32
+ deployed_versions: deployed_versions,
33
+ released_versions: released_versions
34
+ )
35
+ )
36
+ )
37
+ end
38
+
39
+ def policy_name
40
+ :'versions::versions'
41
+ end
42
+
43
+ private
44
+
45
+ def versions
46
+ @versions ||= version_service.find_by_ids_in_reverse_order(
47
+ version_ids,
48
+ pagination_options,
49
+ decorator_class(:versions_decorator).eager_load_associations
50
+ )
51
+ end
52
+
53
+ def version_ids
54
+ deployed_ids = PactBroker::Deployments::DeployedVersion
55
+ .for_environment(environment)
56
+ .for_pacticipant_name(pacticipant_name)
57
+ .select_map(:version_id)
58
+
59
+ released_ids = PactBroker::Deployments::ReleasedVersion
60
+ .for_environment(environment)
61
+ .for_pacticipant_name(pacticipant_name)
62
+ .select_map(:version_id)
63
+
64
+ (deployed_ids + released_ids).uniq
65
+ end
66
+
67
+ def deployed_versions
68
+ @deployed_versions ||= deployed_version_service.find_deployed_versions_for_versions(versions)
69
+ end
70
+
71
+ def released_versions
72
+ @released_versions ||= released_version_service.find_released_versions_for_versions(versions)
73
+ end
74
+
75
+ def environment
76
+ @environment ||= environment_service.find(identifier_from_path[:environment_uuid])
77
+ end
78
+
79
+ def resource_title
80
+ "Versions for #{pacticipant.name} in environment #{environment.display_name}"
81
+ end
82
+
83
+ def schema
84
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema if request.get?
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -30,7 +30,7 @@ module PactBroker
30
30
 
31
31
  def to_json
32
32
  decorator_class(:versions_decorator).new(tag_versions)
33
- .to_json(**decorator_options(deployed_versions: deployed_versions, released_versions: released_versions))
33
+ .to_json(**decorator_options(identifier_from_path.merge(deployed_versions: deployed_versions, released_versions: released_versions)))
34
34
  end
35
35
 
36
36
  private
@@ -104,7 +104,9 @@ module PactBroker
104
104
 
105
105
  # Versions
106
106
  add ["pacticipants", :pacticipant_name, "versions"], Api::Resources::Versions, {resource_name: "pacticipant_versions"}
107
+ add ["pacticipants", :pacticipant_name, "deployed-or-released", "versions"], Api::Resources::DeployedOrReleasedVersions, {resource_name: "pacticipant_deployed_or_released_versions"}
107
108
  add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions"], Api::Resources::BranchVersions, {resource_name: "pacticipant_branch_versions"}
109
+ add ["pacticipants", :pacticipant_name, "environments", :environment_uuid, "versions"], Api::Resources::EnvironmentVersions, {resource_name: "pacticipant_environment_versions"}
108
110
  add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number], Api::Resources::Version, {resource_name: "pacticipant_version"}
109
111
  add ["pacticipants", :pacticipant_name, "latest-version", :tag], Api::Resources::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"}
110
112
  add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to], Api::Resources::CanIDeployPacticipantVersionByTagToTag, { resource_name: "can_i_deploy_latest_tagged_version_to_tag" }
@@ -148,7 +148,6 @@ module PactBroker
148
148
  configuration.database_connection ||= PactBroker.create_database_connection(configuration.database_configuration, configuration.logger)
149
149
  PactBroker::DB.connection = configuration.database_connection
150
150
  PactBroker::DB.validate_connection_config if configuration.validate_database_connection_config
151
- PactBroker::DB.set_mysql_strict_mode_if_mysql
152
151
  PactBroker::DB.connection.extension(:pagination)
153
152
  PactBroker::DB.connection.extension(:statement_timeout)
154
153
  PactBroker::DB.connection.extension(:any_not_empty)
@@ -40,6 +40,7 @@ module PactBroker
40
40
  log_stream: :file,
41
41
  log_level: :info,
42
42
  log_format: nil,
43
+ log_otel_enabled: :auto,
43
44
  warning_error_class_names: ["Sequel::ForeignKeyConstraintViolation"],
44
45
  hide_pactflow_messages: false,
45
46
  log_configuration_on_startup: true,
@@ -129,6 +130,10 @@ module PactBroker
129
130
  super(log_format&.to_sym)
130
131
  end
131
132
 
133
+ def log_otel_enabled= value
134
+ super(coerce_log_otel_enabled(value))
135
+ end
136
+
132
137
  def custom_log_formatters= custom_log_formatters
133
138
  super(custom_log_formatters&.symbolize_keys)
134
139
  end
@@ -215,12 +220,26 @@ module PactBroker
215
220
  if log_stream == :file && log_dir.blank?
216
221
  raise_validation_error("Must specify log_dir if log_stream is set to file")
217
222
  end
223
+
224
+ unless [:auto, true, false].include?(log_otel_enabled)
225
+ raise_validation_error("log_otel_enabled must be one of: auto, true, false")
226
+ end
218
227
  end
219
228
 
220
229
  def raise_validation_error(msg)
221
230
  raise PactBroker::ConfigurationError, msg
222
231
  end
223
232
 
233
+ def coerce_log_otel_enabled(value)
234
+ case value
235
+ when nil, "", "auto", :auto then :auto
236
+ when true, "true", "1", 1 then true
237
+ when false, "false", "0", 0 then false
238
+ else value
239
+ end
240
+ end
241
+ private :coerce_log_otel_enabled
242
+
224
243
  def set_webhook_attribute_defaults
225
244
  # can't set a default on this, or anyway config blows up when trying to merge the
226
245
  # hash from the env vars into an array/nil.
@@ -3,6 +3,8 @@ require "pact_broker/error"
3
3
  require "semantic_logger"
4
4
  require "forwardable"
5
5
  require "pact_broker/config/runtime_configuration"
6
+ require "pact_broker/logging/open_telemetry_appender_setup"
7
+ require "pact_broker/logging/trace_aware_json_formatter"
6
8
  require "anyway/auto_cast"
7
9
  require "request_store"
8
10
 
@@ -98,17 +100,28 @@ module PactBroker
98
100
  def logger_from_runtime_configuration
99
101
  @logger_from_runtime_configuration ||= begin
100
102
  SemanticLogger.default_level = runtime_configuration.log_level
103
+ formatter = resolve_log_formatter(runtime_configuration.log_format)
101
104
  if runtime_configuration.log_stream == :file
102
105
  path = runtime_configuration.log_dir + "/pact_broker.log"
103
106
  FileUtils.mkdir_p(runtime_configuration.log_dir)
104
- @default_appender = SemanticLogger.add_appender(file_name: path, formatter: runtime_configuration.log_format)
107
+ @default_appender = SemanticLogger.add_appender(file_name: path, formatter: formatter)
105
108
  else
106
- @default_appender = SemanticLogger.add_appender(io: $stdout, formatter: runtime_configuration.log_format)
109
+ @default_appender = SemanticLogger.add_appender(io: $stdout, formatter: formatter)
107
110
  end
111
+ PactBroker::Logging::OpenTelemetryAppenderSetup.call(runtime_configuration)
108
112
  @logger_from_runtime_configuration = SemanticLogger["pact-broker"]
109
113
  end
110
114
  end
111
115
 
116
+ def resolve_log_formatter(log_format)
117
+ if log_format == :json
118
+ PactBroker::Logging::TraceAwareJsonFormatter.new
119
+ else
120
+ log_format
121
+ end
122
+ end
123
+ private :resolve_log_formatter
124
+
112
125
  def logger
113
126
  custom_logger || logger_from_runtime_configuration
114
127
  end
@@ -8,10 +8,6 @@ module PactBroker
8
8
  module Helpers
9
9
  extend self
10
10
 
11
- def mysql?
12
- Sequel::Model.db.adapter_scheme.to_s =~ /mysql/
13
- end
14
-
15
11
  def postgres?
16
12
  Sequel::Model.db.adapter_scheme.to_s =~ /postgres/
17
13
  end
@@ -32,14 +28,9 @@ module PactBroker
32
28
 
33
29
  def name_like column_name, value
34
30
  if PactBroker.configuration.use_case_sensitive_resource_names
35
- if mysql?
36
- # sigh, mysql, this is the only way to perform a case sensitive search
37
- Sequel.like(column_name, value.gsub("_", "\\_"), { case_insensitive: false })
38
- else
39
- { column_name => value }
40
- end
31
+ { column_name => value }
41
32
  else
42
- Sequel.like(column_name, value.gsub("_", "\\_"), { case_insensitive: true })
33
+ Sequel.like(column_name, escape_wildcards(value), { case_insensitive: true })
43
34
  end
44
35
  end
45
36
 
@@ -82,12 +73,7 @@ module PactBroker
82
73
  end
83
74
 
84
75
  def select_for_subquery column
85
- if mysql? #stoopid mysql doesn't allow you to modify datasets with subqueries
86
- column_name = column.respond_to?(:alias) ? column.alias : column
87
- select(column).collect{ | it | it[column_name] }
88
- else
89
- select(column)
90
- end
76
+ select(column)
91
77
  end
92
78
 
93
79
  def no_columns_selected?
@@ -109,12 +95,7 @@ module Sequel
109
95
  # This has been used inconsistently, and in the next major version, support for case insensitive names will be dropped.
110
96
  def self.name_like(column_name, value)
111
97
  if PactBroker.configuration.use_case_sensitive_resource_names
112
- if PactBroker::Dataset::Helpers.mysql?
113
- # sigh, mysql, this is the only way to perform a case sensitive search
114
- Sequel.like(column_name, PactBroker::Dataset::Helpers.escape_wildcards(value), { case_insensitive: false })
115
- else
116
- { column_name => value }
117
- end
98
+ { column_name => value }
118
99
  else
119
100
  Sequel.like(column_name, PactBroker::Dataset::Helpers.escape_wildcards(value), { case_insensitive: true })
120
101
  end
@@ -46,9 +46,9 @@ module PactBroker
46
46
  def execute_clean
47
47
  db.transaction do
48
48
  before_counts = current_counts
49
+ delete_stale_branches
49
50
  PactBroker::Domain::Version.where(id: versions_to_delete.from_self.select_map(:id)).delete
50
51
  delete_orphan_pact_versions
51
- delete_stale_branches
52
52
  after_counts = current_counts
53
53
 
54
54
  TABLES.each_with_object({}) do | table_name, comparison_counts |
@@ -116,8 +116,9 @@ module PactBroker
116
116
  def delete_stale_branches
117
117
  return unless keep_branches && !keep_branches.empty?
118
118
 
119
- # A direct subquery here fails in MySQL, so wrap the dataset in a
120
- # derived table with `from_self` before selecting the ids for deletion.
119
+ # Wrap the dataset in a derived table with `from_self` before selecting
120
+ # the ids for deletion, to avoid referencing the target table directly
121
+ # in the subquery.
121
122
  PactBroker::Versions::Branch.where(id: stale_branch_ids_to_delete.from_self.select(:id)).delete
122
123
  end
123
124
 
@@ -156,11 +157,6 @@ module PactBroker
156
157
 
157
158
  def delete_orphan_pact_versions
158
159
  db[:pact_versions].where(id: orphan_pact_versions).delete
159
- rescue Sequel::DatabaseError => e
160
- raise unless e.cause.class.name == "Mysql2::Error"
161
-
162
- ids = orphan_pact_versions.map { |row| row[:id] }
163
- db[:pact_versions].where(id: ids).delete
164
160
  end
165
161
 
166
162
  def orphan_pact_versions
@@ -57,12 +57,5 @@ module PactBroker
57
57
  PactBroker::DB::ValidateEncoding.(connection)
58
58
  end
59
59
 
60
- def self.set_mysql_strict_mode_if_mysql
61
- connection.run("SET sql_mode='STRICT_TRANS_TABLES';") if mysql?
62
- end
63
-
64
- def self.mysql?
65
- connection.adapter_scheme =~ /mysql/
66
- end
67
60
  end
68
61
  end