pact_broker 2.77.0 → 2.78.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/db/migrations/20210210_create_environments_table.rb +16 -0
  4. data/lib/pact_broker/api.rb +5 -0
  5. data/lib/pact_broker/api/contracts/dry_validation_predicates.rb +8 -0
  6. data/lib/pact_broker/api/contracts/environment_schema.rb +49 -0
  7. data/lib/pact_broker/api/decorators/base_decorator.rb +11 -0
  8. data/lib/pact_broker/api/decorators/environment_decorator.rb +30 -0
  9. data/lib/pact_broker/api/decorators/environments_decorator.rb +21 -0
  10. data/lib/pact_broker/api/decorators/version_decorator.rb +12 -1
  11. data/lib/pact_broker/api/pact_broker_urls.rb +8 -0
  12. data/lib/pact_broker/api/resources/default_base_resource.rb +9 -0
  13. data/lib/pact_broker/api/resources/environment.rb +76 -0
  14. data/lib/pact_broker/api/resources/environments.rb +75 -0
  15. data/lib/pact_broker/api/resources/index.rb +14 -0
  16. data/lib/pact_broker/api/resources/version.rb +2 -2
  17. data/lib/pact_broker/configuration.rb +1 -0
  18. data/lib/pact_broker/deployments/environment.rb +15 -0
  19. data/lib/pact_broker/deployments/environment_service.rb +39 -0
  20. data/lib/pact_broker/doc/views/index/environment.markdown +37 -0
  21. data/lib/pact_broker/doc/views/index/environments.markdown +53 -0
  22. data/lib/pact_broker/doc/views/index/latest-pact-versions.markdown +1 -1
  23. data/lib/pact_broker/doc/views/index/pacticipant-version-tag.markdown +1 -0
  24. data/lib/pact_broker/locale/en.yml +3 -1
  25. data/lib/pact_broker/services.rb +9 -0
  26. data/lib/pact_broker/test/test_data_builder.rb +14 -0
  27. data/lib/pact_broker/version.rb +1 -1
  28. data/lib/pact_broker/versions/repository.rb +15 -4
  29. data/lib/pact_broker/versions/service.rb +2 -2
  30. data/lib/pact_broker/webhooks/webhook_execution_result.rb +4 -1
  31. data/spec/features/create_environment_spec.rb +47 -0
  32. data/spec/features/create_tag_spec.rb +32 -0
  33. data/spec/features/create_version_spec.rb +30 -4
  34. data/spec/features/delete_environment_spec.rb +16 -0
  35. data/spec/features/end_deployment_spec.rb +29 -0
  36. data/spec/features/get_environment_spec.rb +19 -0
  37. data/spec/features/get_environments_spec.rb +20 -0
  38. data/spec/features/record_deployment_spec.rb +28 -0
  39. data/spec/features/update_environment_spec.rb +44 -0
  40. data/spec/fixtures/approvals/modifiable_resources.approved.json +6 -0
  41. data/spec/lib/pact_broker/api/contracts/environment_schema_spec.rb +83 -0
  42. data/spec/lib/pact_broker/api/decorators/version_decorator_spec.rb +18 -0
  43. data/spec/lib/pact_broker/api/resources/default_base_resource_approval_spec.rb +1 -1
  44. data/spec/lib/pact_broker/api/resources/webhook_execution_result_spec.rb +56 -0
  45. data/spec/lib/pact_broker/versions/repository_spec.rb +14 -4
  46. data/spec/service_consumers/hal_relation_proxy_app.rb +3 -1
  47. data/spec/service_consumers/provider_states_for_pact_broker_client.rb +16 -0
  48. data/spec/support/shared_examples_for_responses.rb +11 -0
  49. metadata +33 -3
@@ -31,6 +31,7 @@ module PactBroker
31
31
  :webhook_http_method_whitelist,
32
32
  :webhook_scheme_whitelist,
33
33
  :webhook_host_whitelist,
34
+ :webhook_http_code_success,
34
35
  :base_equality_only_on_content_that_affects_verification_results,
35
36
  :seed_example_data,
36
37
  :badge_provider_mode,
@@ -0,0 +1,15 @@
1
+ require 'sequel'
2
+ require 'sequel/plugins/serialization'
3
+
4
+
5
+ module PactBroker
6
+ module Deployments
7
+ class Environment < Sequel::Model
8
+ OPEN_STRUCT_TO_JSON = lambda { |thing| Sequel.object_to_json(thing.collect(&:to_h)) }
9
+ JSON_TO_OPEN_STRUCT = lambda { | json | Sequel.parse_json(json).collect{ | hash| OpenStruct.new(hash) } }
10
+ plugin :upsert, identifying_columns: [:uuid]
11
+ plugin :serialization
12
+ serialize_attributes [OPEN_STRUCT_TO_JSON, JSON_TO_OPEN_STRUCT], :contacts
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ require 'pact_broker/deployments/environment'
2
+ require 'securerandom'
3
+
4
+ module PactBroker
5
+ module Deployments
6
+ module EnvironmentService
7
+
8
+ def self.next_uuid
9
+ SecureRandom.uuid
10
+ end
11
+
12
+ def self.create(uuid, environment)
13
+ environment.uuid = uuid
14
+ environment.save
15
+ end
16
+
17
+ def self.update(uuid, environment)
18
+ environment.uuid = uuid
19
+ environment.upsert
20
+ end
21
+
22
+ def self.find_all
23
+ PactBroker::Deployments::Environment.order(Sequel.function(:lower, :display_name)).all
24
+ end
25
+
26
+ def self.find(uuid)
27
+ PactBroker::Deployments::Environment.where(uuid: uuid).single_record
28
+ end
29
+
30
+ def self.find_by_name(name)
31
+ PactBroker::Deployments::Environment.where(name: name).single_record
32
+ end
33
+
34
+ def self.delete(uuid)
35
+ PactBroker::Deployments::Environment.where(uuid: uuid).delete
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ # Environment
2
+
3
+ Allowed methods: `GET`, `PUT`, `DELETE`
4
+
5
+ Path: `/environment/{uuid}`
6
+
7
+ ## Viewing an environment
8
+
9
+ Example:
10
+
11
+ curl http://${PACT_BROKER_HOST}/environments/79060381-269c-4769-9894-9ec3cab44729 \
12
+ -H "Accept: application/hal+json"
13
+ {
14
+ "uuid": "79060381-269c-4769-9894-9ec3cab44729",
15
+ "name": "test",
16
+ "displayName": "Test",
17
+ "production": false
18
+ }
19
+
20
+ ## Updating an environment
21
+
22
+ Example:
23
+
24
+ curl -X PUT http://${PACT_BROKER_HOST}/environments/79060381-269c-4769-9894-9ec3cab44729 \
25
+ -H "Content-Type: application/json" \
26
+ -H "Accept: application/hal+json" \
27
+ -d '{
28
+ "name": "test",
29
+ "displayName": "Test",
30
+ "production": false
31
+ }'
32
+
33
+ ## Deleting an environment
34
+
35
+ Example:
36
+
37
+ curl -v -X DELETE http://${PACT_BROKER_HOST}/environments/79060381-269c-4769-9894-9ec3cab44729
@@ -0,0 +1,53 @@
1
+ # Environments
2
+
3
+ Allowed methods: `GET`, `POST`
4
+
5
+ Path: `/environments`
6
+
7
+ ## Creating an environment
8
+
9
+ Send a POST to `/environments` with the environment payload.
10
+
11
+ Example:
12
+
13
+ curl http://${PACT_BROKER_HOST}/environments \
14
+ -H "Content-Type: application/json" \
15
+ -H "Accept: application/hal+json" \
16
+ -d '{
17
+ "name": "test",
18
+ "displayName": "Test",
19
+ "production": false
20
+ }'
21
+
22
+ Alternatively, you can use the HAL Browser.
23
+
24
+ * Click on the `API Browser` link at the top of the Pact Broker index page.
25
+ * In the `Links` section on the left, locate the `pb:environments` relation, and click on the yellow `!` "Perform non-GET request" button.
26
+ * In the `Body:` text box, fill in the required JSON properties.
27
+ * Click `Make Request`.
28
+
29
+ Properties:
30
+
31
+ * `uuid`: System generated unique identifier.
32
+ * `name`: Must be unique. No spaces allowed. This will be the name used in the `can-i-deploy` and `record-deployment` CLI commands. eg. "payments-sit-1"
33
+ * `displayName`: A more verbose name for the environment. "Payments Team SIT 1"
34
+ * `production`: Whether or not this environment is a production environment.
35
+
36
+ If all the services in the Broker are deployed to the same "public" internet, then there only needs to be one Production environment. If there are multiple segregated production environments (eg. when maintaining on-premises software for multiple customers ) then you should create a separate production Environment for each logical deployment environment.
37
+
38
+ ## Listing environments
39
+
40
+ `GET /environments`
41
+
42
+ {
43
+ "_embedded": {
44
+ "environments": [
45
+ {
46
+ "uuid": "79060381-269c-4769-9894-9ec3cab44729",
47
+ "name": "production",
48
+ "displayName": "Production",
49
+ "production": true
50
+ }
51
+ ]
52
+ }
53
+ }
@@ -1,5 +1,5 @@
1
1
  # Latest pact versions
2
2
 
3
- Allowed methods: GET
3
+ Allowed methods: `GET`
4
4
 
5
5
  A list of the latest pact versions for each consumer/provider pair. The "latest" is determined by inspecting the consumer version used when each pact was published.
@@ -1,6 +1,7 @@
1
1
  # Pacticipant version tag
2
2
 
3
3
  Allowed methods: `GET`, `PUT`, `DELETE`
4
+
4
5
  Path: `/pacticipants/{pacticipant}/versions/{version}/tags/{tag}`
5
6
 
6
7
  To create a tag, send an empty request with the URL specified above and `Content-Type` of `application/json`.
@@ -9,7 +9,8 @@ en:
9
9
  valid_consumer_version_number?: "Consumer version number '%{value}' cannot be parsed to a version number. The expected format (unless this configuration has been overridden) is a semantic version. eg. 1.3.0 or 2.0.4.rc1"
10
10
  non_templated_host?: "cannot have a template parameter in the host"
11
11
  pacticipant_exists?: "does not match an existing pacticipant"
12
-
12
+ single_line?: "cannot contain multiple lines"
13
+ no_spaces?: "cannot contain spaces"
13
14
 
14
15
  pact_broker:
15
16
  messages:
@@ -46,6 +47,7 @@ en:
46
47
  connection_encoding_not_utf8: "The Sequel connection encoding (%{encoding}) is strongly recommended to be \"utf8\". If you need to set it to %{encoding} for some particular reason, then disable this check by setting config.validate_database_connection_config = false"
47
48
  invalid_webhook_uuid: The UUID can only contain the characters A-Z, a-z, 0-9, _ and -, and must be 16 or more characters.
48
49
  pacticipant_not_found: No pacticipant with name '%{name}' found
50
+ environment_name_must_be_unique: Another environment with name '%{name}' already exists.
49
51
  duplicate_pacticipant: |
50
52
  This is the first time a pact has been published for "%{new_name}".
51
53
  The name "%{new_name}" is very similar to the following existing consumers/providers:
@@ -73,6 +73,10 @@ module PactBroker
73
73
  get(:metrics_service)
74
74
  end
75
75
 
76
+ def environment_service
77
+ get(:environment_service)
78
+ end
79
+
76
80
  def register_default_services
77
81
  register_service(:index_service) do
78
82
  require 'pact_broker/index/service'
@@ -148,6 +152,11 @@ module PactBroker
148
152
  require 'pact_broker/webhooks/trigger_service'
149
153
  Webhooks::TriggerService
150
154
  end
155
+
156
+ register_service(:environment_service) do
157
+ require 'pact_broker/deployments/environment_service'
158
+ Deployments::EnvironmentService
159
+ end
151
160
  end
152
161
  end
153
162
  end
@@ -24,6 +24,7 @@ require 'pact_broker/tags/repository'
24
24
  require 'pact_broker/webhooks/repository'
25
25
  require 'pact_broker/certificates/certificate'
26
26
  require 'pact_broker/matrix/row'
27
+ require 'pact_broker/deployments/environment_service'
27
28
  require 'ostruct'
28
29
 
29
30
  module PactBroker
@@ -45,6 +46,7 @@ module PactBroker
45
46
  attr_reader :webhook
46
47
  attr_reader :webhook_execution
47
48
  attr_reader :triggered_webhook
49
+ attr_reader :environment
48
50
 
49
51
  def initialize(params = {})
50
52
  @now = DateTime.now
@@ -381,6 +383,18 @@ module PactBroker
381
383
  self
382
384
  end
383
385
 
386
+ def create_environment(name, params = {})
387
+ uuid = params[:uuid] || PactBroker::Deployments::EnvironmentService.next_uuid
388
+ production = params[:production] || false
389
+ @environment = PactBroker::Deployments::EnvironmentService.create(uuid, PactBroker::Deployments::Environment.new(params.merge(name: name, production: production)))
390
+ set_created_at_if_set(params[:created_at], :environments, id: environment.id)
391
+ self
392
+ end
393
+
394
+ def create_deployment(_)
395
+ self
396
+ end
397
+
384
398
  def create_everything_for_an_integration
385
399
  create_pact_with_verification("Foo", "1", "Bar", "2")
386
400
  .create_label("label")
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '2.77.0'
2
+ VERSION = '2.78.0'
3
3
  end
@@ -9,6 +9,7 @@ module PactBroker
9
9
 
10
10
  include PactBroker::Logging
11
11
  include PactBroker::Repositories::Helpers
12
+ include PactBroker::Repositories
12
13
 
13
14
  def find_by_pacticipant_id_and_number pacticipant_id, number
14
15
  PactBroker::Domain::Version.where(number: number, pacticipant_id: pacticipant_id).single_record
@@ -60,13 +61,23 @@ module PactBroker
60
61
  PactBroker::Domain::Version.new(version_params).upsert
61
62
  end
62
63
 
63
- def create_or_update(pacticipant, version_number, version)
64
- PactBroker::Domain::Version.new(
64
+ def create_or_overwrite(pacticipant, version_number, open_struct_version)
65
+ saved_version = PactBroker::Domain::Version.new(
65
66
  number: version_number,
66
67
  pacticipant: pacticipant,
67
- build_url: version.build_url,
68
- branch: version.branch
68
+ build_url: open_struct_version.build_url,
69
+ branch: open_struct_version.branch
69
70
  ).upsert
71
+
72
+ if open_struct_version.tags
73
+ tag_repository.delete_by_version_id(saved_version.id)
74
+ open_struct_version.tags.collect do | open_struct_tag |
75
+ tag_repository.create(version: saved_version, name: open_struct_tag.name)
76
+ end
77
+ saved_version.refresh
78
+ end
79
+
80
+ saved_version
70
81
  end
71
82
 
72
83
  def find_by_pacticipant_id_and_number_or_create pacticipant_id, number
@@ -18,9 +18,9 @@ module PactBroker
18
18
  version_repository.find_by_pacticipant_name_and_latest_tag(pacticipant_name, tag)
19
19
  end
20
20
 
21
- def self.create_or_update(pacticipant_name, version_number, version)
21
+ def self.create_or_overwrite(pacticipant_name, version_number, version)
22
22
  pacticipant = pacticipant_repository.find_by_name_or_create(pacticipant_name)
23
- version_repository.create_or_update(pacticipant, version_number, version)
23
+ version_repository.create_or_overwrite(pacticipant, version_number, version)
24
24
  end
25
25
 
26
26
  def self.delete version
@@ -14,7 +14,10 @@ module PactBroker
14
14
  end
15
15
 
16
16
  def success?
17
- !response.nil? && response.code.to_i < 300
17
+ unless response.nil?
18
+ # Response HTTP Code must be in success list otherwise it is false
19
+ PactBroker.configuration.webhook_http_code_success.include? response.code.to_i
20
+ end
18
21
  end
19
22
  end
20
23
  end
@@ -0,0 +1,47 @@
1
+ require 'pact_broker/api/pact_broker_urls'
2
+
3
+ describe "Creating an environment" do
4
+ let(:path) { PactBroker::Api::PactBrokerUrls.environments_url }
5
+ let(:headers) { { "CONTENT_TYPE" => "application/json" } }
6
+ let(:response_body) { JSON.parse(subject.body, symbolize_names: true)}
7
+ let(:environment_hash) do
8
+ {
9
+ name: "test",
10
+ displayName: "Test",
11
+ production: false,
12
+ contacts: [
13
+ { name: "Team Awesome", details: { email: "foo@bar.com", arbitraryThing: "thing" } }
14
+ ]
15
+ }
16
+ end
17
+
18
+ subject { post(path, environment_hash.to_json, headers) }
19
+
20
+ it "returns a 201 response" do
21
+ expect(subject.status).to be 201
22
+ end
23
+
24
+ it "returns the Location header" do
25
+ expect(subject.headers["Location"]).to eq PactBroker::Api::PactBrokerUrls.environment_url(PactBroker::Deployments::Environment.order(:id).last, "http://example.org")
26
+ end
27
+
28
+ it "returns a JSON Content Type" do
29
+ expect(subject.headers["Content-Type"]).to eq "application/hal+json;charset=utf-8"
30
+ end
31
+
32
+ it "returns the newly created environment" do
33
+ expect(response_body).to include environment_hash.merge(name: "test")
34
+ expect(response_body[:uuid]).to_not be nil
35
+ end
36
+
37
+ context "with invalid params" do
38
+ before do
39
+ td.create_environment("test")
40
+ end
41
+
42
+ it "returns a 400 response" do
43
+ expect(subject.status).to be 400
44
+ expect(response_body[:errors]).to_not be nil
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ describe "Creating a tag" do
2
+ let(:path) { "/pacticipants/Foo/versions/1234/tags/foo" }
3
+ let(:headers) { { 'CONTENT_TYPE' => 'application/json' } }
4
+ let(:response_body) { JSON.parse(subject.body, symbolize_names: true)}
5
+
6
+ subject { put(path, {}, headers) }
7
+
8
+ it "returns a 201 response" do
9
+ expect(subject.status).to be 201
10
+ end
11
+
12
+ it "returns a HAL JSON Content Type" do
13
+ expect(subject.headers['Content-Type']).to eq 'application/hal+json;charset=utf-8'
14
+ end
15
+
16
+ it "returns the newly created tag" do
17
+ expect(response_body).to include name: "foo"
18
+ end
19
+
20
+ context "when the tag already exists" do
21
+ before do
22
+ td.subtract_day
23
+ .create_consumer("Foo")
24
+ .create_consumer_version("1234")
25
+ .create_consumer_version_tag("foo")
26
+ end
27
+
28
+ it "returns a 200 response" do
29
+ expect(subject.status).to be 200
30
+ end
31
+ end
32
+ end
@@ -2,7 +2,13 @@ describe "Creating a pacticipant version" do
2
2
  let(:path) { "/pacticipants/Foo/versions/1234" }
3
3
  let(:headers) { { 'CONTENT_TYPE' => 'application/json' } }
4
4
  let(:response_body) { JSON.parse(subject.body, symbolize_names: true)}
5
- let(:version_hash) { { branch: "main", buildUrl: "http://build" } }
5
+ let(:version_hash) do
6
+ {
7
+ branch: "main",
8
+ buildUrl: "http://build",
9
+ tags: [{ name: "foo" }, { name: "bar" }]
10
+ }
11
+ end
6
12
 
7
13
  subject { put(path, version_hash.to_json, headers) }
8
14
 
@@ -15,7 +21,12 @@ describe "Creating a pacticipant version" do
15
21
  end
16
22
 
17
23
  it "returns the newly created version" do
18
- expect(response_body).to include version_hash
24
+ expect(response_body).to include branch: "main", buildUrl: "http://build"
25
+ expect(response_body[:_embedded][:tags].size).to eq 2
26
+ end
27
+
28
+ it "creates the specified tags" do
29
+ expect { subject }.to change { PactBroker::Domain::Tag.count }.by(2)
19
30
  end
20
31
 
21
32
  context "when the version already exists" do
@@ -28,13 +39,28 @@ describe "Creating a pacticipant version" do
28
39
 
29
40
  let(:version_hash) { { branch: "new-branch" } }
30
41
 
42
+ it "returns a 200" do
43
+ expect(subject.status).to be 200
44
+ end
45
+
31
46
  it "overwrites the direct properties" do
32
47
  expect(response_body[:branch]).to eq "new-branch"
33
48
  expect(response_body).to_not have_key(:buildUrl)
34
49
  end
35
50
 
36
- it "does not change the tags" do
37
- expect { subject }.to_not change { PactBroker::Domain::Version.for("Foo", "1234").tags }
51
+ context "when not tags are specified" do
52
+ it "does not change the tags" do
53
+ expect { subject }.to_not change { PactBroker::Domain::Version.for("Foo", "1234").tags }
54
+ end
55
+ end
56
+
57
+ context "when tags are specified" do
58
+ let(:version_hash) { { branch: "new-branch", tags: [ { name: "main" }] } }
59
+
60
+ it "overwrites the tags" do
61
+ expect(response_body[:_embedded][:tags].size).to eq 1
62
+ expect(response_body[:_embedded][:tags].first[:name]).to eq "main"
63
+ end
38
64
  end
39
65
 
40
66
  it "does not change the created date" do