223 lines
6.5 KiB
Ruby
223 lines
6.5 KiB
Ruby
require 'conditional_skylight'
|
|
require 'travis'
|
|
require 'travis/model'
|
|
require 'travis/support/amqp'
|
|
require 'travis/states_cache'
|
|
require 'rack'
|
|
require 'rack/protection'
|
|
require 'rack/contrib'
|
|
require 'dalli'
|
|
require 'memcachier'
|
|
require 'rack/cache'
|
|
require 'rack/attack'
|
|
require 'active_record'
|
|
require 'redis'
|
|
require 'gh'
|
|
require 'raven'
|
|
require 'sidekiq'
|
|
require 'metriks/reporter/logger'
|
|
require 'metriks/librato_metrics_reporter'
|
|
require 'travis/support/log_subscriber/active_record_metrics'
|
|
require 'fileutils'
|
|
require 'travis/api/instruments'
|
|
require 'travis/api/v2/http'
|
|
require 'travis/api/v3'
|
|
require 'travis/api/app/stack_instrumentation'
|
|
require 'travis/api/app/error_handling'
|
|
|
|
# Rack class implementing the HTTP API.
|
|
# Instances respond to #call.
|
|
#
|
|
# run Travis::Api::App.new
|
|
#
|
|
# Requires TLS in production.
|
|
module Travis::Api
|
|
class App
|
|
autoload :AccessToken, 'travis/api/app/access_token'
|
|
autoload :Base, 'travis/api/app/base'
|
|
autoload :Endpoint, 'travis/api/app/endpoint'
|
|
autoload :Extensions, 'travis/api/app/extensions'
|
|
autoload :Helpers, 'travis/api/app/helpers'
|
|
autoload :Middleware, 'travis/api/app/middleware'
|
|
autoload :Responders, 'travis/api/app/responders'
|
|
autoload :Cors, 'travis/api/app/cors'
|
|
|
|
Rack.autoload :SSL, 'rack/ssl'
|
|
|
|
ERROR_RESPONSE = JSON.generate(error: 'Travis encountered an error, sorry :(')
|
|
|
|
# Used to track if setup already ran.
|
|
def self.setup?
|
|
@setup ||= false
|
|
end
|
|
|
|
# Loads all endpoints and middleware and hooks them up properly.
|
|
# Calls #setup on any middleware and endpoint.
|
|
#
|
|
# This method is not threadsafe, but called when loading
|
|
# the environment, so no biggy.
|
|
def self.setup(options = {})
|
|
setup! unless setup?
|
|
Endpoint.set(options) if options
|
|
FileUtils.touch('/tmp/app-initialized') if ENV['DYNO'] # Heroku
|
|
end
|
|
|
|
def self.new(options = {})
|
|
setup(options)
|
|
super()
|
|
end
|
|
|
|
def self.deploy_sha
|
|
@deploy_sha ||= File.exist?(deploy_sha_path) ? File.read(deploy_sha_path)[0..7] : 'deploy-sha'
|
|
end
|
|
|
|
def self.deploy_sha_path
|
|
File.expand_path('../../../../.deploy-sha', __FILE__)
|
|
end
|
|
|
|
attr_accessor :app
|
|
|
|
def initialize
|
|
@app = Rack::Builder.app do
|
|
|
|
if ENV['STACKPROF']
|
|
require 'stackprof'
|
|
puts "Setting up stack profiling."
|
|
use StackProf::Middleware, enabled: true, save_every: 1, mode: :object
|
|
end
|
|
|
|
extend StackInstrumentation
|
|
use Travis::Api::App::Middleware::Skylight
|
|
use(Rack::Config) { |env| env['metriks.request.start'] ||= Time.now.utc }
|
|
|
|
Rack::Utils::HTTP_STATUS_CODES[420] = "Enhance Your Calm"
|
|
use Rack::Attack
|
|
Rack::Attack.blacklist('block client requesting ruby builds') do |req|
|
|
req.ip == "130.15.4.210"
|
|
end
|
|
|
|
Rack::Attack.blacklisted_response = lambda do |env|
|
|
[ 420, {}, ['Enhance Your Calm']]
|
|
end
|
|
|
|
use Travis::Api::App::Cors # if Travis.env == 'development' ???
|
|
use Raven::Rack if Travis.env == 'production' || Travis.env == 'staging'
|
|
use Rack::Protection::PathTraversal
|
|
use Rack::SSL if Endpoint.production?
|
|
use ActiveRecord::ConnectionAdapters::ConnectionManagement
|
|
use ActiveRecord::QueryCache
|
|
|
|
memcache_servers = ENV['MEMCACHIER_SERVERS']
|
|
if Travis::Features.feature_active?(:use_rack_cache) && memcache_servers
|
|
use Rack::Cache,
|
|
metastore: "memcached://#{memcache_servers}/meta-#{Travis::Api::App.deploy_sha}",
|
|
entitystore: "memcached://#{memcache_servers}/body-#{Travis::Api::App.deploy_sha}"
|
|
end
|
|
|
|
use Rack::Deflater
|
|
use Rack::PostBodyContentTypeParser
|
|
use Rack::JSONP
|
|
|
|
use Rack::Config do |env|
|
|
env['SCRIPT_NAME'] = env['HTTP_X_SCRIPT_NAME'].to_s + env['SCRIPT_NAME'].to_s
|
|
env['travis.global_prefix'] = env['SCRIPT_NAME']
|
|
end
|
|
|
|
|
|
use Travis::Api::App::Middleware::Logging
|
|
use Travis::Api::App::Middleware::ScopeCheck
|
|
use Travis::Api::App::Middleware::UserAgentTracker
|
|
use Travis::Api::App::Middleware::Metriks
|
|
|
|
# if this is a v3 API request, ignore everything after
|
|
use Travis::API::V3::OptIn
|
|
|
|
# rewrite should come after V3 hook
|
|
use Travis::Api::App::Middleware::Rewrite
|
|
|
|
SettingsEndpoint.subclass :env_vars
|
|
if Travis.config.endpoints.ssh_key
|
|
SingletonSettingsEndpoint.subclass :ssh_key
|
|
end
|
|
|
|
Endpoint.subclasses.each do |e|
|
|
next if e == SettingsEndpoint # TODO: add something like abstract? method to check if
|
|
# class should be registered
|
|
map(e.prefix) { run(e.new) }
|
|
end
|
|
end
|
|
end
|
|
|
|
# Rack protocol
|
|
def call(env)
|
|
app.call(env)
|
|
rescue
|
|
if Endpoint.production?
|
|
[500, {'Content-Type' => 'application/json'}, [ERROR_RESPONSE]]
|
|
else
|
|
raise
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def self.console?
|
|
defined? Travis::Console
|
|
end
|
|
|
|
def self.use_monitoring?
|
|
Travis.env == 'production' || Travis.env == 'staging'
|
|
end
|
|
|
|
def self.setup!
|
|
setup_travis
|
|
load_endpoints
|
|
setup_endpoints
|
|
@setup = true
|
|
end
|
|
|
|
def self.setup_travis
|
|
Travis::Async.enabled = true
|
|
Travis::Amqp.config = Travis.config.amqp
|
|
|
|
setup_database_connections
|
|
|
|
if use_monitoring?
|
|
Sidekiq.configure_client do |config|
|
|
config.redis = Travis.config.redis.merge(size: 1, namespace: Travis.config.sidekiq.namespace)
|
|
end
|
|
end
|
|
|
|
if use_monitoring? and not console?
|
|
setup_monitoring
|
|
end
|
|
end
|
|
|
|
def self.setup_database_connections
|
|
Travis::Database.connect
|
|
|
|
if Travis.config.logs_database
|
|
Log.establish_connection 'logs_database'
|
|
Log::Part.establish_connection 'logs_database'
|
|
end
|
|
end
|
|
|
|
def self.setup_monitoring
|
|
Travis::Api::App::ErrorHandling.setup
|
|
|
|
Travis::LogSubscriber::ActiveRecordMetrics.attach
|
|
Travis::Notification.setup(instrumentation: false)
|
|
Travis::Metrics.setup
|
|
end
|
|
|
|
def self.load_endpoints
|
|
Dir.glob("#{__dir__}/app/middleware/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
|
Dir.glob("#{__dir__}/app/endpoint/*.rb").each { |f| require f[%r[(?<=lib/).+(?=\.rb$)]] }
|
|
end
|
|
|
|
def self.setup_endpoints
|
|
Base.subclasses.each(&:setup)
|
|
end
|
|
end
|
|
end
|