lib/flapjack/gateways/jsonapi.rb in flapjack-0.8.10 vs lib/flapjack/gateways/jsonapi.rb in flapjack-0.8.11
- old
+ new
@@ -14,22 +14,31 @@
require 'flapjack/rack_logger'
require 'flapjack/redis_pool'
require 'flapjack/gateways/jsonapi/rack/json_params_parser'
+require 'flapjack/gateways/jsonapi/check_methods'
require 'flapjack/gateways/jsonapi/contact_methods'
require 'flapjack/gateways/jsonapi/entity_methods'
+require 'flapjack/gateways/jsonapi/medium_methods'
+require 'flapjack/gateways/jsonapi/notification_rule_methods'
+require 'flapjack/gateways/jsonapi/pagerduty_credential_methods'
+require 'flapjack/gateways/jsonapi/report_methods'
module Flapjack
module Gateways
class JSONAPI < Sinatra::Base
include Flapjack::Utility
JSON_REQUEST_MIME_TYPES = ['application/vnd.api+json', 'application/json', 'application/json-patch+json']
+ # http://www.iana.org/assignments/media-types/application/vnd.api+json
+ JSONAPI_MEDIA_TYPE = 'application/vnd.api+json; charset=utf-8'
+ # http://tools.ietf.org/html/rfc6902
+ JSON_PATCH_MEDIA_TYPE = 'application/json-patch+json; charset=utf-8'
class ContactNotFound < RuntimeError
attr_reader :contact_id
def initialize(contact_id)
@contact_id = contact_id
@@ -55,10 +64,17 @@
def initialize(entity)
@entity = entity
end
end
+ class EntitiesNotFound < RuntimeError
+ attr_reader :entity_ids
+ def initialize(entity_ids)
+ @entity_ids = entity_ids
+ end
+ end
+
class EntityCheckNotFound < RuntimeError
attr_reader :entity, :check
def initialize(entity, check)
@entity = entity
@check = check
@@ -207,65 +223,197 @@
logger.info("Returning #{response.status} for #{request.request_method} " +
"#{request.path_info}#{query_string}")
end
end
- def is_json_request?
- Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type.split(/\s*[;,]\s*/, 2).first.downcase)
- end
+ module Helpers
- def is_jsonpatch_request?
- 'application/json-patch+json'.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first.downcase)
- end
+ def cors_headers
+ allow_headers = %w(* Content-Type Accept AUTHORIZATION Cache-Control)
+ allow_methods = %w(GET POST PUT PATCH DELETE OPTIONS)
+ expose_headers = %w(Cache-Control Content-Language Content-Type Expires Last-Modified Pragma)
+ cors_headers = {
+ 'Access-Control-Allow-Origin' => '*',
+ 'Access-Control-Allow-Methods' => allow_methods.join(', '),
+ 'Access-Control-Allow-Headers' => allow_headers.join(', '),
+ 'Access-Control-Expose-Headers' => expose_headers.join(', '),
+ 'Access-Control-Max-Age' => '1728000'
+ }
+ headers(cors_headers)
+ end
- register Flapjack::Gateways::JSONAPI::EntityMethods
+ def location(ids)
+ location = "#{base_url}#{request.path_info}#{ids.length == 1 ? '/' + ids.first : '?ids=' + ids.join(',')}"
+ headers({'Location' => location})
+ end
- register Flapjack::Gateways::JSONAPI::ContactMethods
+ def err(status, *msg)
+ msg_str = msg.join(", ")
+ logger.info "Error: #{msg_str}"
+ [status, {}, {:errors => msg}.to_json]
+ end
- # the following should add the cors headers to every request, but is no work
- #register Sinatra::CrossOrigin
- #
- #configure do
- # enable :cross_origin
- #end
- #set :allow_origin, :any
- #set :allow_methods, [:get, :post, :put, :patch, :delete, :options]
+ def is_json_request?
+ Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type.split(/\s*[;,]\s*/, 2).first)
+ end
+ def is_jsonapi_request?
+ return false if request.content_type.nil?
+ 'application/vnd.api+json'.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first)
+ end
+
+ def is_jsonpatch_request?
+ return false if request.content_type.nil?
+ 'application/json-patch+json'.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first)
+ end
+
+ def wrapped_params(name, error_on_nil = true)
+ result = params[name.to_sym]
+ if result.nil?
+ if error_on_nil
+ logger.debug("No '#{name}' object found in the following supplied JSON:")
+ logger.debug(request.body.is_a?(StringIO) ? request.body.read : request.body)
+ halt err(403, "No '#{name}' object received")
+ else
+ result = [{}]
+ end
+ end
+ unless result.is_a?(Array)
+ halt err(403, "The received '#{name}'' object is not an Array")
+ end
+ result
+ end
+
+ def find_contact(contact_id)
+ contact = Flapjack::Data::Contact.find_by_id(contact_id, :logger => logger, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::ContactNotFound.new(contact_id) if contact.nil?
+ contact
+ end
+
+ def find_rule(rule_id)
+ rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :logger => logger, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::NotificationRuleNotFound.new(rule_id) if rule.nil?
+ rule
+ end
+
+ def find_tags(tags)
+ halt err(400, "no tags given") if tags.nil? || tags.empty?
+ tags
+ end
+
+ def find_entity(entity_name)
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_name) if entity.nil?
+ entity
+ end
+
+ def find_entity_by_id(entity_id)
+ entity = Flapjack::Data::Entity.find_by_id(entity_id, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_id) if entity.nil?
+ entity
+ end
+
+ def find_entity_check(entity, check_name)
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity, check_name, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity.name, check_name) if entity_check.nil?
+ entity_check
+ end
+
+ def find_entity_check_by_name(entity_name, check_name)
+ entity_check = Flapjack::Data::EntityCheck.for_entity_name(entity_name, check_name, :redis => redis)
+ raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity_name, check_name) if entity_check.nil?
+ entity_check
+ end
+
+ def apply_json_patch(object_path, &block)
+ ops = params[:ops]
+
+ if ops.nil? || !ops.is_a?(Array)
+ halt err(400, "Invalid JSON-Patch request")
+ end
+
+ ops.each do |operation|
+ linked = nil
+ property = nil
+
+ op = operation['op']
+ operation['path'] =~ /\A\/#{object_path}\/0\/([^\/]+)(?:\/([^\/]+)(?:\/([^\/]+))?)?\z/
+ if 'links'.eql?($1)
+ linked = $2
+
+ value = case op
+ when 'add'
+ operation['value']
+ when 'remove'
+ $3
+ end
+ elsif 'replace'.eql?(op)
+ property = $1
+ value = operation['value']
+ else
+ next
+ end
+
+ yield(op, property, linked, value)
+ end
+ end
+
+ # NB: casts to UTC before converting to a timestamp
+ def validate_and_parsetime(value)
+ return unless value
+ Time.iso8601(value).getutc.to_i
+ rescue ArgumentError => e
+ logger.error "Couldn't parse time from '#{value}'"
+ nil
+ end
+
+ end
+
options '*' do
cors_headers
204
end
- not_found do
- err(404, "not routable")
+ # The following catch-all routes act as impromptu filters for their method types
+ get '*' do
+ content_type JSONAPI_MEDIA_TYPE
+ cors_headers
+ pass
end
- def cors_headers
- allow_headers = %w(* Content-Type Accept AUTHORIZATION Cache-Control)
- allow_methods = %w(GET POST PUT PATCH DELETE OPTIONS)
- expose_headers = %w(Cache-Control Content-Language Content-Type Expires Last-Modified Pragma)
- cors_headers = {
- 'Access-Control-Allow-Origin' => '*',
- 'Access-Control-Allow-Methods' => allow_methods.join(', '),
- 'Access-Control-Allow-Headers' => allow_headers.join(', '),
- 'Access-Control-Expose-Headers' => expose_headers.join(', '),
- 'Access-Control-Max-Age' => '1728000'
- }
- headers(cors_headers)
+ # bare 'params' may have splat/captures for regex route, see
+ # https://github.com/sinatra/sinatra/issues/453
+ post '*' do
+ halt(405) unless request.params.empty? || is_json_request? || is_jsonapi_request
+ content_type JSONAPI_MEDIA_TYPE
+ cors_headers
+ pass
end
- def location(ids)
- location = "#{base_url}#{request.path_info}#{ids.length == 1 ? '/' + ids.first : '?ids=' + ids.join(',')}"
- headers({'Location' => location})
+ patch '*' do
+ halt(405) unless is_jsonpatch_request?
+ content_type JSONAPI_MEDIA_TYPE
+ cors_headers
+ pass
end
- private
+ delete '*' do
+ cors_headers
+ pass
+ end
- def err(status, *msg)
- msg_str = msg.join(", ")
- logger.info "Error: #{msg_str}"
- [status, {}, {:errors => msg}.to_json]
+ register Flapjack::Gateways::JSONAPI::CheckMethods
+ register Flapjack::Gateways::JSONAPI::ContactMethods
+ register Flapjack::Gateways::JSONAPI::EntityMethods
+ register Flapjack::Gateways::JSONAPI::MediumMethods
+ register Flapjack::Gateways::JSONAPI::NotificationRuleMethods
+ register Flapjack::Gateways::JSONAPI::PagerdutyCredentialMethods
+ register Flapjack::Gateways::JSONAPI::ReportMethods
+
+ not_found do
+ err(404, "not routable")
end
+
end
end
end