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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +552 -482
- data/Gemfile +0 -6
- data/README.md +0 -1
- data/docs/CONFIGURATION.md +29 -2
- data/docs/matrix-optimisation/README.md +85 -0
- data/lib/pact/doc/interaction_view_model.rb +2 -1
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +3 -1
- data/lib/pact/doc.rb +5 -0
- data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +2 -0
- data/lib/pact_broker/api/decorators/provider_states_decorator.rb +1 -1
- data/lib/pact_broker/api/decorators/version_decorator.rb +7 -2
- data/lib/pact_broker/api/pact_broker_urls.rb +4 -0
- data/lib/pact_broker/api/resources/deployed_or_released_versions.rb +78 -0
- data/lib/pact_broker/api/resources/environment_versions.rb +89 -0
- data/lib/pact_broker/api/resources/tag_versions.rb +1 -1
- data/lib/pact_broker/api.rb +2 -0
- data/lib/pact_broker/app.rb +0 -1
- data/lib/pact_broker/config/runtime_configuration.rb +19 -0
- data/lib/pact_broker/configuration.rb +15 -2
- data/lib/pact_broker/dataset.rb +4 -23
- data/lib/pact_broker/db/clean_incremental.rb +4 -8
- data/lib/pact_broker/db.rb +0 -7
- data/lib/pact_broker/domain/version.rb +6 -2
- data/lib/pact_broker/initializers/database_connection.rb +5 -0
- data/lib/pact_broker/integrations/repository.rb +1 -1
- data/lib/pact_broker/integrations/service.rb +1 -3
- data/lib/pact_broker/logging/open_telemetry_appender_setup.rb +73 -0
- data/lib/pact_broker/logging/trace_aware_json_formatter.rb +38 -0
- data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +29 -11
- data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +23 -12
- data/lib/pact_broker/matrix/selector_resolver.rb +30 -2
- data/lib/pact_broker/pacticipants/repository.rb +4 -1
- data/lib/pact_broker/pacts/service.rb +2 -2
- data/lib/pact_broker/tags/service.rb +4 -5
- data/lib/pact_broker/verifications/repository.rb +1 -1
- data/lib/pact_broker/version.rb +1 -1
- data/lib/pact_broker/versions/branch_version_repository.rb +3 -3
- data/lib/pact_broker/versions/repository.rb +4 -13
- data/lib/pact_broker/webhooks/render.rb +1 -1
- data/lib/sequel/plugins/upsert.rb +1 -8
- data/pact_broker.gemspec +2 -2
- 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.
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -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:** `
|
|
252
|
-
**Allowed values:** A positive integer
|
|
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(
|
|
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
|
|
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
|
@@ -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 }
|
|
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
|
data/lib/pact_broker/api.rb
CHANGED
|
@@ -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" }
|
data/lib/pact_broker/app.rb
CHANGED
|
@@ -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:
|
|
107
|
+
@default_appender = SemanticLogger.add_appender(file_name: path, formatter: formatter)
|
|
105
108
|
else
|
|
106
|
-
@default_appender = SemanticLogger.add_appender(io: $stdout, formatter:
|
|
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
|
data/lib/pact_broker/dataset.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
120
|
-
#
|
|
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
|
data/lib/pact_broker/db.rb
CHANGED
|
@@ -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
|