Skip to content

Commit

Permalink
feat: support recording deployments (#389)
Browse files Browse the repository at this point in the history
* feat(deployments): create endpoint for recording deployments

* test: update tests for version decorator and approvals

* feat: support specifying an environment when calling the can-i-deploy and matrix endpoints

* chore: made sure all db models are loaded

* feat: support marking previously deployed version as not currently deployed

* feat: add validation to ensure both tag and environment can't be used together to query the matrix

* chore: update key name

* chore: update deployed versions resource policy

* feat: distinguish between "the" version in an environment and "one of the versions" in an environment in the matrix explanation messages

* test: add tests to cover the 'multiple versions in an environment' use case

* chore: correct seeded environment data
  • Loading branch information
bethesque authored Feb 23, 2021
1 parent dae9cae commit 19ac1fc
Show file tree
Hide file tree
Showing 41 changed files with 793 additions and 96 deletions.
18 changes: 18 additions & 0 deletions db/migrations/20210215_create_deployed_versions_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Sequel.migration do
change do
create_table(:deployed_versions, charset: 'utf8') do
primary_key :id
String :uuid, null: false
foreign_key :version_id, :versions, null: false
Integer :pacticipant_id, null: false
foreign_key :environment_id, :environments, null: false
Boolean :currently_deployed, null: false
Boolean :replaced_previous_deployed_version, null: false
DateTime :created_at, nullable: false
DateTime :updated_at, nullable: false
DateTime :undeployed_at
index [:uuid], unique: true, name: "deployed_versions_uuid_index"
index [:pacticipant_id, :currently_deployed], name: "deployed_versions_pacticipant_id_currently_deployed_index"
end
end
end
2 changes: 2 additions & 0 deletions lib/pact_broker/api.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'webmachine/adapters/rack_mapped'
require 'webmachine/rack_adapter_monkey_patch'
require 'pact_broker/db/models'
require 'pact_broker/api/resources'
require 'pact_broker/api/decorators'
require 'pact_broker/application_context'
Expand Down Expand Up @@ -108,6 +109,7 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_
if PactBroker.feature_enabled?(:environments)
add ['environments'], Api::Resources::Environments, { resource_name: "environments" }
add ['environments', :environment_uuid], Api::Resources::Environment, { resource_name: "environment" }
add ['pacticipants', :pacticipant_name, 'versions', :pacticipant_version_number, 'deployed-versions', 'environment', :environment_uuid], Api::Resources::DeployedVersionsForVersion, { resource_name: "deployed_versions_for_version" }
end

add ['integrations'], Api::Resources::Integrations, {resource_name: "integrations"}
Expand Down
18 changes: 18 additions & 0 deletions lib/pact_broker/api/decorators/deployed_version_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'pact_broker/api/decorators/base_decorator'
require 'pact_broker/api/decorators/embedded_version_decorator'
require 'pact_broker/api/decorators/environment_decorator'

module PactBroker
module Api
module Decorators
class DeployedVersionDecorator < BaseDecorator
property :version, :extend => EmbeddedVersionDecorator, writeable: false, embedded: true
property :environment, :extend => EnvironmentDecorator, writeable: false, embedded: true
property :currently_deployed, camelize: true
property :replaced_previous_deployed_version, camelize: true
include Timestamps
property :undeployedAt, getter: lambda { |_| undeployed_at ? FormatDateTime.call(undeployed_at) : nil }, writeable: false
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Decorators
class EmbeddedVersionDecorator < BaseDecorator

property :number
property :branch

link :self do | options |
{
Expand Down
10 changes: 10 additions & 0 deletions lib/pact_broker/api/decorators/version_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ class VersionDecorator < BaseDecorator
end
end

links :'pb:record-deployment' do | context |
context.fetch(:environments, []).collect do | environment |
{
title: "Record deployment to #{environment.display_name}",
name: environment.name,
href: deployed_versions_for_environment_url(represented, environment, context.fetch(:base_url))
}
end
end

curies do | options |
[{
name: :pb,
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/api/pact_broker_urls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,14 @@ def environment_url(environment, base_url = '')
"#{environments_url(base_url)}/#{environment.uuid}"
end

def deployed_versions_for_environment_url(version, environment, base_url = '')
"#{version_url(base_url, version)}/deployed-versions/environment/#{environment.uuid}"
end

def deployed_version_url(deployed_version, base_url = '')
"/deployed-versions/#{deployed_version.uuid}"
end

def hal_browser_url target_url, base_url = ''
"#{base_url}/hal-browser/browser.html#" + target_url
end
Expand Down
72 changes: 72 additions & 0 deletions lib/pact_broker/api/resources/deployed_versions_for_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'pact_broker/api/resources/base_resource'
require 'pact_broker/configuration'
require 'pact_broker/api/decorators/versions_decorator'

module PactBroker
module Api
module Resources
class DeployedVersionsForVersion < BaseResource
def content_types_accepted
[["application/json", :from_json]]
end

def content_types_provided
[["application/hal+json"]]
end

def allowed_methods
["POST", "OPTIONS"]
end

def resource_exists?
!!version && !!environment
end

def post_is_create?
true
end

def create_path
deployed_version_url(OpenStruct.new(uuid: deployed_version_uuid), base_url)
end

def from_json
@deployed_version = deployed_version_service.create(deployed_version_uuid, version, environment, replaced_previous_deployed_version)
response.body = to_json
end

def to_json
decorator_class(:deployed_version_decorator).new(deployed_version).to_json(decorator_options)
end

def policy_name
:'versions::versions'
end

private

attr_reader :deployed_version

def version
@version ||= version_service.find_by_pacticipant_name_and_number(identifier_from_path)
end

def environment
@environment ||= environment_service.find(environment_uuid)
end

def environment_uuid
identifier_from_path[:environment_uuid]
end

def deployed_version_uuid
@deployed_version_uuid ||= deployed_version_service.next_uuid
end

def replaced_previous_deployed_version
params[:replacedPreviousDeployedVersion] == true
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/pact_broker/api/resources/matrix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def allowed_methods
end

def malformed_request?
error_messages = matrix_service.validate_selectors(selectors)
error_messages = matrix_service.validate_selectors(selectors, options)
if error_messages.any?
set_json_validation_error_messages error_messages
true
Expand Down
6 changes: 5 additions & 1 deletion lib/pact_broker/api/resources/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def from_json
end

def to_json
decorator_class(:version_decorator).new(version).to_json(decorator_options)
decorator_class(:version_decorator).new(version).to_json(decorator_options(environments: environments))
end

def delete_resource
Expand All @@ -45,6 +45,10 @@ def policy_name

private

def environments
@environments ||= environment_service.find_for_pacticipant(version.pacticipant)
end

def version
@version ||= version_service.find_by_pacticipant_name_and_number(identifier_from_path)
end
Expand Down
3 changes: 3 additions & 0 deletions lib/pact_broker/db/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require 'pact_broker/domain/version'
require 'pact_broker/domain/label'
require 'pact_broker/domain/pacticipant'
require 'pact_broker/deployments/environment'
require 'pact_broker/deployments/deployed_version'

module PactBroker
INTEGRATIONS_TABLES = [
Expand All @@ -22,6 +24,7 @@ module PactBroker
PactBroker::Pacts::PactPublication,
PactBroker::Pacts::PactVersion,
PactBroker::Domain::Tag,
PactBroker::Deployments::DeployedVersion,
PactBroker::Domain::Version,
PactBroker::Domain::Label,
PactBroker::Domain::Pacticipant
Expand Down
9 changes: 7 additions & 2 deletions lib/pact_broker/db/seed_example_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ def self.call
def call(consumer_name: CONSUMER_NAME, provider_name: PROVIDER_NAME)
return unless database_empty?
PactBroker::Test::TestDataBuilder.new
.create_environment("test", display_name: "Test", production: false)
.create_environment("production", display_name: "Production", production: true)
.create_consumer(consumer_name, created_at: days_ago(16))
.create_provider(provider_name, created_at: days_ago(16))
.create_consumer_version("e15da45d3943bf10793a6d04cfb9f5dabe430fe2", branch: "main", created_at: days_ago(16))
.create_consumer_version_tag("prod", created_at: days_ago(16))
.create_consumer_version_tag("main", created_at: days_ago(16))
.create_deployed_version_for_consumer_version(environment_name: "production", currently_deployed: false, created_at: days_ago(16))
.create_pact(json_content: pact_1, created_at: days_ago(16))
.create_verification(provider_version: "1315e0b1924cb6f42751f977789be3559373033a", branch: "main", execution_date: days_ago(15))
.create_provider_version_tag("main", created_at: days_ago(14))
.create_provider_version_tag("prod", created_at: days_ago(14))
.create_deployed_version_for_provider_version(environment_name: "production", currently_deployed: true, created_at: days_ago(15))
.create_deployed_version_for_consumer_version(environment_name: "production", currently_deployed: true, created_at: days_ago(14))
.create_verification(provider_version: "480e5aeb30467856ca995d0024d2c1800b0719e5", branch: "main", success: false, number: 2, execution_date: days_ago(14))
.create_provider_version_tag("main", created_at: days_ago(14))
.create_consumer_version("725c6ccb7cf7efc51b4394f9828585eea9c379d9", branch: "feat/new-thing", created_at: days_ago(7))
Expand All @@ -32,9 +35,11 @@ def call(consumer_name: CONSUMER_NAME, provider_name: PROVIDER_NAME)
.create_consumer_version_tag("main", created_at: days_ago(1))
.create_pact(json_content: pact_3, created_at: days_ago(1))
.create_verification(provider_version: "4fdf20082263d4c5038355a3b734be1c0054d1e1", branch: "main", execution_date: days_ago(1))
.create_deployed_version_for_provider_version(environment_name: "test", created_at: days_ago(1))
.create_provider_version_tag("main", created_at: days_ago(1))
.create_consumer_version("5556b8149bf8bac76bc30f50a8a2dd4c22c85f30", branch: "main", created_at: days_ago(0.5))
.create_consumer_version_tag("main", created_at: days_ago(0.5))
.create_deployed_version_for_consumer_version(environment_name: "test", created_at: days_ago(0.5))
.republish_same_pact(created_at: days_ago(0.5))
end

Expand Down
38 changes: 38 additions & 0 deletions lib/pact_broker/deployments/deployed_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'pact_broker/repositories/helpers'

module PactBroker
module Deployments
class DeployedVersion < Sequel::Model
many_to_one :version, :class => "PactBroker::Domain::Version", :key => :version_id, :primary_key => :id
many_to_one :environment, :class => "PactBroker::Deployments::Environment", :key => :environment_id, :primary_key => :id

dataset_module do
include PactBroker::Repositories::Helpers

def last_deployed_version(pacticipant, environment)
currently_deployed
.where(pacticipant_id: pacticipant.id)
.where(environment: environment)
.order(Sequel.desc(:created_at), Sequel.desc(:id))
.first
end

def currently_deployed
where(currently_deployed: true)
end

def for_environment_name(environment_name)
where(environment_id: db[:environments].select(:id).where(name: environment_name))
end

def for_pacticipant_name(pacticipant_name)
where(pacticipant_id: db[:pacticipants].select(:id).where(name_like(:name, pacticipant_name)))
end
end

def record_undeployed
update(currently_deployed: false, undeployed_at: Sequel.datetime_class.now)
end
end
end
end
29 changes: 29 additions & 0 deletions lib/pact_broker/deployments/deployed_version_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'pact_broker/deployments/deployed_version'

module PactBroker
module Deployments
class DeployedVersionService
def self.next_uuid
SecureRandom.uuid
end

def self.create(uuid, version, environment, replaced_previous_deployed_version)
if replaced_previous_deployed_version
record_previous_version_undeployed(version.pacticipant, environment)
end
DeployedVersion.create(
uuid: uuid,
version: version,
pacticipant_id: version.pacticipant_id,
environment: environment,
currently_deployed: true,
replaced_previous_deployed_version: replaced_previous_deployed_version
)
end

def self.record_previous_version_undeployed(pacticipant, environment)
DeployedVersion.last_deployed_version(pacticipant, environment)&.record_undeployed
end
end
end
end
5 changes: 4 additions & 1 deletion lib/pact_broker/deployments/environment_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
module PactBroker
module Deployments
module EnvironmentService

def self.next_uuid
SecureRandom.uuid
end
Expand Down Expand Up @@ -34,6 +33,10 @@ def self.find_by_name(name)
def self.delete(uuid)
PactBroker::Deployments::Environment.where(uuid: uuid).delete
end

def self.find_for_pacticipant(pacticipant)
find_all
end
end
end
end
1 change: 0 additions & 1 deletion lib/pact_broker/domain/label.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
module PactBroker
module Domain
class Label < Sequel::Model

unrestrict_primary_key

associate(:many_to_one, :pacticipant, :class => "PactBroker::Domain::Pacticipant", :key => :pacticipant_id, :primary_key => :id)
Expand Down
10 changes: 10 additions & 0 deletions lib/pact_broker/domain/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Version < Sequel::Model
one_to_many :pact_publications, order: :revision_number, class: "PactBroker::Pacts::PactPublication", key: :consumer_version_id
associate(:many_to_one, :pacticipant, :class => "PactBroker::Domain::Pacticipant", :key => :pacticipant_id, :primary_key => :id)
one_to_many :tags, :reciprocal => :version, order: :created_at
one_to_many :current_deployed_versions, class: "PactBroker::Deployments::DeployedVersion", key: :version_id, primary_key: :id do | ds |
ds.currently_deployed
end

many_to_one :latest_version_for_pacticipant, read_only: true, key: :id,
class: Version,
Expand Down Expand Up @@ -79,6 +82,12 @@ def where_pacticipant_name(pacticipant_name)
# end
end

def currently_deployed_to_environment(environment_name, pacticipant_name)
deployed_version_query = PactBroker::Deployments::DeployedVersion.currently_deployed.for_environment_name(environment_name)
deployed_version_query = deployed_version_query.for_pacticipant_name(pacticipant_name) if pacticipant_name
where(id: deployed_version_query.select(:version_id))
end

def where_tag(tag)
if tag == true
join(:tags, Sequel[:tags][:version_id] => Sequel[first_source_alias][:id])
Expand Down Expand Up @@ -115,6 +124,7 @@ def delete
def for_selector(selector)
query = self
query = query.where_pacticipant_name(selector.pacticipant_name) if selector.pacticipant_name
query = query.currently_deployed_to_environment(selector.environment_name, selector.pacticipant_name) if selector.environment_name
query = query.where_tag(selector.tag) if selector.tag
query = query.where_number(selector.pacticipant_version_number) if selector.pacticipant_version_number
query = query.where_age_less_than(selector.max_age) if selector.max_age
Expand Down
2 changes: 2 additions & 0 deletions lib/pact_broker/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ en:
invalid_webhook_uuid: The UUID can only contain the characters A-Z, a-z, 0-9, _ and -, and must be 16 or more characters.
pacticipant_not_found: No pacticipant with name '%{name}' found
environment_name_must_be_unique: Another environment with name '%{name}' already exists.
cannot_specify_tag_and_environment: Cannot specify both a 'to' tag and an environment.
cannot_specify_latest_and_environment: Cannot specify both latest=true and an environment.
duplicate_pacticipant: |
This is the first time a pact has been published for "%{new_name}".
The name "%{new_name}" is very similar to the following existing consumers/providers:
Expand Down
Loading

0 comments on commit 19ac1fc

Please sign in to comment.