Skip to content

Commit

Permalink
feat: support create, update and delete of environment resources (#379)
Browse files Browse the repository at this point in the history
* feat: support create, update and delete of environment resources
* feat: support listing of environments, creation via post
* feat: add validation for environment creation
* feat: add validation for environment update
* feat: add 'production' boolean to environment
* feat: add enviroment and environments relation to index
* feat: disallow spaces and new lines in environment properties
  • Loading branch information
bethesque authored Feb 14, 2021
1 parent fa89b06 commit 410f2e8
Show file tree
Hide file tree
Showing 30 changed files with 796 additions and 2 deletions.
16 changes: 16 additions & 0 deletions db/migrations/20210210_create_environments_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Sequel.migration do
change do
create_table(:environments, charset: 'utf8') do
primary_key :id
String :uuid, nullable: false
String :name, nullable: false
String :display_name
Boolean :production, nullable: false
String :contacts
DateTime :created_at, nullable: false
DateTime :updated_at, nullable: false
index [:uuid], unique: true, name: "environments_uuid_index"
index [:name], unique: true, name: "environments_name_index"
end
end
end
5 changes: 5 additions & 0 deletions lib/pact_broker/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_
add ['dashboard', 'provider', :provider_name, 'consumer', :consumer_name ], Api::Resources::Dashboard, {resource_name: "integration_dashboard"}
add ['test','error'], Api::Resources::ErrorTest, {resource_name: "error_test"}

if PactBroker.feature_enabled?(:environments)
add ['environments'], Api::Resources::Environments, { resource_name: "environments" }
add ['environments', :environment_uuid], Api::Resources::Environment, { resource_name: "environment" }
end

add ['integrations'], Api::Resources::Integrations, {resource_name: "integrations"}
add ['integrations', 'provider', :provider_name, 'consumer', :consumer_name], Api::Resources::Integration, {resource_name: "integration"}
add ['metrics'], Api::Resources::Metrics, {resource_name: 'metrics'}
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/api/contracts/dry_validation_predicates.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ module DryValidationPredicates
predicate(:not_blank?) do | value |
value && value.is_a?(String) && value.strip.size > 0
end

predicate(:single_line?) do | value |
value && value.is_a?(String) && !value.include?("\n")
end

predicate(:no_spaces?) do | value |
value && value.is_a?(String) && !value.include?(" ")
end
end
end
end
Expand Down
49 changes: 49 additions & 0 deletions lib/pact_broker/api/contracts/environment_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'dry-validation'
require 'pact_broker/api/contracts/dry_validation_workarounds'
require 'pact_broker/api/contracts/dry_validation_predicates'
require 'pact_broker/messages'

module PactBroker
module Api
module Contracts
class EnvironmentSchema
extend DryValidationWorkarounds
extend PactBroker::Messages
using PactBroker::HashRefinements

SCHEMA = Dry::Validation.Schema do
configure do
predicates(DryValidationPredicates)
config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
end
required(:name).filled(:str?, :single_line?, :no_spaces?)
required(:displayName).filled(:str?, :single_line?)
required(:production).filled(included_in?: [true, false])
optional(:contacts).each do
schema do
required(:name).filled(:str?, :single_line?)
optional(:details).schema do
end
end
end
end

def self.call(params_with_string_keys)
params = params_with_string_keys&.symbolize_keys
results = select_first_message(flatten_indexed_messages(SCHEMA.call(params).messages(full: true)))
validate_name(params, results)
results
end

def self.validate_name(params, results)
if (environment_with_same_name = PactBroker::Deployments::EnvironmentService.find_by_name(params[:name]))
if environment_with_same_name.uuid != params[:uuid]
results[:name] ||= []
results[:name] << message('errors.validation.environment_name_must_be_unique', name: params[:name])
end
end
end
end
end
end
end
11 changes: 11 additions & 0 deletions lib/pact_broker/api/decorators/base_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'pact_broker/api/pact_broker_urls'
require 'pact_broker/api/decorators/decorator_context'
require 'pact_broker/api/decorators/format_date_time'
require 'pact_broker/string_refinements'

module PactBroker
module Api
Expand All @@ -12,6 +13,16 @@ class BaseDecorator < Roar::Decorator
include Roar::JSON::HAL::Links
include PactBroker::Api::PactBrokerUrls
include FormatDateTime
using PactBroker::StringRefinements

def self.property(name, options={}, &block)
if options.delete(:camelize)
camelized_name = name.to_s.camelcase(false).to_sym
super(name, { as: camelized_name }.merge(options), &block)
else
super
end
end
end
end
end
Expand Down
30 changes: 30 additions & 0 deletions lib/pact_broker/api/decorators/environment_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require_relative 'base_decorator'
require_relative 'timestamps'

module PactBroker
module Api
module Decorators
class EnvironmentDecorator < BaseDecorator
property :uuid, writeable: false
property :name
property :display_name, camelize: true
property :production

collection :contacts, class: OpenStruct do
property :name
property :details
end

include Timestamps

link :self do | options |
{
title: 'Environment',
name: represented.name,
href: environment_url(represented, options[:base_url])
}
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/pact_broker/api/decorators/environments_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'pact_broker/api/decorators/base_decorator'
require 'pact_broker/api/decorators/environment_decorator'
require 'pact_broker/deployments/environment'

module PactBroker
module Api
module Decorators
class EnvironmentsDecorator < BaseDecorator

collection :entries, :as => :environments, :class => PactBroker::Deployments::Environment, :extend => PactBroker::Api::Decorators::EnvironmentDecorator, embedded: true

link :self do | options |
{
title: 'Environments',
href: options[:resource_url]
}
end
end
end
end
end
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 @@ -307,6 +307,14 @@ def group_url(pacticipant_name, base_url = '')
"#{base_url}/groups/#{url_encode(pacticipant_name)}"
end

def environments_url(base_url = '')
"#{base_url}/environments"
end

def environment_url(environment, base_url = '')
"#{environments_url(base_url)}/#{environment.uuid}"
end

def hal_browser_url target_url, base_url = ''
"#{base_url}/hal-browser/browser.html#" + target_url
end
Expand Down
9 changes: 9 additions & 0 deletions lib/pact_broker/api/resources/default_base_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ def application_context
def decorator_class(name)
application_context.decorator_configuration.class_for(name)
end

def validation_errors_for_schema?(schema, params_to_validate = params)
if (errors = schema.call(params_to_validate)).any?
set_json_validation_error_messages(errors)
true
else
false
end
end
end
end
end
Expand Down
76 changes: 76 additions & 0 deletions lib/pact_broker/api/resources/environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'pact_broker/api/resources/base_resource'
require 'pact_broker/api/resources/environment'

module PactBroker
module Api
module Resources
class Environment < BaseResource
def content_types_provided
[["application/hal+json", :to_json]]
end

def content_types_accepted
[["application/json", :from_json]]
end

def allowed_methods
["GET", "PUT", "DELETE", "OPTIONS"]
end

def resource_exists?
!!environment
end

def malformed_request?
if request.put? && environment
invalid_json? || validation_errors_for_schema?(schema, params.merge(uuid: uuid))
else
false
end
end

def from_json
if environment
@environment = update_environment
response.body = to_json
else
response.code = 404
end
end

def policy_name
:'deployments::environment'
end

def to_json
decorator_class(:environment_decorator).new(environment).to_json(decorator_options)
end

def parsed_environment
@parsed_environment ||= decorator_class(:environment_decorator).new(PactBroker::Deployments::Environment.new).from_json(request_body)
end

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

def delete_resource
environment_service.delete(uuid)
true
end

def uuid
identifier_from_path[:environment_uuid]
end

def update_environment
environment_service.update(uuid, parsed_environment)
end

def schema
PactBroker::Api::Contracts::EnvironmentSchema
end
end
end
end
end
75 changes: 75 additions & 0 deletions lib/pact_broker/api/resources/environments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'pact_broker/api/resources/base_resource'
require 'pact_broker/api/resources/environment'
require 'pact_broker/api/contracts/environment_schema'

module PactBroker
module Api
module Resources
class Environments < BaseResource
def content_types_provided
[["application/hal+json", :to_json]]
end

def content_types_accepted
[["application/json", :from_json]]
end

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

def resource_exists?
true
end

def post_is_create?
true
end

def malformed_request?
if request.post?
invalid_json? || validation_errors_for_schema?(schema, params.merge(uuid: uuid))
else
false
end
end

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

def from_json
response.body = decorator_class(:environment_decorator).new(create_environment).to_json(decorator_options)
end

def policy_name
:'deployments::environment'
end

def to_json
decorator_class(:environments_decorator).new(environments).to_json(decorator_options)
end

def parsed_environment
@parsed_environment ||= decorator_class(:environment_decorator).new(PactBroker::Deployments::Environment.new).from_json(request_body)
end

def create_environment
environment_service.create(uuid, parsed_environment)
end

def uuid
@uuid ||= environment_service.next_uuid
end

def environments
@environments ||= environment_service.find_all
end

def schema
PactBroker::Api::Contracts::EnvironmentSchema
end
end
end
end
end
14 changes: 14 additions & 0 deletions lib/pact_broker/api/resources/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ def links
}]
}

if PactBroker.feature_enabled?(:environments)
links_hash['pb:environments'] = {
title: "Environments",
href: environments_url(base_url),
templated: false
}

links_hash['pb:environment'] = {
title: "Environment",
href: environments_url(base_url) + "/{uuid}",
templated: true
}
end

if PactBroker.feature_enabled?('disable_pacts_for_verification', true)
links_hash.delete('pb:provider-pacts-for-verification')
links_hash.delete('beta:provider-pacts-for-verification')
Expand Down
15 changes: 15 additions & 0 deletions lib/pact_broker/deployments/environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'sequel'
require 'sequel/plugins/serialization'


module PactBroker
module Deployments
class Environment < Sequel::Model
OPEN_STRUCT_TO_JSON = lambda { |thing| Sequel.object_to_json(thing.collect(&:to_h)) }
JSON_TO_OPEN_STRUCT = lambda { | json | Sequel.parse_json(json).collect{ | hash| OpenStruct.new(hash) } }
plugin :upsert, identifying_columns: [:uuid]
plugin :serialization
serialize_attributes [OPEN_STRUCT_TO_JSON, JSON_TO_OPEN_STRUCT], :contacts
end
end
end
Loading

0 comments on commit 410f2e8

Please sign in to comment.