pact_broker 2.69.0 → 2.70.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +62 -0
  3. data/CHANGELOG.md +16 -0
  4. data/DEVELOPER_SETUP.md +6 -0
  5. data/Dockerfile +4 -3
  6. data/Gemfile +4 -0
  7. data/ISSUES.md +23 -0
  8. data/config/database.yml +14 -0
  9. data/docker-compose-issue-repro.yml +11 -7
  10. data/lib/pact_broker/api/resources/clean.rb +36 -0
  11. data/lib/pact_broker/api/resources/group.rb +5 -1
  12. data/lib/pact_broker/db/clean.rb +4 -1
  13. data/lib/pact_broker/db/clean_incremental.rb +78 -0
  14. data/lib/pact_broker/domain/tag.rb +69 -8
  15. data/lib/pact_broker/domain/verification.rb +6 -0
  16. data/lib/pact_broker/domain/version.rb +35 -1
  17. data/lib/pact_broker/logging.rb +7 -0
  18. data/lib/pact_broker/matrix/unresolved_selector.rb +8 -0
  19. data/lib/pact_broker/pacts/all_pact_publications.rb +1 -3
  20. data/lib/pact_broker/pacts/pact_publication.rb +7 -1
  21. data/lib/pact_broker/pacts/repository.rb +4 -2
  22. data/lib/pact_broker/tasks/clean_task.rb +28 -12
  23. data/lib/pact_broker/test/http_test_data_builder.rb +52 -5
  24. data/lib/pact_broker/version.rb +1 -1
  25. data/lib/pact_broker/webhooks/triggered_webhook.rb +6 -0
  26. data/pact_broker.gemspec +1 -1
  27. data/script/docker/db-start.sh +1 -1
  28. data/script/reproduce-issue.rb +38 -15
  29. data/spec/fixtures/approvals/modifiable_resources.approved.json +3 -0
  30. data/spec/lib/pact_broker/api/resources/group_spec.rb +9 -7
  31. data/spec/lib/pact_broker/db/clean_incremental_spec.rb +93 -0
  32. data/spec/lib/pact_broker/domain/tag_spec.rb +46 -0
  33. data/spec/lib/pact_broker/domain/verification_spec.rb +13 -0
  34. data/spec/lib/pact_broker/domain/version_spec.rb +23 -0
  35. data/spec/lib/pact_broker/pacts/pact_publication_spec.rb +3 -1
  36. data/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb +14 -1
  37. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03c656c61487fd81baf9eb94f73fa12ab27bdf5cea1bd2d670af63199123b6ef
4
- data.tar.gz: '04022874b3eeb6681e79163431ce63c8d6760741dc7ec8db30cc87b8f9257a94'
3
+ metadata.gz: b57a2f04694ce119675cd5b4fb647e1c2faff71b890958391ca30846ef0d7fab
4
+ data.tar.gz: 8b6484329b0821600417fc17ad564a14de06edf2edc21939423c34ea2795cee1
5
5
  SHA512:
6
- metadata.gz: ce0f9fb3186ab25c419b61665245977abfbaa8d245437699c44de54783bde86d8281b950172532d02a31f6aa33f36046dde7a0636075fabe75f3f074c33016c2
7
- data.tar.gz: f6b8baa8a66b80b50df9223086f8e67dd9233106897b15f5cfa16328e954805de786d4fbe672f3cee5ba831001edd78f5c0a977dfc5d767d4c79aa46a61b1010
6
+ metadata.gz: e4d1c82adbbb041f38d64200ccb0f1fc8d4672f3a9e445db0dcf428b25c8353c5922a3fb579b74560114df476886fb8aee93d2b793643ae9bbbbd8f786fec443
7
+ data.tar.gz: dc130a9cea4a62c741c401290b385f807c4597073a1ae467d3b334bd058267eca2179ebb6be60d53c2f6cf7046c7b567edf473738a2642e122d1c6e40938301a
@@ -0,0 +1,62 @@
1
+ name: Test
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ postgres:
7
+ runs-on: "ubuntu-latest"
8
+ strategy:
9
+ matrix:
10
+ ruby_version: ["2.5", "2.7"]
11
+ services:
12
+ postgres:
13
+ image: postgres
14
+ env:
15
+ POSTGRES_PASSWORD: postgres
16
+ options: >-
17
+ --health-cmd pg_isready
18
+ --health-interval 10s
19
+ --health-timeout 5s
20
+ --health-retries 5
21
+ ports:
22
+ - 5432:5432
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - uses: actions/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{ matrix.ruby_version }}
28
+ - run: "bundle install"
29
+ env:
30
+ INSTALL_PG: "true"
31
+ - run: "bundle exec rake"
32
+ env:
33
+ DATABASE_ADAPTER: github_actions_postgres
34
+ INSTALL_PG: "true"
35
+ mysql:
36
+ runs-on: "ubuntu-latest"
37
+ strategy:
38
+ matrix:
39
+ ruby_version: ["2.5", "2.7"]
40
+ steps:
41
+ - uses: actions/checkout@v2
42
+ - uses: actions/setup-ruby@v1
43
+ with:
44
+ ruby-version: ${{ matrix.ruby_version }}
45
+ - uses: mirromutth/mysql-action@v1.1
46
+ with:
47
+ host port: 3306 # Optional, default value is 3306. The port of host
48
+ #container port: 3307 # Optional, default value is 3306. The port of container
49
+ character set server: 'utf8' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld
50
+ collation server: 'utf8_general_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld
51
+ mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL
52
+ mysql database: 'pact_broker' # Optional, default value is "test". The specified database which will be create
53
+ mysql root password: 'pact_broker' # Required if "mysql user" is empty, default is empty. The root superuser password
54
+ mysql user: 'pact_broker' # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Can use secrets, too
55
+ mysql password: 'pact_broker' # Required if "mysql user" exists. The password for the "mysql user"
56
+ - run: "bundle install"
57
+ env:
58
+ INSTALL_MYSQL: "true"
59
+ - run: "bundle exec rake"
60
+ env:
61
+ DATABASE_ADAPTER: github_actions_mysql
62
+ INSTALL_MYSQL: "true"
@@ -1,3 +1,19 @@
1
+ <a name="v2.70.0"></a>
2
+ ### v2.70.0 (2020-11-28)
3
+
4
+ #### Features
5
+
6
+ * allow limit to be applied to clean task ([d29e5c62](/../../commit/d29e5c62))
7
+ * optimise the query to load the tags with latest flags ([bc47613f](/../../commit/bc47613f))
8
+ * optimise query to find head tags for a pact ([67309e37](/../../commit/67309e37))
9
+
10
+ * **wip pacts**
11
+ * experimental feature - if no provider versions exist, consider all head pacts wip ([a635cc53](/../../commit/a635cc53))
12
+
13
+ #### Bug Fixes
14
+
15
+ * return empty body when group csv is requested for a pacticipant that does not have any integrations ([fb4e28ce](/../../commit/fb4e28ce))
16
+
1
17
  <a name="v2.69.0"></a>
2
18
  ### v2.69.0 (2020-11-24)
3
19
 
@@ -28,6 +28,12 @@ This allows you to open a shell to a development environment where you can run t
28
28
 
29
29
  Remember to rebuild the image if you change any of the gems or gem versions.
30
30
 
31
+ * Run the server on the docker image
32
+
33
+ ```sh
34
+ docker run --rm -v $(PWD):/home -w /home --entrypoint /bin/sh -p 9292:9292 -it pact_broker:dev /usr/local/bin/start
35
+ ```
36
+
31
37
  ### With native install
32
38
 
33
39
  * You will need to install Ruby 2.5, and preferably a ruby version manager. I recommend using [chruby][chruby] and [ruby-install][ruby-install].
data/Dockerfile CHANGED
@@ -26,6 +26,7 @@ RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSI
26
26
 
27
27
 
28
28
  COPY Gemfile /home/Gemfile
29
+ COPY Gemfile.lock /home/Gemfile.lock
29
30
  COPY pact_broker.gemspec /home/pact_broker.gemspec
30
31
  COPY lib/pact_broker/version.rb /home/lib/pact_broker/version.rb
31
32
  COPY .gitignore /home/.gitignore
@@ -33,9 +34,9 @@ COPY .gitignore /home/.gitignore
33
34
  RUN gem install bundler -v '~>2.0.0' \
34
35
  && bundle install --jobs 3 --retry 3
35
36
 
36
- RUN echo '#!/bin/sh' >> /home/start.sh
37
- RUN echo 'bundle exec rackup -o 0.0.0.0 -p 9292' >> /home/start.sh
38
- RUN chmod +x /home/start.sh
37
+ RUN echo '#!/bin/sh' >> /usr/local/bin/start
38
+ RUN echo 'bundle exec rackup -o 0.0.0.0 -p 9292' >> /usr/local/bin/start
39
+ RUN chmod +x /usr/local/bin/start
39
40
 
40
41
  RUN echo '#!/bin/sh' >> /home/init-db.sh
41
42
  RUN echo 'bundle exec rake db:prepare:test' >> /home/init-db.sh
data/Gemfile CHANGED
@@ -36,3 +36,7 @@ end
36
36
  if ENV['INSTALL_PG'] == 'true'
37
37
  gem 'pg', '~>1.2'
38
38
  end
39
+
40
+ if ENV['X_PACT_DEVELOPMENT'] == 'true'
41
+ gem 'pact-support', path: '../pact-support'
42
+ end
@@ -0,0 +1,23 @@
1
+ # Issues
2
+
3
+ ## Reproducing an issue
4
+
5
+ In [script/reproduce-issue.rb](script/reproduce-issue.rb) you will find a fluent API that allows you to simulate client libraries interacting with the Pact Broker.
6
+
7
+ You can use it to easily reproduce issues.
8
+
9
+ To use it:
10
+
11
+ * Run the Pact Broker using the latest development code:
12
+
13
+ ```
14
+ docker-compose -f docker-compose-issue-repro.yml up --build pact-broker
15
+ ```
16
+
17
+ * Run the reproduction script.
18
+
19
+ ```
20
+ docker-compose -f docker-compose-issue-repro.yml up repro-issue
21
+ ```
22
+
23
+ You can modify `script/reproduce-issue.rb` and then re-run it with the change applied.
@@ -36,6 +36,20 @@ test:
36
36
  <<: *default
37
37
  adapter: mysql2
38
38
  host: "192.168.0.9"
39
+ github_actions_postgres:
40
+ adapter: postgres
41
+ database: postgres
42
+ username: postgres
43
+ password: postgres
44
+ host: localhost
45
+ port: "5432"
46
+ github_actions_mysql:
47
+ adapter: mysql2
48
+ database: pact_broker
49
+ username: pact_broker
50
+ password: pact_broker
51
+ host: localhost
52
+ port: 3306
39
53
 
40
54
  development:
41
55
  default:
@@ -2,17 +2,21 @@ version: "3"
2
2
 
3
3
  services:
4
4
  pact-broker:
5
- build:
6
- context: .
7
- dockerfile: issue-reproduction/Dockerfile-pact-broker
5
+ build: .
8
6
  ports:
9
7
  - "9292:9292"
10
- command: bundle exec rackup -p 9292 -o 0.0.0.0
8
+ entrypoint: /usr/local/bin/start
9
+ environment:
10
+ - RACK_ENV=production
11
+ volumes:
12
+ - $PWD:/home
11
13
 
12
14
  repro-issue:
13
- build:
14
- context: .
15
- dockerfile: issue-reproduction/Dockerfile-pact-broker
15
+ build: .
16
16
  depends_on:
17
17
  - pact-broker
18
18
  command: dockerize -wait http://pact-broker:9292 -timeout 30s /home/script/reproduce-issue.rb
19
+ environment:
20
+ - PACT_BROKER_BASE_URL=http://pact-broker:9292
21
+ volumes:
22
+ - $PWD:/home
@@ -0,0 +1,36 @@
1
+ require 'pact_broker/api/resources/base_resource'
2
+ require 'pact_broker/db/clean'
3
+ require 'pact_broker/matrix/unresolved_selector'
4
+
5
+ module PactBroker
6
+ module Api
7
+ module Resources
8
+ class Clean < BaseResource
9
+ def content_types_accepted
10
+ [["application/json"]]
11
+ end
12
+
13
+ def content_types_provided
14
+ [["application/hal+json"]]
15
+ end
16
+
17
+ def allowed_methods
18
+ ["POST", "OPTIONS"]
19
+ end
20
+
21
+ def process_post
22
+ keep_selectors = (params[:keep] || []).collect do | hash |
23
+ PactBroker::Matrix::UnresolvedSelector.new(hash)
24
+ end
25
+
26
+ result = PactBroker::DB::Clean.call(Sequel::Model.db, { keep: keep_selectors })
27
+ response.body = result.to_json
28
+ end
29
+
30
+ def policy_name
31
+ :'integrations::clean'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -18,7 +18,11 @@ module PactBroker
18
18
  end
19
19
 
20
20
  def to_csv
21
- PactBroker::Api::Decorators::RelationshipsCsvDecorator.new(group).to_csv
21
+ if group
22
+ PactBroker::Api::Decorators::RelationshipsCsvDecorator.new(group).to_csv
23
+ else
24
+ ""
25
+ end
22
26
  end
23
27
 
24
28
  def policy_name
@@ -1,10 +1,13 @@
1
1
  require 'sequel'
2
2
  require 'pact_broker/project_root'
3
3
  require 'pact_broker/pacts/latest_tagged_pact_publications'
4
+ require 'pact_broker/logging'
4
5
 
5
6
  module PactBroker
6
7
  module DB
7
8
  class Clean
9
+ include PactBroker::Logging
10
+
8
11
 
9
12
  class Unionable < Array
10
13
  alias_method :union, :+
@@ -46,7 +49,7 @@ module PactBroker
46
49
  def latest_tagged_pact_publications_ids_to_keep
47
50
  @latest_tagged_pact_publications_ids_to_keep ||= resolve_ids(keep.select(&:tag).select(&:latest).collect do | selector |
48
51
  PactBroker::Pacts::LatestTaggedPactPublications.select(:id).for_selector(selector)
49
- end.reduce(&:union))
52
+ end.reduce(&:union) || [])
50
53
  end
51
54
 
52
55
 
@@ -0,0 +1,78 @@
1
+ require 'pact_broker/logging'
2
+ require 'pact_broker/matrix/unresolved_selector'
3
+
4
+ module PactBroker
5
+ module DB
6
+ class CleanIncremental
7
+ include PactBroker::Logging
8
+
9
+ DEFAULT_KEEP_SELECTORS = [
10
+ PactBroker::Matrix::UnresolvedSelector.new(tag: true, latest: true),
11
+ PactBroker::Matrix::UnresolvedSelector.new(latest: true),
12
+ PactBroker::Matrix::UnresolvedSelector.new(max_age: 90)
13
+ ]
14
+ TABLES = [:versions, :pact_publications, :pact_versions, :verifications, :triggered_webhooks, :webhook_executions]
15
+
16
+ def self.call database_connection, options = {}
17
+ new(database_connection, options).call
18
+ end
19
+
20
+ def initialize database_connection, options = {}
21
+ @db = database_connection
22
+ @options = options
23
+ end
24
+
25
+ def keep
26
+ options[:keep] || DEFAULT_KEEP_SELECTORS
27
+ end
28
+
29
+ def limit
30
+ options[:limit] || 1000
31
+ end
32
+
33
+ def resolve_ids(query, column_name = :id)
34
+ query.collect { |h| h[column_name] }
35
+ end
36
+
37
+ def version_ids_to_delete
38
+ db[:versions].where(id: version_ids_to_keep).invert.limit(limit).order(Sequel.asc(:id))
39
+ end
40
+
41
+ def version_ids_to_keep
42
+ @version_ids_to_keep ||= keep.collect do | selector |
43
+ PactBroker::Domain::Version.select(:id).for_selector(selector)
44
+ end.reduce(&:union)
45
+ end
46
+
47
+ def call
48
+ require 'pact_broker/domain/version'
49
+ before_counts = current_counts
50
+
51
+ result = PactBroker::Domain::Version.where(id: resolve_ids(version_ids_to_delete)).delete
52
+ delete_orphan_pact_versions
53
+
54
+ after_counts = current_counts
55
+
56
+ TABLES.each_with_object({}) do | table_name, comparison_counts |
57
+ comparison_counts[table_name.to_s] = { "deleted" => before_counts[table_name] - after_counts[table_name], "kept" => after_counts[table_name] }
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :db, :options
64
+
65
+ def current_counts
66
+ TABLES.each_with_object({}) do | table_name, counts |
67
+ counts[table_name] = db[table_name].count
68
+ end
69
+ end
70
+
71
+ def delete_orphan_pact_versions
72
+ logger.info("Deleting orphan pact versions")
73
+ referenced_pact_version_ids = db[:pact_publications].select(:pact_version_id).union(db[:verifications].select(:pact_version_id))
74
+ db[:pact_versions].where(id: referenced_pact_version_ids).invert.delete
75
+ end
76
+ end
77
+ end
78
+ end
@@ -2,27 +2,88 @@ require 'pact_broker/db'
2
2
  require 'pact_broker/repositories/helpers'
3
3
 
4
4
  module PactBroker
5
-
6
5
  module Domain
7
6
  class Tag < Sequel::Model
7
+ plugin :timestamps, update_on_create: true
8
+ plugin :insert_ignore, identifying_columns: [:name, :version_id]
9
+
10
+ unrestrict_primary_key
11
+ associate(:many_to_one, :version, :class => "PactBroker::Domain::Version", :key => :version_id, :primary_key => :id)
8
12
 
9
13
  dataset_module do
10
14
  include PactBroker::Repositories::Helpers
11
- end
12
15
 
13
- plugin :insert_ignore, identifying_columns: [:name, :version_id]
16
+ # Does NOT care about whether or not there is a pact publication
17
+ # for the version
18
+ def latest_tags_for_pacticipant_ids(pacticipant_ids)
19
+ tags_versions_join = {
20
+ Sequel[:tags][:version_id] => Sequel[:versions][:id],
21
+ Sequel[:versions][:pacticipant_id] => pacticipant_ids
22
+ }
14
23
 
15
- unrestrict_primary_key
24
+ latest_tags_versions_join = {
25
+ Sequel[:latest_tags][:name] => Sequel[:tags][:name],
26
+ Sequel[:latest_tags][:latest_order] => Sequel[:versions][:order],
27
+ Sequel[:latest_tags][:pacticipant_id] => Sequel[:versions][:pacticipant_id],
28
+ Sequel[:versions][:pacticipant_id] => pacticipant_ids
29
+ }
16
30
 
17
- associate(:many_to_one, :version, :class => "PactBroker::Domain::Version", :key => :version_id, :primary_key => :id)
31
+ latest_tags = PactBroker::Domain::Tag
32
+ .select_group(Sequel[:tags][:name], Sequel[:versions][:pacticipant_id])
33
+ .select_append{ max(order).as(latest_order) }
34
+ .join(:versions, tags_versions_join)
35
+
36
+ PactBroker::Domain::Tag
37
+ .select_all_qualified
38
+ .join(:versions,
39
+ { Sequel[:tags][:version_id] => Sequel[:versions][:id],
40
+ Sequel[:versions][:pacticipant_id] => pacticipant_ids
41
+ })
42
+ .join(latest_tags, latest_tags_versions_join, { table_alias: :latest_tags })
43
+ end
44
+
45
+ def head_tags_for_consumer_id(consumer_id)
46
+ lp = :latest_pact_publication_ids_for_consumer_versions
47
+ tags_versions_join = {
48
+ Sequel[:tags][:version_id] => Sequel[:versions][:id],
49
+ Sequel[:versions][:pacticipant_id] => consumer_id
50
+ }
51
+
52
+ versions_pact_publications_join = {
53
+ Sequel[:versions][:id] => Sequel[lp][:consumer_version_id],
54
+ Sequel[lp][:consumer_id] => consumer_id
55
+ }
56
+ # head tags for this consumer
57
+ # the latest tag, pacticipant_id, version order
58
+ # for versions that have a pact publication
59
+ PactBroker::Domain::Tag
60
+ .select_group(Sequel[:tags][:name], Sequel[:versions][:pacticipant_id])
61
+ .select_append{ max(order).as(latest_consumer_version_order) }
62
+ .join(:versions, tags_versions_join)
63
+ .join(lp, versions_pact_publications_join)
64
+ end
65
+
66
+ def head_tags_for_pact_publication(pact_publication)
67
+ head_tags_versions_join = {
68
+ Sequel[:head_tags][:latest_consumer_version_order] => Sequel[:versions][:order],
69
+ Sequel[:head_tags][:pacticipant_id] => Sequel[:versions][:pacticipant_id],
70
+ Sequel[:versions][:pacticipant_id] => pact_publication.consumer_id
71
+ }
72
+
73
+ # Find the head tags that belong to this pact publication
74
+ # Note: The tag model has the name and version_id,
75
+ # but does not have the created_at value set - but don't need it for now
76
+ head_tags_for_consumer_id(pact_publication.consumer_id).from_self(alias: :head_tags)
77
+ .select(Sequel[:head_tags][:name], Sequel[:versions][:id].as(:version_id))
78
+ .join(:versions, head_tags_versions_join)
79
+ .where(Sequel[:versions][:id] => pact_publication.consumer_version_id)
80
+ end
81
+ end
18
82
 
19
83
  def <=> other
20
84
  name <=> other.name
21
85
  end
22
-
23
86
  end
24
-
25
- Tag.plugin :timestamps, :update_on_create=>true
26
87
  end
27
88
  end
28
89