class PactBroker::App

Attributes

application_context[RW]
configuration[RW]
create_pact_broker_api_block[R]
custom_ui[R]

Public Class Methods

new(application_context = PactBroker::ApplicationContext.default_application_context) { |configuration| ... } click to toggle source
# File lib/pact_broker/app.rb, line 40
def initialize(application_context = PactBroker::ApplicationContext.default_application_context)
  @application_context = application_context
  @app_builder = ::Rack::Builder.new
  @cascade_apps = []
  @make_it_later_api_auth = ::Rack::PactBroker::ConfigurableMakeItLater.new(Rack::PactBroker::NoAuth)
  @make_it_later_ui_auth = ::Rack::PactBroker::ConfigurableMakeItLater.new(Rack::PactBroker::NoAuth)
  # Can only be required after database connection has been made because the decorators rely on the Sequel models
  @create_pact_broker_api_block = ->() { require "pact_broker/api"; PactBroker.build_api(application_context) }
  @configuration = PactBroker.configuration
  yield configuration if block_given?
  post_configure
  prepare_database
  load_configuration_from_database
  seed_example_data
  print_startup_message
  @configuration.freeze
end

Public Instance Methods

call(env) click to toggle source
# File lib/pact_broker/app.rb, line 89
def call env
  running_app.call env
end
use(*args, &block) click to toggle source

Allows middleware to be inserted at the bottom of the shared middleware stack (ie just before the cascade is called for diagnostic, UI and API). To insert middleware at the top of the stack, initialize the middleware with the app, and run it manually. eg run MyMiddleware.new(app)

# File lib/pact_broker/app.rb, line 63
def use *args, &block
  @app_builder.use(*args, &block)
end
use_api_auth(middleware) click to toggle source

private API, not sure if this will continue to be supported

# File lib/pact_broker/app.rb, line 68
def use_api_auth middleware
  @make_it_later_api_auth.make_it_later(middleware)
end
use_custom_api(custom_api) click to toggle source
# File lib/pact_broker/app.rb, line 81
def use_custom_api custom_api
  @custom_api = custom_api
end
use_custom_ui(custom_ui) click to toggle source
# File lib/pact_broker/app.rb, line 77
def use_custom_ui custom_ui
  @custom_ui = custom_ui
end
use_to_create_pact_broker_api(&block) click to toggle source
# File lib/pact_broker/app.rb, line 85
def use_to_create_pact_broker_api &block
  @create_pact_broker_api_block = block
end
use_ui_auth(middleware) click to toggle source

private API, not sure if this will continue to be supported

# File lib/pact_broker/app.rb, line 73
def use_ui_auth middleware
  @make_it_later_ui_auth.make_it_later(middleware)
end

Private Instance Methods

build_api() click to toggle source
# File lib/pact_broker/app.rb, line 268
def build_api
  logger.info "Mounting PactBroker::API"
  api_apps = [create_pact_broker_api_block.call]
  api_apps.unshift(@custom_api) if @custom_api
  builder = ::Rack::Builder.new
  builder.use @make_it_later_api_auth
  builder.use Rack::PactBroker::DatabaseTransaction, configuration.database_connection
  builder.run Rack::Cascade.new(api_apps, [404])
  builder
end
build_diagnostic() click to toggle source
# File lib/pact_broker/app.rb, line 279
def build_diagnostic
  require "pact_broker/diagnostic/app"
  builder = ::Rack::Builder.new
  builder.use @make_it_later_api_auth
  builder.run PactBroker::Diagnostic::App.new
  builder
end
build_ui() click to toggle source
# File lib/pact_broker/app.rb, line 255
def build_ui
  logger.info "Mounting UI"
  require "pact_broker/ui"
  ui_apps = [PactBroker::UI::App.new]
  ui_apps.unshift(@custom_ui) if @custom_ui
  builder = ::Rack::Builder.new
  builder.use Rack::PactBroker::UIRequestFilter
  builder.use @make_it_later_ui_auth
  builder.use Rack::PactBroker::UIAuthentication # deprecate?
  builder.run Rack::Cascade.new(ui_apps)
  builder
end
configure_basic_auth() click to toggle source
# File lib/pact_broker/app.rb, line 214
def configure_basic_auth
  if configuration.basic_auth_enabled
    logger.info "Configuring basic auth"
    logger.warn "No basic auth credentials are configured" unless configuration.basic_auth_credentials_provided?
    logger.info "Public read access is enabled" if configuration.allow_public_read
    policy = PactBroker::Api::Authorization::ResourceAccessPolicy
              .build(
                configuration.allow_public_read,
                configuration.public_heartbeat,
                configuration.enable_public_badge_access
              )

    @app_builder.use PactBroker::Api::Middleware::BasicAuth,
      configuration.basic_auth_write_credentials,
      configuration.basic_auth_read_credentials,
      policy
  end
end
configure_database_connection() click to toggle source
# File lib/pact_broker/app.rb, line 147
def configure_database_connection
  # Keep this configuration in sync with lib/db.rb
  configuration.database_connection ||= PactBroker.create_database_connection(configuration.database_configuration, configuration.logger)
  PactBroker::DB.connection = configuration.database_connection
  PactBroker::DB.validate_connection_config if configuration.validate_database_connection_config
  PactBroker::DB.set_mysql_strict_mode_if_mysql
  PactBroker::DB.connection.extension(:pagination)
  PactBroker::DB.connection.extension(:statement_timeout)
  PactBroker::DB.connection.extension(:any_not_empty)
  PactBroker::DB.connection.timezone = :utc
  Sequel.datetime_class = DateTime
  Sequel.database_timezone = :utc # Store all dates in UTC, assume any date without a TZ is UTC
  Sequel.application_timezone = :local # Convert dates to localtime when retrieving from database
  Sequel.typecast_timezone = :utc # If no timezone specified on dates going into the database, assume they are UTC
end
configure_middleware() click to toggle source
# File lib/pact_broker/app.rb, line 190
def configure_middleware
  @app_builder.use PactBroker::Api::Middleware::HttpDebugLogs if configuration.http_debug_logging_enabled
  configure_basic_auth
  configure_rack_protection
  @app_builder.use Rack::PactBroker::ApplicationContext, application_context
  @app_builder.use Rack::PactBroker::InvalidUriProtection
  @app_builder.use Rack::PactBroker::ResetThreadData
  @app_builder.use Rack::PactBroker::AddPactBrokerVersionHeader
  @app_builder.use Rack::PactBroker::AddVaryHeader
  @app_builder.use Rack::Static, :urls => ["/stylesheets", "/css", "/fonts", "/js", "/javascripts", "/images"], :root => PactBroker.project_root.join("public")
  @app_builder.use Rack::Static, :urls => ["/favicon.ico"], :root => PactBroker.project_root.join("public/images"), header_rules: [[:all, {"Content-Type" => "image/x-icon"}]]
  @app_builder.use Rack::PactBroker::AddCacheHeader
  @app_builder.use Rack::PactBroker::ConvertFileExtensionToAcceptHeader
  # Rack::PactBroker::SetBaseUrl needs to be before the Rack::PactBroker::HalBrowserRedirect
  @app_builder.use Rack::PactBroker::SetBaseUrl, configuration.base_urls

  if configuration.use_hal_browser
    logger.info "Mounting HAL browser"
    @app_builder.use Rack::HalBrowser::Redirect
  else
    logger.info "Not mounting HAL browser"
  end
end
configure_rack_protection() click to toggle source
# File lib/pact_broker/app.rb, line 233
def configure_rack_protection
  if configuration.use_rack_protection
    rack_protection_options = {
      logger: logger,
      use: configuration.rack_protection_use,
      except: configuration.rack_protection_except
    }.compact

    logger.info("Configuring Rack::Protection", rack_protection_options)
    @app_builder.use Rack::Protection, rack_protection_options

    is_hal_browser = ->(env) { env["PATH_INFO"] == "/hal-browser/browser.html" }
    not_hal_browser = ->(env) { env["PATH_INFO"] != "/hal-browser/browser.html" }

    @app_builder.use_when not_hal_browser,
      Rack::Protection::ContentSecurityPolicy, configuration.content_security_policy
    @app_builder.use_when is_hal_browser,
      Rack::Protection::ContentSecurityPolicy,
      configuration.content_security_policy.merge(configuration.hal_browser_content_security_policy_overrides)
  end
end
configure_sucker_punch() click to toggle source
# File lib/pact_broker/app.rb, line 287
def configure_sucker_punch
  SuckerPunch.exception_handler = -> (ex, klass, args) do
    PactBroker.logger.warn("Unhandled Suckerpunch error for #{klass}.perform(#{args.inspect})", ex)
  end
end
ensure_all_database_migrations_are_applied() click to toggle source
# File lib/pact_broker/app.rb, line 126
def ensure_all_database_migrations_are_applied
  migration_options = { allow_missing_migration_files: configuration.allow_missing_migration_files }

  if PactBroker::DB.is_current?(configuration.database_connection, migration_options)
    logger.info "Skipping database migrations as the latest migration has already been applied"
  else
    logger.info "Migrating database schema"
    PactBroker::DB.run_migrations(configuration.database_connection, migration_options)
    logger.info "Database schema version is now #{PactBroker::DB.version(configuration.database_connection)}"
  end
end
load_configuration_from_database() click to toggle source
# File lib/pact_broker/app.rb, line 143
def load_configuration_from_database
  configuration.load_from_database!
end
post_configure() click to toggle source
# File lib/pact_broker/app.rb, line 97
def post_configure
  SuckerPunch.logger = configuration.custom_logger || SemanticLogger["SuckerPunch"]
  configure_database_connection
  configure_sucker_punch
end
prepare_app() click to toggle source
# File lib/pact_broker/app.rb, line 177
def prepare_app
  configure_middleware

  # need this first so UI login logic is performed before API login logic
  @cascade_apps << build_ui

  if configuration.enable_diagnostic_endpoints
    @cascade_apps << build_diagnostic
  end

  @cascade_apps << build_api
end
prepare_database() click to toggle source
# File lib/pact_broker/app.rb, line 103
def prepare_database
  logger.info "Database schema version is #{PactBroker::DB.version(configuration.database_connection)}"
  lock = PactBroker::DB::AdvisoryLock.new(configuration.database_connection, :migrate, :pg_advisory_lock)
  if configuration.auto_migrate_db
    lock.with_lock do
      ensure_all_database_migrations_are_applied
    end
  else
    logger.info "Skipping database schema migrations as database auto migrate is disabled"
  end

  if configuration.auto_migrate_db_data
    lock.with_lock do
      run_data_migrations
    end
  else
    logger.info "Skipping data migrations"
  end

  require "pact_broker/webhooks/service"
  PactBroker::Webhooks::Service.fail_retrying_triggered_webhooks
end
print_startup_message() click to toggle source
run_data_migrations() click to toggle source
# File lib/pact_broker/app.rb, line 138
def run_data_migrations
  logger.info "Migrating data"
  PactBroker::DB.run_data_migrations(configuration.database_connection)
end
running_app() click to toggle source
# File lib/pact_broker/app.rb, line 293
def running_app
  @running_app ||= begin
    prepare_app
    apps = @cascade_apps
    @app_builder.map "/" do
      run Rack::Cascade.new(apps, [404])
    end
    @app_builder
  end
end
seed_example_data() click to toggle source
# File lib/pact_broker/app.rb, line 163
def seed_example_data
  if configuration.seed_example_data && configuration.example_data_seeder
    logger.info "Seeding example data"
    configuration.example_data_seeder.call
    logger.info "Marking seed as done"
    require "pact_broker/config/repository"
    PactBroker::Config::Repository.new.create_or_update_setting(:seed_example_data, false)
  else
    logger.info "Not seeding example data"
  end
rescue StandardError => e
  logger.error "Error running example data seeder, #{e.class} #{e.message}", e
end