class PactBroker::App

Attributes

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

Public Class Methods

new() { |configuration| ... } click to toggle source
# File lib/pact_broker/app.rb, line 41
def initialize
  @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::API }
  @configuration = PactBroker.configuration
  yield configuration
  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 middlware 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

any?() click to toggle source

rubocop: disable Lint/NestedMethodDefinition

# File lib/pact_broker/app.rb, line 139
def any?
  !empty?
end
build_api() click to toggle source
# File lib/pact_broker/app.rb, line 250
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::Convert404ToHal
  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 262
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 237
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 203
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 133
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.connection.extend_datasets do
    # rubocop: disable Lint/NestedMethodDefinition
    def any?
      !empty?
    end
    # rubocop: enable Lint/NestedMethodDefinition
  end
  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.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 182
def configure_middleware
  configure_basic_auth
  configure_rack_protection
  @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::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 222
def configure_rack_protection
  if configuration.use_rack_protection
    @app_builder.use Rack::Protection, except: [:path_traversal, :remote_token, :session_hijacking, :http_origin]

    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 270
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
load_configuration_from_database() click to toggle source
# File lib/pact_broker/app.rb, line 129
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 169
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)}"
  if configuration.auto_migrate_db
    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
  else
    logger.info "Skipping database schema migrations as database auto migrate is disabled"
  end

  if configuration.auto_migrate_db_data
    logger.info "Migrating data"
    PactBroker::DB.run_data_migrations configuration.database_connection
  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
running_app() click to toggle source
# File lib/pact_broker/app.rb, line 276
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 155
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